@seaverseai/auth-sdk 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,2566 @@
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
+ * Environment Configuration for SeaVerse SDK
1031
+ * 环境配置模块 - 支持多环境部署
1032
+ */
1033
+ /**
1034
+ * 预定义的环境配置
1035
+ *
1036
+ * 使用说明:
1037
+ * - production: 正式生产环境
1038
+ * - staging: 预发布环境(当前默认)
1039
+ * - development: 开发测试环境
1040
+ * - local: 本地开发环境
1041
+ */
1042
+ const ENVIRONMENT_CONFIGS = {
1043
+ production: {
1044
+ name: 'production',
1045
+ baseURL: 'https://api.seaverse.com',
1046
+ wsURL: 'wss://api.seaverse.com',
1047
+ isProduction: true,
1048
+ },
1049
+ staging: {
1050
+ name: 'staging',
1051
+ baseURL: 'https://account-hub.sg.seaverse.dev',
1052
+ wsURL: 'wss://account-hub.sg.seaverse.dev',
1053
+ isProduction: false,
1054
+ },
1055
+ development: {
1056
+ name: 'development',
1057
+ baseURL: 'https://api-dev.seaverse.dev',
1058
+ wsURL: 'wss://api-dev.seaverse.dev',
1059
+ isProduction: false,
1060
+ },
1061
+ local: {
1062
+ name: 'local',
1063
+ baseURL: 'http://localhost:8001',
1064
+ wsURL: 'ws://localhost:8000',
1065
+ isProduction: false,
1066
+ },
1067
+ };
1068
+ /**
1069
+ * 根据当前运行环境自动检测应该使用的环境配置
1070
+ *
1071
+ * 检测规则:
1072
+ * 1. localhost/127.0.0.1 → local
1073
+ * 2. 包含 -dev 或 .dev → development
1074
+ * 3. 包含 staging → staging
1075
+ * 4. 其他 → production (默认)
1076
+ *
1077
+ * @returns 检测到的环境类型
1078
+ */
1079
+ function detectEnvironment() {
1080
+ // Node.js 环境或非浏览器环境,默认返回 staging(安全起见)
1081
+ if (typeof window === 'undefined' || typeof window.location === 'undefined') {
1082
+ return 'staging';
1083
+ }
1084
+ const hostname = window.location.hostname.toLowerCase();
1085
+ // 本地开发环境
1086
+ if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '0.0.0.0') {
1087
+ return 'local';
1088
+ }
1089
+ // 开发环境 (包含 -dev 或 .dev)
1090
+ if (hostname.includes('-dev.') || hostname.includes('.dev') || hostname.endsWith('.dev')) {
1091
+ return 'development';
1092
+ }
1093
+ // Staging 环境
1094
+ if (hostname.includes('-staging.') || hostname.includes('staging.')) {
1095
+ return 'staging';
1096
+ }
1097
+ // 默认使用 production
1098
+ return 'production';
1099
+ }
1100
+ /**
1101
+ * 获取环境配置
1102
+ *
1103
+ * @param env 环境类型,如果不提供则自动检测
1104
+ * @returns 环境配置对象
1105
+ */
1106
+ function getEnvironmentConfig(env) {
1107
+ const targetEnv = env || detectEnvironment();
1108
+ return ENVIRONMENT_CONFIGS[targetEnv];
1109
+ }
1110
+ /**
1111
+ * 解析用户配置,返回最终的 baseURL
1112
+ *
1113
+ * 优先级:
1114
+ * 1. 显式传入的 baseURL(最高优先级)
1115
+ * 2. environment 参数指定的环境
1116
+ * 3. 自动检测环境(最低优先级)
1117
+ *
1118
+ * @param options 用户配置选项
1119
+ * @returns 最终使用的 baseURL
1120
+ */
1121
+ function resolveBaseURL(options) {
1122
+ // 优先级 1: 显式传入的 baseURL
1123
+ if (options.baseURL) {
1124
+ return options.baseURL;
1125
+ }
1126
+ // 优先级 2: environment 参数
1127
+ if (options.environment) {
1128
+ return ENVIRONMENT_CONFIGS[options.environment].baseURL;
1129
+ }
1130
+ // 优先级 3: 自动检测
1131
+ const detectedEnv = detectEnvironment();
1132
+ return ENVIRONMENT_CONFIGS[detectedEnv].baseURL;
1133
+ }
1134
+
1135
+ /**
1136
+ * Type definitions for SeaVerse Dispatcher API
1137
+ * Generated from auth.yaml OpenAPI specification
1138
+ */
1139
+
1140
+ var models = /*#__PURE__*/Object.freeze({
1141
+ __proto__: null
1142
+ });
1143
+
1144
+ /**
1145
+ * SeaVerse Backend API Client
1146
+ * SeaVerse Dispatcher API - 云原生 AI Agent 平台的统一网关服务
1147
+ *
1148
+ * @version 2.0.0
1149
+ *
1150
+ * @example
1151
+ * // 方式 1: 自动检测环境(推荐本地开发)
1152
+ * const client = new SeaVerseBackendAPIClient();
1153
+ *
1154
+ * @example
1155
+ * // 方式 2: 指定环境(推荐生产部署)
1156
+ * const client = new SeaVerseBackendAPIClient({
1157
+ * environment: 'production',
1158
+ * });
1159
+ *
1160
+ * @example
1161
+ * // 方式 3: 自定义 URL(特殊需求)
1162
+ * const client = new SeaVerseBackendAPIClient({
1163
+ * baseURL: 'https://custom-api.example.com',
1164
+ * });
1165
+ */
1166
+ class SeaVerseBackendAPIClient {
1167
+ constructor(options = {}) {
1168
+ // 使用智能配置解析,支持三种优先级:
1169
+ // 1. 显式 baseURL(最高)
1170
+ // 2. environment 参数
1171
+ // 3. 自动检测(默认)
1172
+ const finalBaseURL = resolveBaseURL({
1173
+ baseURL: options.baseURL,
1174
+ environment: options.environment,
1175
+ });
1176
+ const httpOptions = {
1177
+ baseURL: finalBaseURL,
1178
+ timeout: options.timeout,
1179
+ headers: options.headers,
1180
+ auth: options.auth || this.getDefaultAuth(),
1181
+ hooks: options.hooks || this.getDefaultHooks(),
1182
+ };
1183
+ this.httpClient = new HttpClient(httpOptions);
1184
+ }
1185
+ /**
1186
+ * Get default authentication configuration
1187
+ */
1188
+ getDefaultAuth() {
1189
+ const token = getEnvConfig('JWT_TOKEN');
1190
+ if (!token)
1191
+ return undefined;
1192
+ return AuthFactory.create({
1193
+ type: 'jwt',
1194
+ credentials: {
1195
+ type: 'jwt',
1196
+ token,
1197
+ },
1198
+ });
1199
+ }
1200
+ /**
1201
+ * Get default hooks configuration
1202
+ */
1203
+ getDefaultHooks() {
1204
+ return {
1205
+ hooks: [
1206
+ BuiltInHooks.createLoggerHook({
1207
+ logLevel: 'info',
1208
+ logRequestBody: true,
1209
+ logResponseBody: true,
1210
+ }),
1211
+ BuiltInHooks.createRequestIdHook(),
1212
+ ],
1213
+ };
1214
+ }
1215
+ // ============================================================================
1216
+ // Authentication & OAuth APIs
1217
+ // ============================================================================
1218
+ /**
1219
+ * Health check
1220
+ * Check if the service is healthy
1221
+ */
1222
+ async getHealth(options) {
1223
+ const config = {
1224
+ method: 'GET',
1225
+ url: `/health`,
1226
+ headers: {
1227
+ 'X-Operation-Id': 'getHealth',
1228
+ ...options?.headers,
1229
+ },
1230
+ ...options,
1231
+ };
1232
+ const response = await this.httpClient.request(config);
1233
+ return response.data;
1234
+ }
1235
+ /**
1236
+ * User registration
1237
+ * Register a new user with email verification
1238
+ */
1239
+ async register(data, options) {
1240
+ const config = {
1241
+ method: 'POST',
1242
+ url: `/api/auth/register`,
1243
+ data,
1244
+ headers: {
1245
+ 'X-Operation-Id': 'register',
1246
+ ...options?.headers,
1247
+ },
1248
+ ...options,
1249
+ };
1250
+ const response = await this.httpClient.request(config);
1251
+ return response.data;
1252
+ }
1253
+ /**
1254
+ * User login
1255
+ * Login with email and password
1256
+ */
1257
+ async login(data, options) {
1258
+ const config = {
1259
+ method: 'POST',
1260
+ url: `/api/auth/login`,
1261
+ data,
1262
+ headers: {
1263
+ 'X-Operation-Id': 'login',
1264
+ ...options?.headers,
1265
+ },
1266
+ ...options,
1267
+ };
1268
+ const response = await this.httpClient.request(config);
1269
+ return response.data;
1270
+ }
1271
+ /**
1272
+ * Get current user
1273
+ * Get the authenticated user's information
1274
+ */
1275
+ async getCurrentUser(options) {
1276
+ const config = {
1277
+ method: 'GET',
1278
+ url: `/api/auth/me`,
1279
+ headers: {
1280
+ 'X-Operation-Id': 'getCurrentUser',
1281
+ ...options?.headers,
1282
+ },
1283
+ ...options,
1284
+ };
1285
+ const response = await this.httpClient.request(config);
1286
+ return response.data;
1287
+ }
1288
+ /**
1289
+ * User logout
1290
+ * Logout the current user
1291
+ */
1292
+ async logout(options) {
1293
+ const config = {
1294
+ method: 'POST',
1295
+ url: `/api/auth/logout`,
1296
+ headers: {
1297
+ 'X-Operation-Id': 'logout',
1298
+ ...options?.headers,
1299
+ },
1300
+ ...options,
1301
+ };
1302
+ const response = await this.httpClient.request(config);
1303
+ return response.data;
1304
+ }
1305
+ /**
1306
+ * Request password reset
1307
+ * Send password reset email
1308
+ */
1309
+ async forgotPassword(data, options) {
1310
+ const config = {
1311
+ method: 'POST',
1312
+ url: `/api/auth/forgot-password`,
1313
+ data,
1314
+ headers: {
1315
+ 'X-Operation-Id': 'forgotPassword',
1316
+ ...options?.headers,
1317
+ },
1318
+ ...options,
1319
+ };
1320
+ const response = await this.httpClient.request(config);
1321
+ return response.data;
1322
+ }
1323
+ /**
1324
+ * Reset password
1325
+ * Reset password with token from email
1326
+ */
1327
+ async resetPassword(data, options) {
1328
+ const config = {
1329
+ method: 'POST',
1330
+ url: `/api/auth/reset-password`,
1331
+ data,
1332
+ headers: {
1333
+ 'X-Operation-Id': 'resetPassword',
1334
+ ...options?.headers,
1335
+ },
1336
+ ...options,
1337
+ };
1338
+ const response = await this.httpClient.request(config);
1339
+ return response.data;
1340
+ }
1341
+ /**
1342
+ * Get api-service token
1343
+ * Generate token for accessing api-service from sandbox
1344
+ */
1345
+ async getApiServiceToken(options) {
1346
+ const config = {
1347
+ method: 'GET',
1348
+ url: `/api/auth/api-service-token`,
1349
+ headers: {
1350
+ 'X-Operation-Id': 'getApiServiceToken',
1351
+ ...options?.headers,
1352
+ },
1353
+ ...options,
1354
+ };
1355
+ const response = await this.httpClient.request(config);
1356
+ return response.data;
1357
+ }
1358
+ // ============================================================================
1359
+ // OAuth APIs
1360
+ // ============================================================================
1361
+ /**
1362
+ * Exchange Google code for token
1363
+ * Exchange Google authorization code for JWT token (for app)
1364
+ */
1365
+ async googleCodeToToken(data, options) {
1366
+ const config = {
1367
+ method: 'POST',
1368
+ url: `/api/auth/google/code2token`,
1369
+ data,
1370
+ headers: {
1371
+ 'X-Operation-Id': 'googleCodeToToken',
1372
+ ...options?.headers,
1373
+ },
1374
+ ...options,
1375
+ };
1376
+ const response = await this.httpClient.request(config);
1377
+ return response.data;
1378
+ }
1379
+ /**
1380
+ * Unlink Google account
1381
+ * Unlink the user's Google account
1382
+ */
1383
+ async unlinkGoogle(options) {
1384
+ const config = {
1385
+ method: 'POST',
1386
+ url: `/api/auth/google/unlink`,
1387
+ headers: {
1388
+ 'X-Operation-Id': 'unlinkGoogle',
1389
+ ...options?.headers,
1390
+ },
1391
+ ...options,
1392
+ };
1393
+ const response = await this.httpClient.request(config);
1394
+ return response.data;
1395
+ }
1396
+ /**
1397
+ * Exchange Discord code for token
1398
+ */
1399
+ async discordCodeToToken(data, options) {
1400
+ const config = {
1401
+ method: 'POST',
1402
+ url: `/api/auth/discord/code2token`,
1403
+ data,
1404
+ headers: {
1405
+ 'X-Operation-Id': 'discordCodeToToken',
1406
+ ...options?.headers,
1407
+ },
1408
+ ...options,
1409
+ };
1410
+ const response = await this.httpClient.request(config);
1411
+ return response.data;
1412
+ }
1413
+ /**
1414
+ * Unlink Discord account
1415
+ */
1416
+ async unlinkDiscord(options) {
1417
+ const config = {
1418
+ method: 'POST',
1419
+ url: `/api/auth/discord/unlink`,
1420
+ headers: {
1421
+ 'X-Operation-Id': 'unlinkDiscord',
1422
+ ...options?.headers,
1423
+ },
1424
+ ...options,
1425
+ };
1426
+ const response = await this.httpClient.request(config);
1427
+ return response.data;
1428
+ }
1429
+ /**
1430
+ * Exchange GitHub code for token
1431
+ */
1432
+ async githubCodeToToken(data, options) {
1433
+ const config = {
1434
+ method: 'POST',
1435
+ url: `/api/auth/github/code2token`,
1436
+ data,
1437
+ headers: {
1438
+ 'X-Operation-Id': 'githubCodeToToken',
1439
+ ...options?.headers,
1440
+ },
1441
+ ...options,
1442
+ };
1443
+ const response = await this.httpClient.request(config);
1444
+ return response.data;
1445
+ }
1446
+ /**
1447
+ * Unlink GitHub account
1448
+ */
1449
+ async unlinkGithub(options) {
1450
+ const config = {
1451
+ method: 'POST',
1452
+ url: `/api/auth/github/unlink`,
1453
+ headers: {
1454
+ 'X-Operation-Id': 'unlinkGithub',
1455
+ ...options?.headers,
1456
+ },
1457
+ ...options,
1458
+ };
1459
+ const response = await this.httpClient.request(config);
1460
+ return response.data;
1461
+ }
1462
+ // ============================================================================
1463
+ // Container APIs
1464
+ // ============================================================================
1465
+ /**
1466
+ * List all containers
1467
+ */
1468
+ async listContainers(options) {
1469
+ const config = {
1470
+ method: 'GET',
1471
+ url: `/api/containers`,
1472
+ headers: {
1473
+ 'X-Operation-Id': 'listContainers',
1474
+ ...options?.headers,
1475
+ },
1476
+ ...options,
1477
+ };
1478
+ const response = await this.httpClient.request(config);
1479
+ return response.data;
1480
+ }
1481
+ /**
1482
+ * Register a container
1483
+ * Internal service call to register a new container
1484
+ */
1485
+ async registerContainer(data, options) {
1486
+ const config = {
1487
+ method: 'POST',
1488
+ url: `/api/containers`,
1489
+ data,
1490
+ headers: {
1491
+ 'X-Operation-Id': 'registerContainer',
1492
+ ...options?.headers,
1493
+ },
1494
+ ...options,
1495
+ };
1496
+ const response = await this.httpClient.request(config);
1497
+ return response.data;
1498
+ }
1499
+ /**
1500
+ * Get container by ID
1501
+ */
1502
+ async getContainer(id, options) {
1503
+ const config = {
1504
+ method: 'GET',
1505
+ url: `/api/containers/${id}`,
1506
+ headers: {
1507
+ 'X-Operation-Id': 'getContainer',
1508
+ ...options?.headers,
1509
+ },
1510
+ ...options,
1511
+ };
1512
+ const response = await this.httpClient.request(config);
1513
+ return response.data;
1514
+ }
1515
+ /**
1516
+ * Unregister a container
1517
+ * Internal service call to unregister a container
1518
+ */
1519
+ async unregisterContainer(id, options) {
1520
+ const config = {
1521
+ method: 'DELETE',
1522
+ url: `/api/containers/${id}`,
1523
+ headers: {
1524
+ 'X-Operation-Id': 'unregisterContainer',
1525
+ ...options?.headers,
1526
+ },
1527
+ ...options,
1528
+ };
1529
+ const response = await this.httpClient.request(config);
1530
+ return response.data;
1531
+ }
1532
+ /**
1533
+ * Get container statistics
1534
+ */
1535
+ async getContainerStats(options) {
1536
+ const config = {
1537
+ method: 'GET',
1538
+ url: `/api/containers/stats`,
1539
+ headers: {
1540
+ 'X-Operation-Id': 'getContainerStats',
1541
+ ...options?.headers,
1542
+ },
1543
+ ...options,
1544
+ };
1545
+ const response = await this.httpClient.request(config);
1546
+ return response.data;
1547
+ }
1548
+ /**
1549
+ * Container heartbeat
1550
+ * Internal service call to update container heartbeat
1551
+ */
1552
+ async containerHeartbeat(id, options) {
1553
+ const config = {
1554
+ method: 'POST',
1555
+ url: `/api/containers/${id}/heartbeat`,
1556
+ headers: {
1557
+ 'X-Operation-Id': 'containerHeartbeat',
1558
+ ...options?.headers,
1559
+ },
1560
+ ...options,
1561
+ };
1562
+ const response = await this.httpClient.request(config);
1563
+ return response.data;
1564
+ }
1565
+ // ============================================================================
1566
+ // Conversation APIs
1567
+ // ============================================================================
1568
+ /**
1569
+ * Get conversation status
1570
+ * Query conversation streaming status from Redis
1571
+ */
1572
+ async getConversationStatus(conversationId, options) {
1573
+ const config = {
1574
+ method: 'GET',
1575
+ url: `/api/conversations/${conversationId}/status`,
1576
+ headers: {
1577
+ 'X-Operation-Id': 'getConversationStatus',
1578
+ ...options?.headers,
1579
+ },
1580
+ ...options,
1581
+ };
1582
+ const response = await this.httpClient.request(config);
1583
+ return response.data;
1584
+ }
1585
+ // ============================================================================
1586
+ // Skills Marketplace APIs
1587
+ // ============================================================================
1588
+ /**
1589
+ * List marketplace skills
1590
+ */
1591
+ async listMarketplaceSkills(params, options) {
1592
+ const config = {
1593
+ method: 'GET',
1594
+ url: `/api/marketplace/skills`,
1595
+ params,
1596
+ headers: {
1597
+ 'X-Operation-Id': 'listMarketplaceSkills',
1598
+ ...options?.headers,
1599
+ },
1600
+ ...options,
1601
+ };
1602
+ const response = await this.httpClient.request(config);
1603
+ return response.data;
1604
+ }
1605
+ /**
1606
+ * Get skill details
1607
+ */
1608
+ async getMarketplaceSkill(skillId, options) {
1609
+ const config = {
1610
+ method: 'GET',
1611
+ url: `/api/marketplace/skills/${skillId}`,
1612
+ headers: {
1613
+ 'X-Operation-Id': 'getMarketplaceSkill',
1614
+ ...options?.headers,
1615
+ },
1616
+ ...options,
1617
+ };
1618
+ const response = await this.httpClient.request(config);
1619
+ return response.data;
1620
+ }
1621
+ /**
1622
+ * Install skill from marketplace
1623
+ */
1624
+ async installSkill(skillId, options) {
1625
+ const config = {
1626
+ method: 'POST',
1627
+ url: `/api/marketplace/skills/${skillId}/download`,
1628
+ headers: {
1629
+ 'X-Operation-Id': 'installSkill',
1630
+ ...options?.headers,
1631
+ },
1632
+ ...options,
1633
+ };
1634
+ const response = await this.httpClient.request(config);
1635
+ return response.data;
1636
+ }
1637
+ /**
1638
+ * List user installed skills
1639
+ */
1640
+ async listUserSkills(options) {
1641
+ const config = {
1642
+ method: 'GET',
1643
+ url: `/api/user/skills`,
1644
+ headers: {
1645
+ 'X-Operation-Id': 'listUserSkills',
1646
+ ...options?.headers,
1647
+ },
1648
+ ...options,
1649
+ };
1650
+ const response = await this.httpClient.request(config);
1651
+ return response.data;
1652
+ }
1653
+ /**
1654
+ * Uninstall skill
1655
+ */
1656
+ async uninstallSkill(localName, options) {
1657
+ const config = {
1658
+ method: 'DELETE',
1659
+ url: `/api/user/skills/${localName}`,
1660
+ headers: {
1661
+ 'X-Operation-Id': 'uninstallSkill',
1662
+ ...options?.headers,
1663
+ },
1664
+ ...options,
1665
+ };
1666
+ await this.httpClient.request(config);
1667
+ }
1668
+ // ============================================================================
1669
+ // Speech Service APIs
1670
+ // ============================================================================
1671
+ /**
1672
+ * Get Azure Speech token
1673
+ * Get temporary token for Azure Speech Service
1674
+ */
1675
+ async getSpeechToken(options) {
1676
+ const config = {
1677
+ method: 'GET',
1678
+ url: `/api/speech/token`,
1679
+ headers: {
1680
+ 'X-Operation-Id': 'getSpeechToken',
1681
+ ...options?.headers,
1682
+ },
1683
+ ...options,
1684
+ };
1685
+ const response = await this.httpClient.request(config);
1686
+ return response.data;
1687
+ }
1688
+ // ============================================================================
1689
+ // Deprecated/Legacy Methods (for backward compatibility)
1690
+ // ============================================================================
1691
+ /**
1692
+ * @deprecated Use register() instead
1693
+ */
1694
+ async postapiauthregister(data, options) {
1695
+ return this.register(data, options);
1696
+ }
1697
+ /**
1698
+ * @deprecated Use login() instead
1699
+ */
1700
+ async postapiauthlogin(data, options) {
1701
+ return this.login(data, options);
1702
+ }
1703
+ /**
1704
+ * @deprecated Use getHealth() instead
1705
+ */
1706
+ async gethealth(options) {
1707
+ return this.getHealth(options);
1708
+ }
1709
+ /**
1710
+ * @deprecated Use listContainers() instead
1711
+ */
1712
+ async getapicontainers(options) {
1713
+ return this.listContainers(options);
1714
+ }
1715
+ }
1716
+
1717
+ class AuthModal {
1718
+ constructor(options) {
1719
+ this.modal = null;
1720
+ this.currentView = 'login';
1721
+ this.client = options.client;
1722
+ this.options = options;
1723
+ }
1724
+ /**
1725
+ * Show the authentication modal
1726
+ */
1727
+ show(initialView = 'login') {
1728
+ this.currentView = initialView;
1729
+ if (!this.modal) {
1730
+ this.createModal();
1731
+ }
1732
+ this.modal.classList.remove('hidden');
1733
+ this.switchView(initialView);
1734
+ }
1735
+ /**
1736
+ * Hide the authentication modal
1737
+ */
1738
+ hide() {
1739
+ if (this.modal) {
1740
+ this.modal.classList.add('hidden');
1741
+ }
1742
+ }
1743
+ /**
1744
+ * Destroy the modal and remove from DOM
1745
+ */
1746
+ destroy() {
1747
+ if (this.modal) {
1748
+ this.modal.remove();
1749
+ this.modal = null;
1750
+ }
1751
+ }
1752
+ createModal() {
1753
+ const theme = this.options.theme || 'dark';
1754
+ // Create modal container
1755
+ this.modal = document.createElement('div');
1756
+ this.modal.id = 'authModal';
1757
+ this.modal.className = `auth-modal auth-modal-${theme}`;
1758
+ // Create backdrop
1759
+ const backdrop = document.createElement('div');
1760
+ backdrop.className = 'auth-modal-backdrop';
1761
+ this.modal.appendChild(backdrop);
1762
+ // Create content container
1763
+ const content = document.createElement('div');
1764
+ content.className = 'auth-modal-content';
1765
+ this.modal.appendChild(content);
1766
+ // Create left panel (branding)
1767
+ const leftPanel = this.createLeftPanel();
1768
+ content.appendChild(leftPanel);
1769
+ // Create right panel (forms)
1770
+ const rightPanel = this.createRightPanel();
1771
+ content.appendChild(rightPanel);
1772
+ // Append to body
1773
+ document.body.appendChild(this.modal);
1774
+ // Bind event listeners
1775
+ this.bindEventListeners();
1776
+ }
1777
+ createLeftPanel() {
1778
+ const leftPanel = document.createElement('div');
1779
+ leftPanel.className = 'auth-modal-left';
1780
+ // Logo
1781
+ const logo = document.createElement('div');
1782
+ logo.className = 'auth-modal-logo';
1783
+ const logoText = document.createElement('span');
1784
+ logoText.className = 'logo-text';
1785
+ logoText.textContent = 'SeaVerse';
1786
+ logo.appendChild(logoText);
1787
+ leftPanel.appendChild(logo);
1788
+ // Decoration
1789
+ const decoration = document.createElement('div');
1790
+ decoration.className = 'auth-modal-decoration';
1791
+ const orb1 = document.createElement('div');
1792
+ orb1.className = 'glow-orb glow-orb-1';
1793
+ const orb2 = document.createElement('div');
1794
+ orb2.className = 'glow-orb glow-orb-2';
1795
+ decoration.appendChild(orb1);
1796
+ decoration.appendChild(orb2);
1797
+ leftPanel.appendChild(decoration);
1798
+ // Branding
1799
+ const branding = document.createElement('div');
1800
+ branding.className = 'auth-modal-branding';
1801
+ const brandingTitle = document.createElement('h2');
1802
+ brandingTitle.className = 'branding-title';
1803
+ brandingTitle.textContent = 'Get Started with Us';
1804
+ const brandingSubtitle = document.createElement('p');
1805
+ brandingSubtitle.className = 'branding-subtitle';
1806
+ brandingSubtitle.textContent = "Every conversation changes this place. You're not just using Infinity. You're creating it.";
1807
+ branding.appendChild(brandingTitle);
1808
+ branding.appendChild(brandingSubtitle);
1809
+ leftPanel.appendChild(branding);
1810
+ return leftPanel;
1811
+ }
1812
+ createRightPanel() {
1813
+ const rightPanel = document.createElement('div');
1814
+ rightPanel.className = 'auth-modal-right';
1815
+ // Close button
1816
+ const closeBtn = document.createElement('button');
1817
+ closeBtn.className = 'auth-modal-close';
1818
+ closeBtn.setAttribute('aria-label', 'Close modal');
1819
+ 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>';
1820
+ rightPanel.appendChild(closeBtn);
1821
+ // Login form
1822
+ const loginForm = this.createLoginForm();
1823
+ rightPanel.appendChild(loginForm);
1824
+ // Signup form
1825
+ const signupForm = this.createSignupForm();
1826
+ rightPanel.appendChild(signupForm);
1827
+ // Forgot password form
1828
+ const forgotForm = this.createForgotPasswordForm();
1829
+ rightPanel.appendChild(forgotForm);
1830
+ // Success message
1831
+ const successMessage = this.createSuccessMessage();
1832
+ rightPanel.appendChild(successMessage);
1833
+ return rightPanel;
1834
+ }
1835
+ createLoginForm() {
1836
+ const container = document.createElement('div');
1837
+ container.id = 'loginForm';
1838
+ container.className = 'auth-form-view';
1839
+ // Header
1840
+ const header = document.createElement('div');
1841
+ header.className = 'auth-form-header';
1842
+ const title = document.createElement('h2');
1843
+ title.className = 'auth-form-title';
1844
+ title.textContent = 'Welcome back.';
1845
+ const subtitle = document.createElement('p');
1846
+ subtitle.className = 'auth-form-subtitle';
1847
+ subtitle.textContent = "Don't have an account? ";
1848
+ const signupLink = document.createElement('a');
1849
+ signupLink.href = '#';
1850
+ signupLink.id = 'showSignup';
1851
+ signupLink.className = 'link-primary';
1852
+ signupLink.textContent = 'Sign up';
1853
+ subtitle.appendChild(signupLink);
1854
+ header.appendChild(title);
1855
+ header.appendChild(subtitle);
1856
+ container.appendChild(header);
1857
+ // Form
1858
+ const form = document.createElement('form');
1859
+ form.id = 'loginFormElement';
1860
+ form.className = 'auth-form';
1861
+ // Email field
1862
+ const emailGroup = this.createFormGroup('loginEmail', 'Email', 'email', 'Email');
1863
+ form.appendChild(emailGroup);
1864
+ // Password field with forgot link
1865
+ const passwordGroup = document.createElement('div');
1866
+ passwordGroup.className = 'form-group';
1867
+ const groupHeader = document.createElement('div');
1868
+ groupHeader.className = 'form-group-header';
1869
+ const passwordLabel = document.createElement('label');
1870
+ passwordLabel.htmlFor = 'loginPassword';
1871
+ passwordLabel.className = 'form-label';
1872
+ passwordLabel.textContent = 'Password';
1873
+ const forgotLink = document.createElement('a');
1874
+ forgotLink.href = '#';
1875
+ forgotLink.id = 'forgotPasswordLink';
1876
+ forgotLink.className = 'forgot-password-link';
1877
+ forgotLink.textContent = 'Forgot Password?';
1878
+ groupHeader.appendChild(passwordLabel);
1879
+ groupHeader.appendChild(forgotLink);
1880
+ passwordGroup.appendChild(groupHeader);
1881
+ const passwordInputWrapper = this.createPasswordInput('loginPassword', 'Password');
1882
+ passwordGroup.appendChild(passwordInputWrapper);
1883
+ form.appendChild(passwordGroup);
1884
+ // Submit button
1885
+ const submitBtn = document.createElement('button');
1886
+ submitBtn.type = 'submit';
1887
+ submitBtn.id = 'loginButton';
1888
+ submitBtn.className = 'btn-auth-primary';
1889
+ const btnText = document.createElement('span');
1890
+ btnText.className = 'btn-text';
1891
+ btnText.textContent = 'Sign In';
1892
+ const btnLoader = document.createElement('span');
1893
+ btnLoader.className = 'btn-loader hidden';
1894
+ 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>';
1895
+ submitBtn.appendChild(btnText);
1896
+ submitBtn.appendChild(btnLoader);
1897
+ form.appendChild(submitBtn);
1898
+ // Divider
1899
+ const divider = document.createElement('div');
1900
+ divider.className = 'divider';
1901
+ divider.textContent = 'OR SIGN IN WITH';
1902
+ form.appendChild(divider);
1903
+ // Social buttons grid (Google + GitHub)
1904
+ const socialGrid = document.createElement('div');
1905
+ socialGrid.className = 'social-buttons-grid';
1906
+ // Google button
1907
+ const googleBtn = this.createSocialButton('google', 'Google', 'login');
1908
+ socialGrid.appendChild(googleBtn);
1909
+ // GitHub button
1910
+ const githubBtn = this.createSocialButton('github', 'Github', 'login');
1911
+ socialGrid.appendChild(githubBtn);
1912
+ form.appendChild(socialGrid);
1913
+ // Discord button (full width)
1914
+ const discordBtn = this.createSocialButton('discord', 'Discord', 'login', true);
1915
+ form.appendChild(discordBtn);
1916
+ container.appendChild(form);
1917
+ return container;
1918
+ }
1919
+ createSignupForm() {
1920
+ const container = document.createElement('div');
1921
+ container.id = 'signupForm';
1922
+ container.className = 'auth-form-view hidden';
1923
+ // Header
1924
+ const header = document.createElement('div');
1925
+ header.className = 'auth-form-header';
1926
+ const title = document.createElement('h2');
1927
+ title.className = 'auth-form-title';
1928
+ title.textContent = 'Create an account.';
1929
+ const subtitle = document.createElement('p');
1930
+ subtitle.className = 'auth-form-subtitle';
1931
+ subtitle.textContent = 'Already have an account? ';
1932
+ const loginLink = document.createElement('a');
1933
+ loginLink.href = '#';
1934
+ loginLink.id = 'showLogin';
1935
+ loginLink.className = 'link-primary';
1936
+ loginLink.textContent = 'Sign in';
1937
+ subtitle.appendChild(loginLink);
1938
+ header.appendChild(title);
1939
+ header.appendChild(subtitle);
1940
+ container.appendChild(header);
1941
+ // Form
1942
+ const form = document.createElement('form');
1943
+ form.id = 'signupFormElement';
1944
+ form.className = 'auth-form';
1945
+ // Email field
1946
+ const emailGroup = this.createFormGroup('signupEmail', 'Email', 'email', 'Email');
1947
+ form.appendChild(emailGroup);
1948
+ // Password field
1949
+ const passwordGroup = document.createElement('div');
1950
+ passwordGroup.className = 'form-group';
1951
+ const passwordLabel = document.createElement('label');
1952
+ passwordLabel.htmlFor = 'signupPassword';
1953
+ passwordLabel.className = 'form-label';
1954
+ passwordLabel.textContent = 'Password';
1955
+ passwordGroup.appendChild(passwordLabel);
1956
+ const passwordInputWrapper = this.createPasswordInput('signupPassword', 'Password');
1957
+ passwordGroup.appendChild(passwordInputWrapper);
1958
+ const strengthText = document.createElement('p');
1959
+ strengthText.className = 'strength-text';
1960
+ strengthText.textContent = 'Use 8+ characters with mix of letters, numbers & symbols';
1961
+ passwordGroup.appendChild(strengthText);
1962
+ form.appendChild(passwordGroup);
1963
+ // Confirm password field
1964
+ const confirmGroup = document.createElement('div');
1965
+ confirmGroup.className = 'form-group';
1966
+ const confirmLabel = document.createElement('label');
1967
+ confirmLabel.htmlFor = 'signupPasswordConfirm';
1968
+ confirmLabel.className = 'form-label';
1969
+ confirmLabel.textContent = 'Confirm Password';
1970
+ confirmGroup.appendChild(confirmLabel);
1971
+ const confirmInputWrapper = this.createPasswordInput('signupPasswordConfirm', 'Confirm Password');
1972
+ confirmGroup.appendChild(confirmInputWrapper);
1973
+ form.appendChild(confirmGroup);
1974
+ // Submit button
1975
+ const submitBtn = document.createElement('button');
1976
+ submitBtn.type = 'submit';
1977
+ submitBtn.id = 'signupButton';
1978
+ submitBtn.className = 'btn-auth-primary';
1979
+ const btnText = document.createElement('span');
1980
+ btnText.className = 'btn-text';
1981
+ btnText.textContent = 'Sign Up';
1982
+ const btnLoader = document.createElement('span');
1983
+ btnLoader.className = 'btn-loader hidden';
1984
+ 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>';
1985
+ submitBtn.appendChild(btnText);
1986
+ submitBtn.appendChild(btnLoader);
1987
+ form.appendChild(submitBtn);
1988
+ // Divider
1989
+ const divider = document.createElement('div');
1990
+ divider.className = 'divider';
1991
+ divider.textContent = 'OR SIGN UP WITH';
1992
+ form.appendChild(divider);
1993
+ // Social buttons grid (Google + GitHub)
1994
+ const socialGrid = document.createElement('div');
1995
+ socialGrid.className = 'social-buttons-grid';
1996
+ // Google button
1997
+ const googleBtn = this.createSocialButton('google', 'Google', 'signup');
1998
+ socialGrid.appendChild(googleBtn);
1999
+ // GitHub button
2000
+ const githubBtn = this.createSocialButton('github', 'Github', 'signup');
2001
+ socialGrid.appendChild(githubBtn);
2002
+ form.appendChild(socialGrid);
2003
+ // Discord button (full width)
2004
+ const discordBtn = this.createSocialButton('discord', 'Discord', 'signup', true);
2005
+ form.appendChild(discordBtn);
2006
+ container.appendChild(form);
2007
+ return container;
2008
+ }
2009
+ createForgotPasswordForm() {
2010
+ const container = document.createElement('div');
2011
+ container.id = 'forgotPasswordForm';
2012
+ container.className = 'auth-form-view hidden';
2013
+ // Icon
2014
+ const icon = document.createElement('div');
2015
+ icon.className = 'forgot-password-icon';
2016
+ 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>';
2017
+ container.appendChild(icon);
2018
+ // Header
2019
+ const header = document.createElement('div');
2020
+ header.className = 'auth-form-header';
2021
+ const title = document.createElement('h2');
2022
+ title.className = 'auth-form-title';
2023
+ title.textContent = 'Reset Password';
2024
+ const subtitle = document.createElement('p');
2025
+ subtitle.className = 'auth-form-subtitle';
2026
+ subtitle.textContent = "We'll send a magic link to your inbox";
2027
+ header.appendChild(title);
2028
+ header.appendChild(subtitle);
2029
+ container.appendChild(header);
2030
+ // Form
2031
+ const form = document.createElement('form');
2032
+ form.id = 'forgotPasswordFormElement';
2033
+ form.className = 'auth-form';
2034
+ const emailGroup = this.createFormGroup('resetEmail', 'Email Address', 'email', 'Enter your email');
2035
+ form.appendChild(emailGroup);
2036
+ const submitBtn = document.createElement('button');
2037
+ submitBtn.type = 'submit';
2038
+ submitBtn.id = 'resetButton';
2039
+ submitBtn.className = 'btn-auth-primary btn-forgot-password';
2040
+ 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>';
2041
+ form.appendChild(submitBtn);
2042
+ container.appendChild(form);
2043
+ // Footer
2044
+ const footer = document.createElement('div');
2045
+ footer.className = 'auth-footer forgot-footer';
2046
+ const backLink = document.createElement('a');
2047
+ backLink.href = '#';
2048
+ backLink.id = 'backToLogin';
2049
+ backLink.className = 'back-to-login';
2050
+ 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>';
2051
+ footer.appendChild(backLink);
2052
+ container.appendChild(footer);
2053
+ return container;
2054
+ }
2055
+ createSuccessMessage() {
2056
+ const container = document.createElement('div');
2057
+ container.id = 'authMessage';
2058
+ container.className = 'auth-form-view hidden';
2059
+ const content = document.createElement('div');
2060
+ content.className = 'auth-message-content';
2061
+ const icon = document.createElement('div');
2062
+ icon.className = 'message-icon';
2063
+ 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>';
2064
+ content.appendChild(icon);
2065
+ const title = document.createElement('h3');
2066
+ title.id = 'messageTitle';
2067
+ title.className = 'message-title';
2068
+ title.textContent = 'Success!';
2069
+ content.appendChild(title);
2070
+ const text = document.createElement('p');
2071
+ text.id = 'messageText';
2072
+ text.className = 'message-text';
2073
+ text.textContent = 'Operation completed successfully.';
2074
+ content.appendChild(text);
2075
+ const button = document.createElement('button');
2076
+ button.id = 'messageButton';
2077
+ button.className = 'btn-auth-primary';
2078
+ button.textContent = 'Continue';
2079
+ content.appendChild(button);
2080
+ container.appendChild(content);
2081
+ return container;
2082
+ }
2083
+ createFormGroup(id, label, type, placeholder) {
2084
+ const group = document.createElement('div');
2085
+ group.className = 'form-group';
2086
+ const labelEl = document.createElement('label');
2087
+ labelEl.htmlFor = id;
2088
+ labelEl.className = 'form-label';
2089
+ labelEl.textContent = label;
2090
+ group.appendChild(labelEl);
2091
+ const input = document.createElement('input');
2092
+ input.type = type;
2093
+ input.id = id;
2094
+ input.className = 'form-input';
2095
+ input.placeholder = placeholder;
2096
+ input.required = true;
2097
+ group.appendChild(input);
2098
+ return group;
2099
+ }
2100
+ createPasswordInput(id, placeholder) {
2101
+ const wrapper = document.createElement('div');
2102
+ wrapper.className = 'input-with-icon';
2103
+ const input = document.createElement('input');
2104
+ input.type = 'password';
2105
+ input.id = id;
2106
+ input.className = 'form-input';
2107
+ input.placeholder = placeholder;
2108
+ input.required = true;
2109
+ wrapper.appendChild(input);
2110
+ const toggleBtn = document.createElement('button');
2111
+ toggleBtn.type = 'button';
2112
+ toggleBtn.className = 'input-icon-btn toggle-password';
2113
+ toggleBtn.dataset.target = id;
2114
+ 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>';
2115
+ wrapper.appendChild(toggleBtn);
2116
+ return wrapper;
2117
+ }
2118
+ createSocialButton(provider, label, mode, fullWidth = false) {
2119
+ const button = document.createElement('button');
2120
+ button.type = 'button';
2121
+ button.className = fullWidth ? 'btn-social btn-social-full' : 'btn-social';
2122
+ button.id = `${provider}${mode === 'login' ? 'SignIn' : 'SignUp'}Modal`;
2123
+ // SVG icons for each provider
2124
+ const icons = {
2125
+ google: `<svg width="20" height="20" viewBox="0 0 24 24">
2126
+ <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"/>
2127
+ <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"/>
2128
+ <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"/>
2129
+ <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"/>
2130
+ </svg>`,
2131
+ github: `<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
2132
+ <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"/>
2133
+ </svg>`,
2134
+ discord: `<svg width="20" height="20" viewBox="0 0 24 24" fill="#5865F2">
2135
+ <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"/>
2136
+ </svg>`
2137
+ };
2138
+ button.innerHTML = icons[provider];
2139
+ const span = document.createElement('span');
2140
+ span.textContent = label;
2141
+ button.appendChild(span);
2142
+ return button;
2143
+ }
2144
+ bindEventListeners() {
2145
+ if (!this.modal)
2146
+ return;
2147
+ // Close button
2148
+ const closeBtn = this.modal.querySelector('.auth-modal-close');
2149
+ closeBtn?.addEventListener('click', () => this.hide());
2150
+ // Backdrop click to close
2151
+ const backdrop = this.modal.querySelector('.auth-modal-backdrop');
2152
+ backdrop?.addEventListener('click', () => this.hide());
2153
+ // View switching
2154
+ const showSignup = this.modal.querySelector('#showSignup');
2155
+ showSignup?.addEventListener('click', (e) => {
2156
+ e.preventDefault();
2157
+ this.switchView('signup');
2158
+ });
2159
+ const showLogin = this.modal.querySelector('#showLogin');
2160
+ showLogin?.addEventListener('click', (e) => {
2161
+ e.preventDefault();
2162
+ this.switchView('login');
2163
+ });
2164
+ const forgotPasswordLink = this.modal.querySelector('#forgotPasswordLink');
2165
+ forgotPasswordLink?.addEventListener('click', (e) => {
2166
+ e.preventDefault();
2167
+ this.switchView('forgot');
2168
+ });
2169
+ const backToLogin = this.modal.querySelector('#backToLogin');
2170
+ backToLogin?.addEventListener('click', (e) => {
2171
+ e.preventDefault();
2172
+ this.switchView('login');
2173
+ });
2174
+ // Password toggle
2175
+ const passwordToggles = this.modal.querySelectorAll('.toggle-password');
2176
+ passwordToggles.forEach((toggle) => {
2177
+ toggle.addEventListener('click', (e) => {
2178
+ e.preventDefault();
2179
+ const target = toggle.dataset.target;
2180
+ if (target) {
2181
+ const input = this.modal.querySelector(`#${target}`);
2182
+ const iconEye = toggle.querySelector('.icon-eye');
2183
+ const iconEyeOff = toggle.querySelector('.icon-eye-off');
2184
+ if (input.type === 'password') {
2185
+ input.type = 'text';
2186
+ iconEye?.classList.add('hidden');
2187
+ iconEyeOff?.classList.remove('hidden');
2188
+ }
2189
+ else {
2190
+ input.type = 'password';
2191
+ iconEye?.classList.remove('hidden');
2192
+ iconEyeOff?.classList.add('hidden');
2193
+ }
2194
+ }
2195
+ });
2196
+ });
2197
+ // Form submissions
2198
+ const loginForm = this.modal.querySelector('#loginFormElement');
2199
+ loginForm?.addEventListener('submit', (e) => this.handleLogin(e));
2200
+ const signupForm = this.modal.querySelector('#signupFormElement');
2201
+ signupForm?.addEventListener('submit', (e) => this.handleSignup(e));
2202
+ const forgotForm = this.modal.querySelector('#forgotPasswordFormElement');
2203
+ forgotForm?.addEventListener('submit', (e) => this.handleForgotPassword(e));
2204
+ // OAuth social login buttons
2205
+ this.bindSocialLoginButtons();
2206
+ }
2207
+ /**
2208
+ * Bind click events to social login buttons
2209
+ */
2210
+ bindSocialLoginButtons() {
2211
+ if (!this.modal)
2212
+ return;
2213
+ // Google login buttons
2214
+ const googleSignInBtn = this.modal.querySelector('#googleSignInModal');
2215
+ googleSignInBtn?.addEventListener('click', (e) => {
2216
+ e.preventDefault();
2217
+ this.startOAuthFlow('google');
2218
+ });
2219
+ const googleSignUpBtn = this.modal.querySelector('#googleSignUpModal');
2220
+ googleSignUpBtn?.addEventListener('click', (e) => {
2221
+ e.preventDefault();
2222
+ this.startOAuthFlow('google');
2223
+ });
2224
+ // Discord login buttons
2225
+ const discordSignInBtn = this.modal.querySelector('#discordSignInModal');
2226
+ discordSignInBtn?.addEventListener('click', (e) => {
2227
+ e.preventDefault();
2228
+ this.startOAuthFlow('discord');
2229
+ });
2230
+ const discordSignUpBtn = this.modal.querySelector('#discordSignUpModal');
2231
+ discordSignUpBtn?.addEventListener('click', (e) => {
2232
+ e.preventDefault();
2233
+ this.startOAuthFlow('discord');
2234
+ });
2235
+ // GitHub login buttons
2236
+ const githubSignInBtn = this.modal.querySelector('#githubSignInModal');
2237
+ githubSignInBtn?.addEventListener('click', (e) => {
2238
+ e.preventDefault();
2239
+ this.startOAuthFlow('github');
2240
+ });
2241
+ const githubSignUpBtn = this.modal.querySelector('#githubSignUpModal');
2242
+ githubSignUpBtn?.addEventListener('click', (e) => {
2243
+ e.preventDefault();
2244
+ this.startOAuthFlow('github');
2245
+ });
2246
+ }
2247
+ switchView(view) {
2248
+ if (!this.modal)
2249
+ return;
2250
+ const views = ['loginForm', 'signupForm', 'forgotPasswordForm', 'authMessage'];
2251
+ views.forEach((viewId) => {
2252
+ const element = this.modal.querySelector(`#${viewId}`);
2253
+ element?.classList.add('hidden');
2254
+ });
2255
+ const viewMap = {
2256
+ login: 'loginForm',
2257
+ signup: 'signupForm',
2258
+ forgot: 'forgotPasswordForm',
2259
+ message: 'authMessage',
2260
+ };
2261
+ const targetView = this.modal.querySelector(`#${viewMap[view]}`);
2262
+ targetView?.classList.remove('hidden');
2263
+ this.currentView = view;
2264
+ }
2265
+ async handleLogin(e) {
2266
+ e.preventDefault();
2267
+ const emailInput = this.modal?.querySelector('#loginEmail');
2268
+ const passwordInput = this.modal?.querySelector('#loginPassword');
2269
+ const submitBtn = this.modal?.querySelector('#loginButton');
2270
+ const btnText = submitBtn?.querySelector('.btn-text');
2271
+ const btnLoader = submitBtn?.querySelector('.btn-loader');
2272
+ if (!emailInput || !passwordInput || !submitBtn)
2273
+ return;
2274
+ const email = emailInput.value;
2275
+ const password = passwordInput.value;
2276
+ try {
2277
+ // Show loading state
2278
+ submitBtn.disabled = true;
2279
+ btnText?.classList.add('hidden');
2280
+ btnLoader?.classList.remove('hidden');
2281
+ // Call login API using new typed method
2282
+ const response = await this.client.login({
2283
+ email,
2284
+ password,
2285
+ });
2286
+ // Handle success
2287
+ if (response.token) {
2288
+ if (this.options.onLoginSuccess) {
2289
+ this.options.onLoginSuccess(response.token, response.user);
2290
+ }
2291
+ this.showMessage('Login Successful', 'Welcome back!');
2292
+ }
2293
+ else {
2294
+ throw new Error('Invalid response from server');
2295
+ }
2296
+ }
2297
+ catch (error) {
2298
+ // Handle error
2299
+ const errorMessage = error instanceof Error ? error.message : 'Login failed';
2300
+ this.showError(errorMessage);
2301
+ if (this.options.onError) {
2302
+ this.options.onError(error);
2303
+ }
2304
+ }
2305
+ finally {
2306
+ // Reset loading state
2307
+ submitBtn.disabled = false;
2308
+ btnText?.classList.remove('hidden');
2309
+ btnLoader?.classList.add('hidden');
2310
+ }
2311
+ }
2312
+ async handleSignup(e) {
2313
+ e.preventDefault();
2314
+ const emailInput = this.modal?.querySelector('#signupEmail');
2315
+ const passwordInput = this.modal?.querySelector('#signupPassword');
2316
+ const passwordConfirmInput = this.modal?.querySelector('#signupPasswordConfirm');
2317
+ const submitBtn = this.modal?.querySelector('#signupButton');
2318
+ const btnText = submitBtn?.querySelector('.btn-text');
2319
+ const btnLoader = submitBtn?.querySelector('.btn-loader');
2320
+ if (!emailInput || !passwordInput || !passwordConfirmInput || !submitBtn)
2321
+ return;
2322
+ const email = emailInput.value;
2323
+ const password = passwordInput.value;
2324
+ const passwordConfirm = passwordConfirmInput.value;
2325
+ // Validate passwords match
2326
+ if (password !== passwordConfirm) {
2327
+ this.showError('Passwords do not match');
2328
+ return;
2329
+ }
2330
+ try {
2331
+ // Show loading state
2332
+ submitBtn.disabled = true;
2333
+ btnText?.classList.add('hidden');
2334
+ btnLoader?.classList.remove('hidden');
2335
+ // Call signup API using new typed method
2336
+ const response = await this.client.register({
2337
+ email,
2338
+ password,
2339
+ });
2340
+ // Handle success - Note: register returns different response format
2341
+ if (response.success) {
2342
+ // After successful registration, perform login to get token
2343
+ const loginResponse = await this.client.login({ email, password });
2344
+ if (loginResponse.token) {
2345
+ if (this.options.onSignupSuccess) {
2346
+ this.options.onSignupSuccess(loginResponse.token, loginResponse.user);
2347
+ }
2348
+ this.showMessage('Account Created', response.message || 'Your account has been created successfully!');
2349
+ }
2350
+ }
2351
+ else {
2352
+ throw new Error('Registration failed');
2353
+ }
2354
+ }
2355
+ catch (error) {
2356
+ // Handle error
2357
+ const errorMessage = error instanceof Error ? error.message : 'Signup failed';
2358
+ this.showError(errorMessage);
2359
+ if (this.options.onError) {
2360
+ this.options.onError(error);
2361
+ }
2362
+ }
2363
+ finally {
2364
+ // Reset loading state
2365
+ submitBtn.disabled = false;
2366
+ btnText?.classList.remove('hidden');
2367
+ btnLoader?.classList.add('hidden');
2368
+ }
2369
+ }
2370
+ async handleForgotPassword(e) {
2371
+ e.preventDefault();
2372
+ const emailInput = this.modal?.querySelector('#resetEmail');
2373
+ const submitBtn = this.modal?.querySelector('#resetButton');
2374
+ if (!emailInput || !submitBtn)
2375
+ return;
2376
+ const email = emailInput.value;
2377
+ try {
2378
+ // Show loading state
2379
+ submitBtn.disabled = true;
2380
+ // TODO: Call forgot password API when available
2381
+ // await this.client.postapiauthforgotpassword({ email });
2382
+ // Show success message
2383
+ this.showMessage('Reset Link Sent', `We've sent a password reset link to ${email}`);
2384
+ }
2385
+ catch (error) {
2386
+ // Handle error
2387
+ const errorMessage = error instanceof Error ? error.message : 'Failed to send reset link';
2388
+ this.showError(errorMessage);
2389
+ if (this.options.onError) {
2390
+ this.options.onError(error);
2391
+ }
2392
+ }
2393
+ finally {
2394
+ // Reset loading state
2395
+ submitBtn.disabled = false;
2396
+ }
2397
+ }
2398
+ showMessage(title, message) {
2399
+ if (!this.modal)
2400
+ return;
2401
+ const messageTitle = this.modal.querySelector('#messageTitle');
2402
+ const messageText = this.modal.querySelector('#messageText');
2403
+ const messageButton = this.modal.querySelector('#messageButton');
2404
+ if (messageTitle)
2405
+ messageTitle.textContent = title;
2406
+ if (messageText)
2407
+ messageText.textContent = message;
2408
+ messageButton?.addEventListener('click', () => {
2409
+ this.hide();
2410
+ }, { once: true });
2411
+ this.switchView('message');
2412
+ }
2413
+ showError(message) {
2414
+ // Simple error display - you can enhance this
2415
+ alert(message);
2416
+ }
2417
+ // ============================================================================
2418
+ // OAuth Methods
2419
+ // ============================================================================
2420
+ /**
2421
+ * Start OAuth flow for a given provider
2422
+ */
2423
+ startOAuthFlow(provider) {
2424
+ const config = this.options.oauthConfig?.[provider];
2425
+ if (!config) {
2426
+ this.showError(`${provider} OAuth is not configured`);
2427
+ return;
2428
+ }
2429
+ // Store the current state (to verify callback)
2430
+ const state = this.generateRandomState();
2431
+ sessionStorage.setItem('oauth_state', state);
2432
+ sessionStorage.setItem('oauth_provider', provider);
2433
+ // Build authorization URL
2434
+ const authUrl = this.buildAuthUrl(provider, config, state);
2435
+ // Redirect to OAuth provider
2436
+ window.location.href = authUrl;
2437
+ }
2438
+ /**
2439
+ * Generate random state for CSRF protection
2440
+ */
2441
+ generateRandomState() {
2442
+ const array = new Uint8Array(32);
2443
+ crypto.getRandomValues(array);
2444
+ return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
2445
+ }
2446
+ /**
2447
+ * Build authorization URL for each provider
2448
+ */
2449
+ buildAuthUrl(provider, config, state) {
2450
+ const params = new URLSearchParams({
2451
+ client_id: config.clientId,
2452
+ redirect_uri: config.redirectUri,
2453
+ state,
2454
+ response_type: 'code',
2455
+ });
2456
+ // Provider-specific configurations
2457
+ switch (provider) {
2458
+ case 'google':
2459
+ params.append('scope', config.scope || 'openid email profile');
2460
+ params.append('access_type', 'offline');
2461
+ params.append('prompt', 'consent');
2462
+ return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
2463
+ case 'discord':
2464
+ params.append('scope', config.scope || 'identify email');
2465
+ return `https://discord.com/api/oauth2/authorize?${params.toString()}`;
2466
+ case 'github':
2467
+ params.append('scope', config.scope || 'read:user user:email');
2468
+ return `https://github.com/login/oauth/authorize?${params.toString()}`;
2469
+ default:
2470
+ throw new Error(`Unknown provider: ${provider}`);
2471
+ }
2472
+ }
2473
+ /**
2474
+ * Handle OAuth callback
2475
+ * Call this method from your redirect page with the code and state from URL
2476
+ */
2477
+ async handleOAuthCallback(code, state) {
2478
+ try {
2479
+ // Verify state
2480
+ const storedState = sessionStorage.getItem('oauth_state');
2481
+ const provider = sessionStorage.getItem('oauth_provider');
2482
+ if (!storedState || storedState !== state) {
2483
+ throw new Error('Invalid state parameter - possible CSRF attack');
2484
+ }
2485
+ if (!provider) {
2486
+ throw new Error('No provider stored in session');
2487
+ }
2488
+ // Clear stored state
2489
+ sessionStorage.removeItem('oauth_state');
2490
+ sessionStorage.removeItem('oauth_provider');
2491
+ // Exchange code for token using the appropriate SDK method
2492
+ let response;
2493
+ switch (provider) {
2494
+ case 'google':
2495
+ response = await this.client.googleCodeToToken({ code });
2496
+ break;
2497
+ case 'discord':
2498
+ response = await this.client.discordCodeToToken({ code });
2499
+ break;
2500
+ case 'github':
2501
+ response = await this.client.githubCodeToToken({ code });
2502
+ break;
2503
+ default:
2504
+ throw new Error(`Unknown provider: ${provider}`);
2505
+ }
2506
+ // Handle success
2507
+ if (response.token) {
2508
+ if (this.options.onLoginSuccess) {
2509
+ this.options.onLoginSuccess(response.token, response.user);
2510
+ }
2511
+ return {
2512
+ success: true,
2513
+ token: response.token,
2514
+ user: response.user,
2515
+ };
2516
+ }
2517
+ else {
2518
+ throw new Error('No token received from server');
2519
+ }
2520
+ }
2521
+ catch (error) {
2522
+ const err = error instanceof Error ? error : new Error('OAuth authentication failed');
2523
+ if (this.options.onError) {
2524
+ this.options.onError(err);
2525
+ }
2526
+ return {
2527
+ success: false,
2528
+ error: err,
2529
+ };
2530
+ }
2531
+ }
2532
+ /**
2533
+ * Check if current page is an OAuth callback and handle it automatically
2534
+ */
2535
+ static async handleOAuthCallbackFromUrl(client, options) {
2536
+ const urlParams = new URLSearchParams(window.location.search);
2537
+ const code = urlParams.get('code');
2538
+ const state = urlParams.get('state');
2539
+ if (!code || !state) {
2540
+ return null; // Not an OAuth callback
2541
+ }
2542
+ const modal = new AuthModal({
2543
+ client,
2544
+ ...options,
2545
+ });
2546
+ return await modal.handleOAuthCallback(code, state);
2547
+ }
2548
+ }
2549
+ /**
2550
+ * Create and show auth modal
2551
+ */
2552
+ function createAuthModal(options) {
2553
+ return new AuthModal(options);
2554
+ }
2555
+
2556
+ exports.AuthFactory = AuthFactory;
2557
+ exports.AuthModal = AuthModal;
2558
+ exports.AuthProvider = AuthProvider;
2559
+ exports.BuiltInHooks = BuiltInHooks;
2560
+ exports.ENVIRONMENT_CONFIGS = ENVIRONMENT_CONFIGS;
2561
+ exports.SeaVerseBackendAPIClient = SeaVerseBackendAPIClient;
2562
+ exports.createAuthModal = createAuthModal;
2563
+ exports.detectEnvironment = detectEnvironment;
2564
+ exports.getEnvironmentConfig = getEnvironmentConfig;
2565
+ exports.models = models;
2566
+ //# sourceMappingURL=index.cjs.map