@seaverse/auth-sdk 0.4.0 → 0.4.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/README.md +674 -674
- package/dist/auth-modal.css +1 -1
- package/dist/index.cjs +44 -143
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +15 -2
- package/dist/index.js +44 -122
- package/dist/index.js.map +1 -1
- package/dist/toast.css +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,129 +1,129 @@
|
|
|
1
1
|
# @seaverse/auth-sdk
|
|
2
2
|
|
|
3
|
-
SeaVerse Backend API
|
|
3
|
+
SeaVerse Backend API Client SDK - Provides complete authentication, container management, skill marketplace, and more
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@seaverse/auth-sdk)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Features
|
|
9
9
|
|
|
10
|
-
- 🔐
|
|
11
|
-
- 🌐 **OAuth
|
|
12
|
-
- 🎨
|
|
13
|
-
- ✨ **Toast
|
|
14
|
-
- 🌍
|
|
15
|
-
- ⚡ **TypeScript** -
|
|
16
|
-
- 🔧
|
|
17
|
-
- 🔄
|
|
10
|
+
- 🔐 **User Authentication** - Registration, login, logout, password reset
|
|
11
|
+
- 🌐 **OAuth Login** - Google, Discord, GitHub third-party login
|
|
12
|
+
- 🎨 **Login UI Components** - Out-of-the-box beautiful login modal
|
|
13
|
+
- ✨ **Toast Notifications** - Modern glassmorphism alerts with auto-injected CSS
|
|
14
|
+
- 🌍 **Multi-Environment Support** - Production, Staging, Development, Local
|
|
15
|
+
- ⚡ **TypeScript** - Full type definitions
|
|
16
|
+
- 🔧 **Auth Integration** - Built-in Auth and Hooks system support
|
|
17
|
+
- 🔄 **Response Format Compatibility** - Automatic compatibility with multiple API response formats
|
|
18
18
|
|
|
19
|
-
##
|
|
19
|
+
## Installation
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
22
|
npm install @seaverse/auth-sdk
|
|
23
|
-
#
|
|
23
|
+
# or
|
|
24
24
|
pnpm add @seaverse/auth-sdk
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
##
|
|
27
|
+
## Core Concepts
|
|
28
28
|
|
|
29
|
-
### App ID
|
|
29
|
+
### App ID and Multi-Tenant Architecture
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
**Important**: Starting from v0.2.0, `appId` is a **required parameter** when initializing the SDK.
|
|
32
32
|
|
|
33
|
-
####
|
|
33
|
+
#### What is App ID?
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
Each application has a unique `app_id`:
|
|
36
36
|
- `app_id = "your app id"`
|
|
37
37
|
|
|
38
|
-
####
|
|
38
|
+
#### Multi-Tenant Isolation
|
|
39
39
|
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
40
|
+
- User data is isolated by `app_id`
|
|
41
|
+
- The same email can be registered in different applications with different passwords
|
|
42
|
+
- Each application has its own independent user pool
|
|
43
43
|
|
|
44
|
-
#### X-App-ID
|
|
44
|
+
#### X-App-ID Request Header
|
|
45
45
|
|
|
46
|
-
SDK
|
|
46
|
+
The SDK automatically adds `X-App-ID` to **every request** header, no manual configuration needed:
|
|
47
47
|
|
|
48
48
|
```typescript
|
|
49
49
|
const client = new SeaVerseBackendAPIClient({
|
|
50
|
-
appId: 'game-abc123', // SDK
|
|
50
|
+
appId: 'game-abc123', // SDK automatically adds this value to all requests' X-App-ID header
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
-
//
|
|
53
|
+
// All API calls will automatically carry X-App-ID: game-abc123
|
|
54
54
|
await client.login({ email, password });
|
|
55
55
|
await client.getCurrentUser();
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
##
|
|
58
|
+
## Quick Start
|
|
59
59
|
|
|
60
|
-
### 1.
|
|
60
|
+
### 1. Basic Usage
|
|
61
61
|
|
|
62
62
|
```typescript
|
|
63
63
|
import { SeaVerseBackendAPIClient } from '@seaverse/auth-sdk';
|
|
64
64
|
|
|
65
|
-
//
|
|
65
|
+
// Method 1: SeaVerse platform application (auto-detect environment)
|
|
66
66
|
const client = new SeaVerseBackendAPIClient({
|
|
67
|
-
appId: 'your app id', //
|
|
67
|
+
appId: 'your app id', // Required: Application ID
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
-
//
|
|
70
|
+
// Method 2: Third-party application (specify environment)
|
|
71
71
|
const client = new SeaVerseBackendAPIClient({
|
|
72
|
-
appId: 'your app id', //
|
|
72
|
+
appId: 'your app id', // Required: Your application ID
|
|
73
73
|
environment: 'production', // 'production' | 'staging' | 'development' | 'local'
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
-
//
|
|
76
|
+
// Method 3: Custom URL
|
|
77
77
|
const client = new SeaVerseBackendAPIClient({
|
|
78
|
-
appId: 'your app id', //
|
|
78
|
+
appId: 'your app id', // Required: Your application ID
|
|
79
79
|
baseURL: 'https://custom-api.example.com',
|
|
80
80
|
});
|
|
81
81
|
|
|
82
|
-
//
|
|
82
|
+
// Method 4: Enable request retry (disabled by default)
|
|
83
83
|
const client = new SeaVerseBackendAPIClient({
|
|
84
84
|
appId: 'your app id',
|
|
85
85
|
retryOptions: {
|
|
86
|
-
maxRetries: 3, //
|
|
87
|
-
retryDelay: 1000, //
|
|
88
|
-
retryStatusCodes: [408, 429, 500, 502, 503, 504], //
|
|
86
|
+
maxRetries: 3, // Retry up to 3 times
|
|
87
|
+
retryDelay: 1000, // Initial delay 1000ms, doubles each retry
|
|
88
|
+
retryStatusCodes: [408, 429, 500, 502, 503, 504], // Retry on these status codes
|
|
89
89
|
},
|
|
90
90
|
});
|
|
91
91
|
|
|
92
|
-
//
|
|
92
|
+
// Health check
|
|
93
93
|
const health = await client.getHealth();
|
|
94
94
|
console.log('Health:', health);
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
### 2.
|
|
97
|
+
### 2. Token Retrieval in Iframe Scenarios
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
When your application is embedded in an iframe, you can use the `getIframeToken()` method to retrieve the authentication token from the parent page. This is particularly useful for third-party login redirect scenarios.
|
|
100
100
|
|
|
101
|
-
####
|
|
101
|
+
#### Check if Running in Iframe
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
Before calling iframe-related methods, it's recommended to check if the application is running in an iframe:
|
|
104
104
|
|
|
105
105
|
```typescript
|
|
106
106
|
import { SeaVerseBackendAPIClient } from '@seaverse/auth-sdk';
|
|
107
107
|
|
|
108
|
-
//
|
|
108
|
+
// Method 1: Use static method (no client instance needed)
|
|
109
109
|
if (SeaVerseBackendAPIClient.isInIframe()) {
|
|
110
|
-
console.log('
|
|
110
|
+
console.log('Application is running in iframe');
|
|
111
111
|
} else {
|
|
112
|
-
console.log('
|
|
112
|
+
console.log('Application is not running in iframe');
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
//
|
|
115
|
+
// Method 2: Use instance method
|
|
116
116
|
const client = new SeaVerseBackendAPIClient({
|
|
117
117
|
appId: 'your app id',
|
|
118
118
|
});
|
|
119
119
|
|
|
120
120
|
if (client.isInIframe()) {
|
|
121
|
-
console.log('
|
|
122
|
-
//
|
|
121
|
+
console.log('Application is running in iframe');
|
|
122
|
+
// Safe to call getIframeToken()
|
|
123
123
|
}
|
|
124
124
|
```
|
|
125
125
|
|
|
126
|
-
####
|
|
126
|
+
#### Child Page (Inside Iframe) Usage
|
|
127
127
|
|
|
128
128
|
```typescript
|
|
129
129
|
import { SeaVerseBackendAPIClient } from '@seaverse/auth-sdk';
|
|
@@ -132,46 +132,46 @@ const client = new SeaVerseBackendAPIClient({
|
|
|
132
132
|
appId: 'your app id',
|
|
133
133
|
});
|
|
134
134
|
|
|
135
|
-
//
|
|
135
|
+
// First check if running in iframe
|
|
136
136
|
if (!client.isInIframe()) {
|
|
137
|
-
console.log('
|
|
138
|
-
//
|
|
137
|
+
console.log('Not in iframe, skip token retrieval');
|
|
138
|
+
// Can use other login methods
|
|
139
139
|
} else {
|
|
140
|
-
//
|
|
140
|
+
// Request token from parent page while in iframe
|
|
141
141
|
try {
|
|
142
142
|
const token = await client.getIframeToken({
|
|
143
|
-
timeout: 10000 //
|
|
143
|
+
timeout: 10000 // Optional, default 30 seconds
|
|
144
144
|
});
|
|
145
145
|
|
|
146
|
-
//
|
|
146
|
+
// Set token to client
|
|
147
147
|
client.setToken(token);
|
|
148
148
|
localStorage.setItem('token', token);
|
|
149
149
|
|
|
150
|
-
console.log('
|
|
150
|
+
console.log('Successfully retrieved token from parent page');
|
|
151
151
|
|
|
152
|
-
//
|
|
152
|
+
// Now can use authenticated APIs
|
|
153
153
|
const user = await client.getCurrentUser();
|
|
154
|
-
console.log('
|
|
154
|
+
console.log('Current user:', user);
|
|
155
155
|
} catch (error) {
|
|
156
|
-
console.error('
|
|
156
|
+
console.error('Failed to retrieve token:', error);
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
159
|
```
|
|
160
160
|
|
|
161
|
-
####
|
|
161
|
+
#### Parent Page Listens and Responds to Token Requests
|
|
162
162
|
|
|
163
163
|
```typescript
|
|
164
|
-
//
|
|
164
|
+
// Parent page needs to listen for token requests from iframe
|
|
165
165
|
window.addEventListener('message', (event) => {
|
|
166
|
-
//
|
|
166
|
+
// Check message type
|
|
167
167
|
if (event.data.type === 'seaverse:get_token') {
|
|
168
168
|
const requestId = event.data.requestId;
|
|
169
169
|
|
|
170
|
-
//
|
|
170
|
+
// Get your token (from localStorage, API, etc)
|
|
171
171
|
const token = localStorage.getItem('token');
|
|
172
172
|
|
|
173
173
|
if (token) {
|
|
174
|
-
//
|
|
174
|
+
// Send token to iframe
|
|
175
175
|
event.source.postMessage({
|
|
176
176
|
type: 'seaverse:token',
|
|
177
177
|
requestId: requestId,
|
|
@@ -181,7 +181,7 @@ window.addEventListener('message', (event) => {
|
|
|
181
181
|
},
|
|
182
182
|
}, '*');
|
|
183
183
|
} else {
|
|
184
|
-
//
|
|
184
|
+
// Send error message
|
|
185
185
|
event.source.postMessage({
|
|
186
186
|
type: 'seaverse:token_error',
|
|
187
187
|
requestId: requestId,
|
|
@@ -192,10 +192,10 @@ window.addEventListener('message', (event) => {
|
|
|
192
192
|
});
|
|
193
193
|
```
|
|
194
194
|
|
|
195
|
-
####
|
|
195
|
+
#### Complete Iframe Login Flow Example
|
|
196
196
|
|
|
197
197
|
```typescript
|
|
198
|
-
//
|
|
198
|
+
// Child page (iframe)
|
|
199
199
|
import { SeaVerseBackendAPIClient } from '@seaverse/auth-sdk';
|
|
200
200
|
|
|
201
201
|
const client = new SeaVerseBackendAPIClient({
|
|
@@ -203,28 +203,28 @@ const client = new SeaVerseBackendAPIClient({
|
|
|
203
203
|
});
|
|
204
204
|
|
|
205
205
|
async function initIframeApp() {
|
|
206
|
-
// 0.
|
|
206
|
+
// 0. Check if running in iframe
|
|
207
207
|
if (!client.isInIframe()) {
|
|
208
|
-
console.log('
|
|
209
|
-
//
|
|
208
|
+
console.log('Not in iframe, use regular login flow');
|
|
209
|
+
// Use regular login flow
|
|
210
210
|
showLoginForm();
|
|
211
211
|
return;
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
try {
|
|
215
|
-
// 1.
|
|
215
|
+
// 1. Try to get token from parent page
|
|
216
216
|
const token = await client.getIframeToken();
|
|
217
217
|
client.setToken(token);
|
|
218
218
|
localStorage.setItem('token', token);
|
|
219
219
|
|
|
220
|
-
// 2.
|
|
220
|
+
// 2. Validate token and get user info
|
|
221
221
|
const user = await client.getCurrentUser();
|
|
222
|
-
console.log('
|
|
222
|
+
console.log('Logged in:', user);
|
|
223
223
|
|
|
224
|
-
// 3.
|
|
224
|
+
// 3. Continue app logic
|
|
225
225
|
startApp(user);
|
|
226
226
|
} catch (error) {
|
|
227
|
-
// 4.
|
|
227
|
+
// 4. If failed, notify parent page that login is needed
|
|
228
228
|
window.parent.postMessage({
|
|
229
229
|
type: 'seaverse:need_login',
|
|
230
230
|
}, '*');
|
|
@@ -235,9 +235,9 @@ initIframeApp();
|
|
|
235
235
|
```
|
|
236
236
|
|
|
237
237
|
```typescript
|
|
238
|
-
//
|
|
238
|
+
// Parent page
|
|
239
239
|
window.addEventListener('message', (event) => {
|
|
240
|
-
//
|
|
240
|
+
// Handle token request
|
|
241
241
|
if (event.data.type === 'seaverse:get_token') {
|
|
242
242
|
const requestId = event.data.requestId;
|
|
243
243
|
const token = localStorage.getItem('token');
|
|
@@ -252,23 +252,23 @@ window.addEventListener('message', (event) => {
|
|
|
252
252
|
},
|
|
253
253
|
}, '*');
|
|
254
254
|
} else {
|
|
255
|
-
//
|
|
255
|
+
// If no token, trigger login
|
|
256
256
|
showLoginModal();
|
|
257
257
|
}
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
-
//
|
|
260
|
+
// Handle need login message
|
|
261
261
|
if (event.data.type === 'seaverse:need_login') {
|
|
262
262
|
showLoginModal();
|
|
263
263
|
}
|
|
264
264
|
});
|
|
265
265
|
|
|
266
|
-
//
|
|
266
|
+
// After successful login, notify iframe
|
|
267
267
|
function onLoginSuccess(newToken) {
|
|
268
268
|
const iframe = document.querySelector('iframe');
|
|
269
269
|
iframe.contentWindow.postMessage({
|
|
270
270
|
type: 'seaverse:token',
|
|
271
|
-
requestId: 'auto', //
|
|
271
|
+
requestId: 'auto', // Can use fixed ID for auto-login
|
|
272
272
|
payload: {
|
|
273
273
|
accessToken: newToken,
|
|
274
274
|
expiresIn: 3600,
|
|
@@ -277,9 +277,9 @@ function onLoginSuccess(newToken) {
|
|
|
277
277
|
}
|
|
278
278
|
```
|
|
279
279
|
|
|
280
|
-
#### TypeScript
|
|
280
|
+
#### TypeScript Type Definitions
|
|
281
281
|
|
|
282
|
-
SDK
|
|
282
|
+
The SDK provides complete TypeScript type support:
|
|
283
283
|
|
|
284
284
|
```typescript
|
|
285
285
|
import type {
|
|
@@ -288,13 +288,13 @@ import type {
|
|
|
288
288
|
IframeTokenErrorMessage,
|
|
289
289
|
} from '@seaverse/auth-sdk';
|
|
290
290
|
|
|
291
|
-
// Token
|
|
291
|
+
// Token request message
|
|
292
292
|
const request: IframeTokenRequestMessage = {
|
|
293
293
|
type: 'seaverse:get_token',
|
|
294
294
|
requestId: 'unique-id',
|
|
295
295
|
};
|
|
296
296
|
|
|
297
|
-
// Token
|
|
297
|
+
// Token response message
|
|
298
298
|
const response: IframeTokenResponseMessage = {
|
|
299
299
|
type: 'seaverse:token',
|
|
300
300
|
requestId: 'unique-id',
|
|
@@ -304,7 +304,7 @@ const response: IframeTokenResponseMessage = {
|
|
|
304
304
|
},
|
|
305
305
|
};
|
|
306
306
|
|
|
307
|
-
//
|
|
307
|
+
// Error message
|
|
308
308
|
const error: IframeTokenErrorMessage = {
|
|
309
309
|
type: 'seaverse:token_error',
|
|
310
310
|
requestId: 'unique-id',
|
|
@@ -312,328 +312,328 @@ const error: IframeTokenErrorMessage = {
|
|
|
312
312
|
};
|
|
313
313
|
```
|
|
314
314
|
|
|
315
|
-
####
|
|
315
|
+
#### Security Considerations
|
|
316
316
|
|
|
317
|
-
1.
|
|
317
|
+
1. **Use specific origin**: In production, use specific origin instead of `'*'`:
|
|
318
318
|
```typescript
|
|
319
|
-
//
|
|
319
|
+
// Parent page
|
|
320
320
|
const ALLOWED_ORIGIN = 'https://your-iframe-domain.com';
|
|
321
321
|
event.source.postMessage(message, ALLOWED_ORIGIN);
|
|
322
322
|
|
|
323
|
-
//
|
|
323
|
+
// Child page can also specify when sending
|
|
324
324
|
window.parent.postMessage(message, 'https://parent-domain.com');
|
|
325
325
|
```
|
|
326
326
|
|
|
327
|
-
2.
|
|
327
|
+
2. **Verify message origin**: Validate origin when handling postMessage:
|
|
328
328
|
```typescript
|
|
329
329
|
window.addEventListener('message', (event) => {
|
|
330
|
-
//
|
|
330
|
+
// Verify origin
|
|
331
331
|
if (event.origin !== 'https://trusted-domain.com') {
|
|
332
332
|
return;
|
|
333
333
|
}
|
|
334
|
-
//
|
|
334
|
+
// Handle message...
|
|
335
335
|
});
|
|
336
336
|
```
|
|
337
337
|
|
|
338
|
-
3. **
|
|
338
|
+
3. **Handle token expiration**: Remember to handle token expiration:
|
|
339
339
|
```typescript
|
|
340
340
|
try {
|
|
341
341
|
const user = await client.getCurrentUser();
|
|
342
342
|
} catch (error) {
|
|
343
343
|
if (error.response?.status === 401) {
|
|
344
|
-
// Token
|
|
344
|
+
// Token expired, re-retrieve
|
|
345
345
|
const newToken = await client.getIframeToken();
|
|
346
346
|
client.setToken(newToken);
|
|
347
347
|
}
|
|
348
348
|
}
|
|
349
349
|
```
|
|
350
350
|
|
|
351
|
-
### 3.
|
|
351
|
+
### 3. User Authentication
|
|
352
352
|
|
|
353
353
|
```typescript
|
|
354
|
-
//
|
|
354
|
+
// Register new user
|
|
355
355
|
const registerResult = await client.register({
|
|
356
356
|
email: 'user@example.com',
|
|
357
357
|
password: 'SecurePassword123',
|
|
358
|
-
username: 'johndoe', //
|
|
359
|
-
invitation_code: 'INVITE123', //
|
|
360
|
-
frontend_url: 'https://mygame.com/verify', //
|
|
358
|
+
username: 'johndoe', // Optional, auto-generated from email if not provided
|
|
359
|
+
invitation_code: 'INVITE123', // Optional
|
|
360
|
+
frontend_url: 'https://mygame.com/verify', // Optional, frontend URL for email verification link, defaults to window.location.href
|
|
361
361
|
});
|
|
362
362
|
|
|
363
|
-
//
|
|
363
|
+
// Check registration result
|
|
364
364
|
if (registerResult.success) {
|
|
365
|
-
console.log('
|
|
365
|
+
console.log('Registration successful:', registerResult);
|
|
366
366
|
} else if (registerResult.code === 'ACCOUNT_EXISTS') {
|
|
367
|
-
console.log('
|
|
368
|
-
console.log('
|
|
367
|
+
console.log('Account exists, please login directly');
|
|
368
|
+
console.log('Error details:', registerResult.details);
|
|
369
369
|
}
|
|
370
370
|
|
|
371
|
-
//
|
|
371
|
+
// Login
|
|
372
372
|
const loginResult = await client.login({
|
|
373
373
|
email: 'user@example.com',
|
|
374
374
|
password: 'SecurePassword123',
|
|
375
375
|
});
|
|
376
376
|
|
|
377
|
-
//
|
|
377
|
+
// Save token
|
|
378
378
|
localStorage.setItem('token', loginResult.token);
|
|
379
379
|
|
|
380
|
-
// ⚠️
|
|
381
|
-
//
|
|
382
|
-
// SDK
|
|
383
|
-
//
|
|
380
|
+
// ⚠️ Note: Automatic invitation code redirect
|
|
381
|
+
// If server returns INVITE_CODE_REQUIRED error and includes redirectUrl,
|
|
382
|
+
// SDK will automatically redirect user to that URL (usually invitation code input page)
|
|
383
|
+
// No extra handling needed, page will redirect automatically
|
|
384
384
|
|
|
385
|
-
//
|
|
385
|
+
// Get current user info
|
|
386
386
|
const user = await client.getCurrentUser();
|
|
387
387
|
console.log('User:', user);
|
|
388
|
-
console.log('App ID:', user.app_id); //
|
|
388
|
+
console.log('App ID:', user.app_id); // Multi-tenant application ID
|
|
389
389
|
console.log('Email verified:', user.email_verified);
|
|
390
390
|
|
|
391
|
-
//
|
|
391
|
+
// Logout
|
|
392
392
|
await client.logout();
|
|
393
393
|
|
|
394
|
-
//
|
|
394
|
+
// Forgot password
|
|
395
395
|
await client.forgotPassword({
|
|
396
396
|
email: 'user@example.com',
|
|
397
|
-
frontend_url: 'https://mygame.com/', //
|
|
397
|
+
frontend_url: 'https://mygame.com/', // Optional, defaults to window.location.href
|
|
398
398
|
});
|
|
399
399
|
|
|
400
|
-
//
|
|
400
|
+
// Reset password
|
|
401
401
|
await client.resetPassword({
|
|
402
402
|
token: 'reset-token-from-email',
|
|
403
403
|
new_password: 'NewSecurePassword123',
|
|
404
404
|
});
|
|
405
405
|
```
|
|
406
406
|
|
|
407
|
-
### 4. OAuth
|
|
407
|
+
### 4. OAuth Third-Party Login (Backend Proxy Mode)
|
|
408
408
|
|
|
409
|
-
SDK
|
|
409
|
+
The SDK uses Backend Proxy Mode where Client Secret is never exposed to the frontend, providing higher security.
|
|
410
410
|
|
|
411
|
-
|
|
412
|
-
- ✅ Client Secret
|
|
413
|
-
- ✅
|
|
414
|
-
- ✅
|
|
415
|
-
- ✅
|
|
411
|
+
**Advantages**:
|
|
412
|
+
- ✅ Client Secret is never exposed to frontend
|
|
413
|
+
- ✅ Supports any developer domain (no need to configure in OAuth platform)
|
|
414
|
+
- ✅ Built-in CSRF protection
|
|
415
|
+
- ✅ Zero configuration, works out of the box
|
|
416
416
|
|
|
417
|
-
|
|
418
|
-
1.
|
|
419
|
-
2.
|
|
420
|
-
3.
|
|
421
|
-
4. account-hub
|
|
422
|
-
5. account-hub 302
|
|
423
|
-
6.
|
|
417
|
+
**Workflow**:
|
|
418
|
+
1. Frontend calls `{provider}Authorize()` to get OAuth URL
|
|
419
|
+
2. Frontend redirects user to OAuth provider
|
|
420
|
+
3. After user authorization, OAuth provider callbacks to fixed account-hub URL
|
|
421
|
+
4. account-hub handles OAuth, creates JWT token
|
|
422
|
+
5. account-hub 302 redirects to `return_url?token=xxx`
|
|
423
|
+
6. Frontend extracts token from URL and stores it
|
|
424
424
|
|
|
425
|
-
####
|
|
425
|
+
#### Usage Examples
|
|
426
426
|
|
|
427
|
-
|
|
427
|
+
**Method 1: Use default return_url (current page)**
|
|
428
428
|
|
|
429
429
|
```typescript
|
|
430
|
-
// Google
|
|
430
|
+
// Google login
|
|
431
431
|
const { authorize_url } = await client.googleAuthorize();
|
|
432
432
|
window.location.href = authorize_url;
|
|
433
433
|
|
|
434
|
-
// Discord
|
|
434
|
+
// Discord login
|
|
435
435
|
const { authorize_url } = await client.discordAuthorize();
|
|
436
436
|
window.location.href = authorize_url;
|
|
437
437
|
|
|
438
|
-
// GitHub
|
|
438
|
+
// GitHub login
|
|
439
439
|
const { authorize_url } = await client.githubAuthorize();
|
|
440
440
|
window.location.href = authorize_url;
|
|
441
441
|
```
|
|
442
442
|
|
|
443
|
-
|
|
443
|
+
**Method 2: Custom return_url**
|
|
444
444
|
|
|
445
445
|
```typescript
|
|
446
|
-
//
|
|
446
|
+
// Redirect to dashboard after login
|
|
447
447
|
const { authorize_url } = await client.googleAuthorize({
|
|
448
448
|
return_url: 'https://mygame.com/dashboard'
|
|
449
449
|
});
|
|
450
450
|
window.location.href = authorize_url;
|
|
451
451
|
```
|
|
452
452
|
|
|
453
|
-
|
|
453
|
+
**Extract token in callback page**:
|
|
454
454
|
|
|
455
455
|
```typescript
|
|
456
456
|
// URL: https://mygame.com/?token=eyJhbGc...
|
|
457
457
|
const token = new URLSearchParams(window.location.search).get('token');
|
|
458
458
|
if (token) {
|
|
459
459
|
localStorage.setItem('token', token);
|
|
460
|
-
//
|
|
460
|
+
// Login successful, redirect or update UI
|
|
461
461
|
}
|
|
462
462
|
```
|
|
463
463
|
|
|
464
|
-
#### OAuth
|
|
464
|
+
#### OAuth Account Unlinking
|
|
465
465
|
|
|
466
466
|
```typescript
|
|
467
|
-
//
|
|
467
|
+
// Unlink Google account
|
|
468
468
|
await client.unlinkGoogle();
|
|
469
469
|
|
|
470
|
-
//
|
|
470
|
+
// Unlink Discord account
|
|
471
471
|
await client.unlinkDiscord();
|
|
472
472
|
|
|
473
|
-
//
|
|
473
|
+
// Unlink GitHub account
|
|
474
474
|
await client.unlinkGithub();
|
|
475
475
|
```
|
|
476
476
|
|
|
477
|
-
### 5.
|
|
477
|
+
### 5. Using Login UI Components
|
|
478
478
|
|
|
479
|
-
SDK
|
|
479
|
+
The SDK provides beautiful login modal components with dark and light themes:
|
|
480
480
|
|
|
481
|
-
####
|
|
481
|
+
#### Theme Comparison
|
|
482
482
|
|
|
483
|
-
|
|
|
483
|
+
| Theme | Design Style | Use Cases |
|
|
484
484
|
|------|---------|---------|
|
|
485
|
-
| **Dark** 🌙 |
|
|
486
|
-
| **Light** ☀️ |
|
|
485
|
+
| **Dark** 🌙 | Dynamic grid gradient + glassmorphism | Tech products, gaming platforms, dark interface apps |
|
|
486
|
+
| **Light** ☀️ | Elevated card design + soft shadows | Business apps, content platforms, light interface apps |
|
|
487
487
|
|
|
488
|
-
####
|
|
488
|
+
#### Basic Usage
|
|
489
489
|
|
|
490
490
|
```typescript
|
|
491
491
|
import { SeaVerseBackendAPIClient, AuthModal } from '@seaverse/auth-sdk';
|
|
492
|
-
import '@seaverse/auth-sdk/auth-modal.css'; //
|
|
492
|
+
import '@seaverse/auth-sdk/auth-modal.css'; // Import styles
|
|
493
493
|
|
|
494
494
|
const client = new SeaVerseBackendAPIClient({
|
|
495
|
-
appId: 'your app id', //
|
|
495
|
+
appId: 'your app id', // Required: Your application ID
|
|
496
496
|
environment: 'production',
|
|
497
497
|
});
|
|
498
498
|
|
|
499
|
-
//
|
|
499
|
+
// Create login modal
|
|
500
500
|
const authModal = new AuthModal({
|
|
501
501
|
client,
|
|
502
|
-
theme: 'dark', // 'dark' | 'light' -
|
|
502
|
+
theme: 'dark', // 'dark' | 'light' - defaults to 'dark'
|
|
503
503
|
|
|
504
|
-
//
|
|
504
|
+
// Login success callback
|
|
505
505
|
onLoginSuccess: (token, user) => {
|
|
506
506
|
localStorage.setItem('token', token);
|
|
507
|
-
console.log('
|
|
507
|
+
console.log('Login successful:', user);
|
|
508
508
|
},
|
|
509
509
|
|
|
510
|
-
//
|
|
510
|
+
// Signup success callback
|
|
511
511
|
onSignupSuccess: (token, user) => {
|
|
512
512
|
localStorage.setItem('token', token);
|
|
513
|
-
console.log('
|
|
513
|
+
console.log('Signup successful:', user);
|
|
514
514
|
},
|
|
515
515
|
|
|
516
|
-
//
|
|
516
|
+
// Invitation code required callback (optional)
|
|
517
517
|
onInviteCodeRequired: (userId, email) => {
|
|
518
|
-
console.log('
|
|
519
|
-
// AuthModal
|
|
518
|
+
console.log('Need invitation code to activate account:', userId, email);
|
|
519
|
+
// AuthModal will automatically show invitation code input interface
|
|
520
520
|
},
|
|
521
521
|
|
|
522
|
-
//
|
|
522
|
+
// Apply invite success callback (optional)
|
|
523
523
|
onApplyInviteSuccess: (applicationId, email) => {
|
|
524
|
-
console.log('
|
|
525
|
-
//
|
|
524
|
+
console.log('Invitation code application submitted:', applicationId, email);
|
|
525
|
+
// Can add custom logic here, such as showing notifications
|
|
526
526
|
},
|
|
527
527
|
|
|
528
|
-
//
|
|
528
|
+
// Error callback
|
|
529
529
|
onError: (error) => {
|
|
530
|
-
console.error('
|
|
530
|
+
console.error('Authentication error:', error.message);
|
|
531
531
|
},
|
|
532
532
|
|
|
533
|
-
// OAuth
|
|
534
|
-
returnUrl: 'https://mygame.com/', // OAuth
|
|
533
|
+
// OAuth configuration (optional)
|
|
534
|
+
returnUrl: 'https://mygame.com/', // URL to return after OAuth login, optional, defaults to window.location.href
|
|
535
535
|
enableOAuth: {
|
|
536
|
-
google: true, //
|
|
537
|
-
discord: true, //
|
|
538
|
-
github: true, //
|
|
536
|
+
google: true, // Enable Google login
|
|
537
|
+
discord: true, // Enable Discord login
|
|
538
|
+
github: true, // Enable GitHub login
|
|
539
539
|
},
|
|
540
540
|
});
|
|
541
541
|
|
|
542
|
-
//
|
|
542
|
+
// Show login interface
|
|
543
543
|
authModal.show('login');
|
|
544
544
|
|
|
545
|
-
//
|
|
545
|
+
// Show signup interface
|
|
546
546
|
authModal.show('signup');
|
|
547
547
|
|
|
548
|
-
//
|
|
548
|
+
// Hide modal
|
|
549
549
|
authModal.hide();
|
|
550
550
|
|
|
551
|
-
//
|
|
551
|
+
// Destroy modal
|
|
552
552
|
authModal.destroy();
|
|
553
553
|
```
|
|
554
554
|
|
|
555
|
-
**✨
|
|
555
|
+
**✨ New Feature: Auto Toast Notifications**
|
|
556
556
|
|
|
557
|
-
AuthModal
|
|
557
|
+
AuthModal has built-in modern Toast notification system, no extra configuration needed:
|
|
558
558
|
|
|
559
559
|
```typescript
|
|
560
|
-
// Toast
|
|
561
|
-
// -
|
|
562
|
-
// -
|
|
563
|
-
// -
|
|
564
|
-
// -
|
|
560
|
+
// Toast displays automatically, CSS auto-injected
|
|
561
|
+
// - Registration success → Green success notification
|
|
562
|
+
// - Account exists → Yellow warning notification
|
|
563
|
+
// - Login failure → Red error notification
|
|
564
|
+
// - Reset password → Blue info notification
|
|
565
565
|
|
|
566
|
-
//
|
|
566
|
+
// You can also use Toast independently (CSS auto-injected)
|
|
567
567
|
import { Toast } from '@seaverse/auth-sdk';
|
|
568
568
|
|
|
569
|
-
Toast.success('
|
|
570
|
-
Toast.error('
|
|
571
|
-
Toast.warning('
|
|
572
|
-
Toast.info('
|
|
569
|
+
Toast.success('Success', 'Data saved');
|
|
570
|
+
Toast.error('Failed', 'Please try again later');
|
|
571
|
+
Toast.warning('Notice', 'Account already exists');
|
|
572
|
+
Toast.info('Info', 'Email sent');
|
|
573
573
|
```
|
|
574
574
|
|
|
575
|
-
####
|
|
575
|
+
#### Password Reset Flow
|
|
576
576
|
|
|
577
|
-
AuthModal
|
|
577
|
+
AuthModal supports complete password reset flow:
|
|
578
578
|
|
|
579
|
-
1.
|
|
580
|
-
2.
|
|
581
|
-
3.
|
|
582
|
-
4.
|
|
583
|
-
5.
|
|
579
|
+
1. **User triggers forgot password**: Click "Forgot Password?" link in login interface
|
|
580
|
+
2. **Send reset email**: After entering email, system sends reset link with `reset_token`
|
|
581
|
+
3. **Auto trigger reset modal**: When user clicks link in email and returns to website, AuthModal automatically detects `reset_token` parameter in URL and shows reset password form
|
|
582
|
+
4. **Set new password**: User enters and confirms new password then submits
|
|
583
|
+
5. **Auto cleanup URL**: After successful reset, automatically clears `reset_token` parameter from URL
|
|
584
584
|
|
|
585
|
-
|
|
585
|
+
The entire process requires no extra code, AuthModal handles it automatically:
|
|
586
586
|
|
|
587
587
|
```typescript
|
|
588
|
-
// 1.
|
|
588
|
+
// 1. Initialize AuthModal (only once)
|
|
589
589
|
const authModal = new AuthModal({
|
|
590
590
|
client,
|
|
591
|
-
// ...
|
|
591
|
+
// ... other configuration
|
|
592
592
|
});
|
|
593
593
|
|
|
594
|
-
// 2.
|
|
595
|
-
// -
|
|
596
|
-
// -
|
|
597
|
-
// -
|
|
598
|
-
// -
|
|
599
|
-
// -
|
|
600
|
-
// -
|
|
594
|
+
// 2. After user clicks reset link in email, AuthModal automatically:
|
|
595
|
+
// - Detects ?reset_token=xxx parameter in URL
|
|
596
|
+
// - Shows reset password form
|
|
597
|
+
// - User submits new password
|
|
598
|
+
// - Calls client.resetPassword() API
|
|
599
|
+
// - Cleans reset_token parameter from URL
|
|
600
|
+
// - Shows success message
|
|
601
601
|
```
|
|
602
602
|
|
|
603
|
-
#### OAuth
|
|
603
|
+
#### OAuth Configuration Instructions
|
|
604
604
|
|
|
605
|
-
`enableOAuth`
|
|
605
|
+
The `enableOAuth` parameter is **completely optional**:
|
|
606
606
|
|
|
607
|
-
-
|
|
608
|
-
-
|
|
609
|
-
-
|
|
607
|
+
- If **not provided**, no third-party login buttons will be shown
|
|
608
|
+
- If **partially configured** (e.g., only enable Google), only enabled buttons will be shown
|
|
609
|
+
- If **fully configured** for all platforms, all third-party login buttons will be shown
|
|
610
610
|
|
|
611
|
-
|
|
612
|
-
- `returnUrl
|
|
613
|
-
- `enableOAuth.google
|
|
614
|
-
- `enableOAuth.discord
|
|
615
|
-
- `enableOAuth.github
|
|
611
|
+
**Configuration field descriptions**:
|
|
612
|
+
- `returnUrl`: **Optional** - URL to return after OAuth login, defaults to `window.location.href` if not provided
|
|
613
|
+
- `enableOAuth.google`: Whether to enable Google login
|
|
614
|
+
- `enableOAuth.discord`: Whether to enable Discord login
|
|
615
|
+
- `enableOAuth.github`: Whether to enable GitHub login
|
|
616
616
|
|
|
617
617
|
```typescript
|
|
618
|
-
//
|
|
618
|
+
// Example 1: No OAuth buttons
|
|
619
619
|
const authModal1 = new AuthModal({
|
|
620
620
|
client,
|
|
621
621
|
theme: 'dark',
|
|
622
|
-
//
|
|
622
|
+
// No enableOAuth, no OAuth buttons shown
|
|
623
623
|
});
|
|
624
624
|
|
|
625
|
-
//
|
|
625
|
+
// Example 2: Only show Google login
|
|
626
626
|
const authModal2 = new AuthModal({
|
|
627
627
|
client,
|
|
628
628
|
theme: 'light',
|
|
629
629
|
returnUrl: 'https://mygame.com/dashboard',
|
|
630
630
|
enableOAuth: {
|
|
631
631
|
google: true,
|
|
632
|
-
// Discord
|
|
632
|
+
// Discord and GitHub not enabled, won't show these buttons
|
|
633
633
|
},
|
|
634
634
|
});
|
|
635
635
|
|
|
636
|
-
//
|
|
636
|
+
// Example 3: Show all OAuth buttons
|
|
637
637
|
const authModal3 = new AuthModal({
|
|
638
638
|
client,
|
|
639
639
|
theme: 'dark',
|
|
@@ -645,58 +645,58 @@ const authModal3 = new AuthModal({
|
|
|
645
645
|
});
|
|
646
646
|
```
|
|
647
647
|
|
|
648
|
-
####
|
|
648
|
+
#### Handling OAuth Callback
|
|
649
649
|
|
|
650
|
-
|
|
650
|
+
In Backend Proxy Mode, after OAuth login it redirects to `returnUrl?token=xxx`. Check and handle token on page load:
|
|
651
651
|
|
|
652
652
|
```typescript
|
|
653
|
-
//
|
|
653
|
+
// Automatically handle OAuth callback on page load
|
|
654
654
|
const result = AuthModal.handleOAuthCallback({
|
|
655
655
|
client,
|
|
656
656
|
onLoginSuccess: (token) => {
|
|
657
657
|
localStorage.setItem('token', token);
|
|
658
|
-
console.log('OAuth
|
|
658
|
+
console.log('OAuth login successful');
|
|
659
659
|
|
|
660
|
-
//
|
|
661
|
-
// handleOAuthCallback
|
|
660
|
+
// Can now call authenticated APIs directly
|
|
661
|
+
// handleOAuthCallback has automatically called client.setToken()
|
|
662
662
|
client.getCurrentUser()
|
|
663
|
-
.then(user => console.log('
|
|
663
|
+
.then(user => console.log('User info:', user));
|
|
664
664
|
},
|
|
665
665
|
});
|
|
666
666
|
|
|
667
667
|
if (result) {
|
|
668
|
-
console.log('
|
|
668
|
+
console.log('Handled OAuth callback, token:', result.token);
|
|
669
669
|
}
|
|
670
670
|
```
|
|
671
671
|
|
|
672
|
-
|
|
673
|
-
- `handleOAuthCallback()`
|
|
674
|
-
-
|
|
672
|
+
**Important Note**:
|
|
673
|
+
- `handleOAuthCallback()` automatically calls `client.setToken(token)` to update client authentication configuration
|
|
674
|
+
- This means after the `onLoginSuccess` callback, all authenticated APIs (such as `getCurrentUser()`, `logout()`, etc.) will automatically include the `Authorization` header
|
|
675
675
|
|
|
676
|
-
####
|
|
676
|
+
#### Manual Token Setting
|
|
677
677
|
|
|
678
|
-
|
|
678
|
+
If you don't use `AuthModal.handleOAuthCallback()`, you can also set the token manually:
|
|
679
679
|
|
|
680
680
|
```typescript
|
|
681
|
-
//
|
|
681
|
+
// Extract token from URL
|
|
682
682
|
const token = new URLSearchParams(window.location.search).get('token');
|
|
683
683
|
if (token) {
|
|
684
|
-
//
|
|
684
|
+
// Manually set token
|
|
685
685
|
client.setToken(token);
|
|
686
686
|
localStorage.setItem('token', token);
|
|
687
687
|
|
|
688
|
-
//
|
|
688
|
+
// Now can call authenticated APIs
|
|
689
689
|
const user = await client.getCurrentUser();
|
|
690
690
|
}
|
|
691
691
|
```
|
|
692
692
|
|
|
693
|
-
### 6.
|
|
693
|
+
### 6. Container Management
|
|
694
694
|
|
|
695
695
|
```typescript
|
|
696
|
-
//
|
|
696
|
+
// List all containers
|
|
697
697
|
const containers = await client.listContainers();
|
|
698
698
|
|
|
699
|
-
//
|
|
699
|
+
// Register new container
|
|
700
700
|
const result = await client.registerContainer({
|
|
701
701
|
containerId: 'container-123',
|
|
702
702
|
metadata: {
|
|
@@ -705,226 +705,226 @@ const result = await client.registerContainer({
|
|
|
705
705
|
},
|
|
706
706
|
});
|
|
707
707
|
|
|
708
|
-
//
|
|
708
|
+
// Get container info
|
|
709
709
|
const container = await client.getContainer({
|
|
710
710
|
containerId: 'container-123',
|
|
711
711
|
});
|
|
712
712
|
|
|
713
|
-
//
|
|
713
|
+
// Container heartbeat
|
|
714
714
|
await client.containerHeartbeat({
|
|
715
715
|
containerId: 'container-123',
|
|
716
716
|
status: 'healthy',
|
|
717
717
|
});
|
|
718
718
|
|
|
719
|
-
//
|
|
719
|
+
// Unregister container
|
|
720
720
|
await client.unregisterContainer({
|
|
721
721
|
containerId: 'container-123',
|
|
722
722
|
});
|
|
723
723
|
|
|
724
|
-
//
|
|
724
|
+
// Get container stats
|
|
725
725
|
const stats = await client.getContainerStats();
|
|
726
726
|
```
|
|
727
727
|
|
|
728
|
-
### 7.
|
|
728
|
+
### 7. Skill Marketplace
|
|
729
729
|
|
|
730
730
|
```typescript
|
|
731
|
-
//
|
|
731
|
+
// List marketplace skills
|
|
732
732
|
const skills = await client.listMarketplaceSkills({
|
|
733
733
|
category: 'productivity',
|
|
734
734
|
page: 1,
|
|
735
735
|
pageSize: 20,
|
|
736
736
|
});
|
|
737
737
|
|
|
738
|
-
//
|
|
738
|
+
// Get skill details
|
|
739
739
|
const skill = await client.getMarketplaceSkill({
|
|
740
740
|
skillId: 'skill-123',
|
|
741
741
|
});
|
|
742
742
|
|
|
743
|
-
//
|
|
743
|
+
// Install skill
|
|
744
744
|
await client.installSkill({
|
|
745
745
|
skillId: 'skill-123',
|
|
746
746
|
});
|
|
747
747
|
|
|
748
|
-
//
|
|
748
|
+
// List installed skills
|
|
749
749
|
const userSkills = await client.listUserSkills();
|
|
750
750
|
|
|
751
|
-
//
|
|
751
|
+
// Uninstall skill
|
|
752
752
|
await client.uninstallSkill({
|
|
753
753
|
skillId: 'skill-123',
|
|
754
754
|
});
|
|
755
755
|
```
|
|
756
756
|
|
|
757
|
-
### 8.
|
|
757
|
+
### 8. Invitation Code Management
|
|
758
758
|
|
|
759
759
|
```typescript
|
|
760
|
-
//
|
|
760
|
+
// Apply for invitation code (when user doesn't have one)
|
|
761
761
|
const application = await client.applyInvite({
|
|
762
762
|
email: 'player@example.com',
|
|
763
763
|
reason: 'I want to join this amazing platform to build innovative applications and connect with the community.'
|
|
764
764
|
});
|
|
765
765
|
|
|
766
766
|
if (application.success) {
|
|
767
|
-
console.log('
|
|
768
|
-
console.log('
|
|
769
|
-
console.log('
|
|
767
|
+
console.log('Application submitted:', application.data);
|
|
768
|
+
console.log('Application ID:', application.data.id);
|
|
769
|
+
console.log('Status:', application.data.status); // 'pending'
|
|
770
770
|
} else {
|
|
771
|
-
//
|
|
771
|
+
// Handle error
|
|
772
772
|
if (application.code === 'APPLICATION_DUPLICATE') {
|
|
773
|
-
console.log('
|
|
773
|
+
console.log('You have already submitted an application in the past 24 hours');
|
|
774
774
|
} else {
|
|
775
|
-
console.error('
|
|
775
|
+
console.error('Application failed:', application.error);
|
|
776
776
|
}
|
|
777
777
|
}
|
|
778
778
|
|
|
779
|
-
//
|
|
779
|
+
// List my invitation codes
|
|
780
780
|
const invites = await client.listInvites({
|
|
781
781
|
status: 'active',
|
|
782
782
|
page: 1,
|
|
783
783
|
page_size: 20,
|
|
784
784
|
});
|
|
785
|
-
console.log('
|
|
785
|
+
console.log('My invitation codes:', invites.data.invites);
|
|
786
786
|
|
|
787
|
-
//
|
|
787
|
+
// Get invitation code stats
|
|
788
788
|
const stats = await client.getInviteStats();
|
|
789
|
-
console.log('
|
|
790
|
-
console.log('
|
|
791
|
-
console.log('
|
|
789
|
+
console.log('Total invitation codes:', stats.data.total_codes);
|
|
790
|
+
console.log('Active invitation codes:', stats.data.active_codes);
|
|
791
|
+
console.log('Total usage count:', stats.data.total_uses);
|
|
792
792
|
|
|
793
|
-
//
|
|
793
|
+
// Get invitation code details
|
|
794
794
|
const invite = await client.getInvite('inv_abc123');
|
|
795
|
-
console.log('
|
|
796
|
-
console.log('
|
|
797
|
-
console.log('
|
|
795
|
+
console.log('Invitation code:', invite.data.code);
|
|
796
|
+
console.log('Used count:', invite.data.used_count);
|
|
797
|
+
console.log('Max uses:', invite.data.max_uses);
|
|
798
798
|
|
|
799
|
-
//
|
|
799
|
+
// Get invitation code usage records
|
|
800
800
|
const usages = await client.getInviteUsages('inv_abc123', {
|
|
801
801
|
page: 1,
|
|
802
802
|
page_size: 20,
|
|
803
803
|
});
|
|
804
|
-
console.log('
|
|
804
|
+
console.log('Usage records:', usages.data.usages);
|
|
805
805
|
```
|
|
806
806
|
|
|
807
|
-
### 9.
|
|
807
|
+
### 9. Email Verification and Invitation Code Binding
|
|
808
808
|
|
|
809
|
-
####
|
|
809
|
+
#### Email Verification (Auto Login)
|
|
810
810
|
|
|
811
|
-
|
|
811
|
+
After registration, users receive a verification email containing a verification link in the format: `frontend_url?verify_token=xxx`
|
|
812
812
|
|
|
813
|
-
|
|
813
|
+
**Method 1: Use AuthModal Auto-Handling (Recommended)**
|
|
814
814
|
|
|
815
815
|
```typescript
|
|
816
816
|
import { AuthModal } from '@seaverse/auth-sdk';
|
|
817
817
|
|
|
818
|
-
//
|
|
818
|
+
// Create AuthModal instance
|
|
819
819
|
const modal = new AuthModal({
|
|
820
820
|
client,
|
|
821
821
|
onLoginSuccess: (token, user) => {
|
|
822
822
|
localStorage.setItem('token', token);
|
|
823
|
-
console.log('
|
|
823
|
+
console.log('Email verified and logged in successfully:', user);
|
|
824
824
|
},
|
|
825
825
|
onError: (error) => {
|
|
826
|
-
console.error('
|
|
826
|
+
console.error('Email verification failed:', error);
|
|
827
827
|
}
|
|
828
828
|
});
|
|
829
829
|
|
|
830
|
-
// AuthModal
|
|
831
|
-
//
|
|
832
|
-
// 1.
|
|
833
|
-
// 2.
|
|
834
|
-
// 3.
|
|
835
|
-
// 4.
|
|
836
|
-
// 5.
|
|
830
|
+
// AuthModal will automatically detect verify_token parameter in URL
|
|
831
|
+
// After detection, it will automatically:
|
|
832
|
+
// 1. Call verifyEmail() API to verify email
|
|
833
|
+
// 2. Get returned JWT token and auto-login
|
|
834
|
+
// 3. Trigger onLoginSuccess callback
|
|
835
|
+
// 4. Clean verify_token parameter from URL
|
|
836
|
+
// 5. Show success message
|
|
837
837
|
```
|
|
838
838
|
|
|
839
|
-
|
|
839
|
+
**Debugging Tips**:
|
|
840
840
|
|
|
841
|
-
|
|
841
|
+
If email verification has issues, check browser console logs:
|
|
842
842
|
|
|
843
843
|
```javascript
|
|
844
|
-
//
|
|
844
|
+
// Normal case should show:
|
|
845
845
|
[AuthModal] Detected verify_token, starting email verification...
|
|
846
846
|
[AuthModal] Email verification successful: { id: "xxx", email: "user@example.com", ... }
|
|
847
847
|
|
|
848
|
-
//
|
|
848
|
+
// If verification fails, will show:
|
|
849
849
|
[AuthModal] Email verification failed: Error: ...
|
|
850
850
|
```
|
|
851
851
|
|
|
852
|
-
|
|
852
|
+
**Method 2: Manual Email Verification Handling**
|
|
853
853
|
|
|
854
854
|
```typescript
|
|
855
|
-
//
|
|
855
|
+
// Verify email (get verify_token from email link)
|
|
856
856
|
const urlParams = new URLSearchParams(window.location.search);
|
|
857
857
|
const verifyToken = urlParams.get('verify_token');
|
|
858
858
|
|
|
859
859
|
if (verifyToken) {
|
|
860
860
|
const result = await client.verifyEmail(verifyToken);
|
|
861
861
|
|
|
862
|
-
//
|
|
862
|
+
// Auto-login: save returned token
|
|
863
863
|
localStorage.setItem('token', result.data.token);
|
|
864
864
|
localStorage.setItem('refreshToken', result.data.refreshToken);
|
|
865
865
|
|
|
866
|
-
console.log('
|
|
866
|
+
console.log('Email verified successfully, auto-logged in:', result.data.user);
|
|
867
867
|
|
|
868
|
-
//
|
|
868
|
+
// Redirect to homepage
|
|
869
869
|
window.location.href = '/';
|
|
870
870
|
}
|
|
871
871
|
```
|
|
872
872
|
|
|
873
|
-
####
|
|
873
|
+
#### Invitation Code Binding (Account Activation)
|
|
874
874
|
|
|
875
|
-
|
|
875
|
+
When registering with external email or OAuth login without providing invitation code, a temporary account is created and requires subsequent invitation code binding for activation.
|
|
876
876
|
|
|
877
|
-
|
|
877
|
+
**Apply for Invitation Code Feature**:
|
|
878
878
|
|
|
879
|
-
|
|
880
|
-
1.
|
|
881
|
-
2.
|
|
882
|
-
3.
|
|
883
|
-
4.
|
|
879
|
+
If user doesn't have an invitation code, AuthModal provides built-in application feature:
|
|
880
|
+
1. In invitation code input interface, user can click "Don't have an invitation code? Apply for one" link
|
|
881
|
+
2. Automatically jumps to apply for invitation code form
|
|
882
|
+
3. User fills in email and application reason (10-500 characters)
|
|
883
|
+
4. After submission, calls `applyInvite()` API, after backend approval, invitation code will be sent via email
|
|
884
884
|
|
|
885
|
-
|
|
886
|
-
-
|
|
887
|
-
- redirectUrl
|
|
888
|
-
-
|
|
885
|
+
**Auto Redirect Mechanism**:
|
|
886
|
+
- When `login()` returns `INVITE_CODE_REQUIRED` error and includes `redirectUrl`, SDK automatically redirects page to that URL
|
|
887
|
+
- redirectUrl usually contains `error_code=INVITE_CODE_REQUIRED&user_id=xxx&email=xxx` parameters
|
|
888
|
+
- No need to manually handle redirect logic, SDK completes automatically
|
|
889
889
|
|
|
890
|
-
|
|
890
|
+
For other scenarios (such as redirects after OAuth login), backend directly redirects to `frontend_url?error_code=INVITE_CODE_REQUIRED&user_id=xxx&email=xxx`.
|
|
891
891
|
|
|
892
|
-
|
|
892
|
+
**Method 1: Use AuthModal Auto-Handling (Recommended)**
|
|
893
893
|
|
|
894
894
|
```typescript
|
|
895
895
|
import { AuthModal } from '@seaverse/auth-sdk';
|
|
896
896
|
|
|
897
|
-
//
|
|
897
|
+
// Create AuthModal instance
|
|
898
898
|
const modal = new AuthModal({
|
|
899
899
|
client,
|
|
900
900
|
onLoginSuccess: (token, user) => {
|
|
901
901
|
localStorage.setItem('token', token);
|
|
902
|
-
console.log('
|
|
902
|
+
console.log('Login successful:', user);
|
|
903
903
|
},
|
|
904
904
|
onInviteCodeRequired: (userId, email) => {
|
|
905
|
-
//
|
|
906
|
-
console.log('
|
|
905
|
+
// Optional: custom handling when invitation code is needed
|
|
906
|
+
console.log('Need invitation code to activate account:', userId, email);
|
|
907
907
|
}
|
|
908
908
|
});
|
|
909
909
|
|
|
910
|
-
// AuthModal
|
|
910
|
+
// AuthModal will automatically detect the following parameter combinations in URL:
|
|
911
911
|
// - error_code=INVITE_CODE_REQUIRED
|
|
912
|
-
// - user_id
|
|
913
|
-
// - email
|
|
912
|
+
// - user_id or temp_user_id (user ID)
|
|
913
|
+
// - email (optional, user email)
|
|
914
914
|
//
|
|
915
|
-
//
|
|
916
|
-
// 1.
|
|
917
|
-
// 2.
|
|
918
|
-
// 3.
|
|
919
|
-
// 4.
|
|
915
|
+
// After detection, it will automatically:
|
|
916
|
+
// 1. Show invitation code input interface
|
|
917
|
+
// 2. After user inputs invitation code, call bindInviteCode() API
|
|
918
|
+
// 3. After successful activation, auto-login (save token)
|
|
919
|
+
// 4. Clean parameters from URL
|
|
920
920
|
```
|
|
921
921
|
|
|
922
|
-
|
|
922
|
+
**Debugging Tips**:
|
|
923
923
|
|
|
924
|
-
|
|
924
|
+
If invitation code modal doesn't appear, check browser console for the following logs:
|
|
925
925
|
|
|
926
926
|
```javascript
|
|
927
|
-
//
|
|
927
|
+
// Normal case should show:
|
|
928
928
|
[AuthModal] Detected INVITE_CODE_REQUIRED: {
|
|
929
929
|
errorCode: "INVITE_CODE_REQUIRED",
|
|
930
930
|
userId: "xxx",
|
|
@@ -933,50 +933,50 @@ const modal = new AuthModal({
|
|
|
933
933
|
fullURL: "http://localhost:8001/?error_code=INVITE_CODE_REQUIRED&user_id=xxx&email=xxx"
|
|
934
934
|
}
|
|
935
935
|
|
|
936
|
-
//
|
|
936
|
+
// If user_id is missing, will show error:
|
|
937
937
|
[AuthModal] Missing user_id in URL parameters.
|
|
938
|
-
//
|
|
938
|
+
// And shows alert with full URL for troubleshooting backend redirect issues
|
|
939
939
|
```
|
|
940
940
|
|
|
941
|
-
|
|
941
|
+
**Method 2: Manual Invitation Code Binding Handling**
|
|
942
942
|
|
|
943
943
|
```typescript
|
|
944
|
-
// 1.
|
|
944
|
+
// 1. Check if invitation code is needed in URL
|
|
945
945
|
const urlParams = new URLSearchParams(window.location.search);
|
|
946
946
|
const errorCode = urlParams.get('error_code');
|
|
947
947
|
const userId = urlParams.get('user_id');
|
|
948
948
|
|
|
949
949
|
if (errorCode === 'INVITE_CODE_REQUIRED' && userId) {
|
|
950
|
-
// 2.
|
|
951
|
-
const inviteCode = await showInviteCodeInput(); //
|
|
950
|
+
// 2. Show invitation code input interface (custom UI)
|
|
951
|
+
const inviteCode = await showInviteCodeInput(); // Your custom UI
|
|
952
952
|
|
|
953
|
-
// 3.
|
|
953
|
+
// 3. Bind invitation code
|
|
954
954
|
const result = await client.bindInviteCode({
|
|
955
955
|
user_id: userId,
|
|
956
956
|
invite_code: inviteCode
|
|
957
957
|
});
|
|
958
958
|
|
|
959
|
-
// 4.
|
|
959
|
+
// 4. Activation successful, auto-login
|
|
960
960
|
localStorage.setItem('token', result.data.token);
|
|
961
961
|
localStorage.setItem('refreshToken', result.data.refreshToken);
|
|
962
|
-
console.log('
|
|
962
|
+
console.log('Account activated successfully:', result.data.user);
|
|
963
963
|
|
|
964
|
-
// 5.
|
|
964
|
+
// 5. Redirect to homepage
|
|
965
965
|
window.location.href = '/';
|
|
966
966
|
}
|
|
967
967
|
```
|
|
968
968
|
|
|
969
|
-
|
|
969
|
+
**Method 3: Use Static Method Handling**
|
|
970
970
|
|
|
971
971
|
```typescript
|
|
972
972
|
import { AuthModal } from '@seaverse/auth-sdk';
|
|
973
973
|
|
|
974
|
-
// AuthModal
|
|
974
|
+
// AuthModal provides static method to handle invitation code scenario
|
|
975
975
|
const inviteCodeInfo = AuthModal.handleInviteCodeRequired(
|
|
976
976
|
{ client },
|
|
977
977
|
(userId, email) => {
|
|
978
|
-
//
|
|
979
|
-
const code = prompt(
|
|
978
|
+
// Custom handling logic
|
|
979
|
+
const code = prompt(`Please enter invitation code to activate account (${email}):`);
|
|
980
980
|
if (code) {
|
|
981
981
|
client.bindInviteCode({ user_id: userId, invite_code: code })
|
|
982
982
|
.then(res => {
|
|
@@ -988,33 +988,33 @@ const inviteCodeInfo = AuthModal.handleInviteCodeRequired(
|
|
|
988
988
|
);
|
|
989
989
|
```
|
|
990
990
|
|
|
991
|
-
### 10.
|
|
991
|
+
### 10. Other Features
|
|
992
992
|
|
|
993
993
|
```typescript
|
|
994
|
-
//
|
|
994
|
+
// Get API Service Token
|
|
995
995
|
const apiToken = await client.getApiServiceToken();
|
|
996
996
|
|
|
997
|
-
//
|
|
997
|
+
// Get conversation status
|
|
998
998
|
const status = await client.getConversationStatus({
|
|
999
999
|
conversationId: 'conv-123',
|
|
1000
1000
|
});
|
|
1001
1001
|
|
|
1002
|
-
//
|
|
1002
|
+
// Get speech token
|
|
1003
1003
|
const speechToken = await client.getSpeechToken();
|
|
1004
1004
|
```
|
|
1005
1005
|
|
|
1006
|
-
##
|
|
1006
|
+
## Advanced Configuration
|
|
1007
1007
|
|
|
1008
|
-
###
|
|
1008
|
+
### Custom Authentication
|
|
1009
1009
|
|
|
1010
1010
|
```typescript
|
|
1011
1011
|
import { AuthFactory } from '@seaverse/auth-sdk';
|
|
1012
1012
|
|
|
1013
1013
|
const client = new SeaVerseBackendAPIClient({
|
|
1014
|
-
appId: 'your app id', //
|
|
1014
|
+
appId: 'your app id', // Required: Application ID
|
|
1015
1015
|
environment: 'production',
|
|
1016
1016
|
|
|
1017
|
-
//
|
|
1017
|
+
// Use JWT authentication
|
|
1018
1018
|
auth: AuthFactory.create({
|
|
1019
1019
|
type: 'jwt',
|
|
1020
1020
|
credentials: {
|
|
@@ -1025,28 +1025,28 @@ const client = new SeaVerseBackendAPIClient({
|
|
|
1025
1025
|
});
|
|
1026
1026
|
```
|
|
1027
1027
|
|
|
1028
|
-
###
|
|
1028
|
+
### Custom Hooks
|
|
1029
1029
|
|
|
1030
1030
|
```typescript
|
|
1031
1031
|
import { BuiltInHooks } from '@seaverse/auth-sdk';
|
|
1032
1032
|
|
|
1033
1033
|
const client = new SeaVerseBackendAPIClient({
|
|
1034
|
-
appId: 'your app id', //
|
|
1034
|
+
appId: 'your app id', // Required: Application ID
|
|
1035
1035
|
environment: 'production',
|
|
1036
1036
|
|
|
1037
1037
|
hooks: {
|
|
1038
1038
|
hooks: [
|
|
1039
|
-
//
|
|
1039
|
+
// Logger Hook
|
|
1040
1040
|
BuiltInHooks.createLoggerHook({
|
|
1041
1041
|
logLevel: 'debug',
|
|
1042
1042
|
logRequestBody: true,
|
|
1043
1043
|
logResponseBody: true,
|
|
1044
1044
|
}),
|
|
1045
1045
|
|
|
1046
|
-
//
|
|
1046
|
+
// Request ID Hook
|
|
1047
1047
|
BuiltInHooks.createRequestIdHook(),
|
|
1048
1048
|
|
|
1049
|
-
//
|
|
1049
|
+
// Custom Hook
|
|
1050
1050
|
{
|
|
1051
1051
|
type: 'beforeRequest',
|
|
1052
1052
|
name: 'custom-hook',
|
|
@@ -1060,22 +1060,22 @@ const client = new SeaVerseBackendAPIClient({
|
|
|
1060
1060
|
});
|
|
1061
1061
|
```
|
|
1062
1062
|
|
|
1063
|
-
###
|
|
1063
|
+
### Request Retry Configuration
|
|
1064
1064
|
|
|
1065
|
-
SDK
|
|
1065
|
+
The SDK supports custom HTTP request retry strategies. **By default, retry functionality is disabled (maxRetries: 0)** to ensure request predictability.
|
|
1066
1066
|
|
|
1067
|
-
####
|
|
1067
|
+
#### Default Behavior (Retry Disabled)
|
|
1068
1068
|
|
|
1069
1069
|
```typescript
|
|
1070
1070
|
const client = new SeaVerseBackendAPIClient({
|
|
1071
1071
|
appId: 'your app id',
|
|
1072
|
-
// retryOptions
|
|
1072
|
+
// retryOptions not set, no retry by default
|
|
1073
1073
|
});
|
|
1074
1074
|
|
|
1075
|
-
//
|
|
1075
|
+
// If request fails, will return error immediately, no retry
|
|
1076
1076
|
```
|
|
1077
1077
|
|
|
1078
|
-
####
|
|
1078
|
+
#### Enable Basic Retry
|
|
1079
1079
|
|
|
1080
1080
|
```typescript
|
|
1081
1081
|
import { SeaVerseBackendAPIClient } from '@seaverse/auth-sdk';
|
|
@@ -1083,25 +1083,25 @@ import { SeaVerseBackendAPIClient } from '@seaverse/auth-sdk';
|
|
|
1083
1083
|
const client = new SeaVerseBackendAPIClient({
|
|
1084
1084
|
appId: 'your app id',
|
|
1085
1085
|
retryOptions: {
|
|
1086
|
-
maxRetries: 3, //
|
|
1087
|
-
retryDelay: 1000, //
|
|
1088
|
-
//
|
|
1086
|
+
maxRetries: 3, // Retry up to 3 times
|
|
1087
|
+
retryDelay: 1000, // Initial delay 1000ms (1 second)
|
|
1088
|
+
// Defaults to retry on these status codes: [408, 429, 500, 502, 503, 504]
|
|
1089
1089
|
},
|
|
1090
1090
|
});
|
|
1091
1091
|
```
|
|
1092
1092
|
|
|
1093
|
-
|
|
1094
|
-
-
|
|
1095
|
-
-
|
|
1096
|
-
- `408` - Request Timeout
|
|
1097
|
-
- `429` - Too Many Requests
|
|
1098
|
-
- `500` - Internal Server Error
|
|
1099
|
-
- `502` - Bad Gateway
|
|
1100
|
-
- `503` - Service Unavailable
|
|
1101
|
-
- `504` - Gateway Timeout
|
|
1102
|
-
-
|
|
1093
|
+
**Retry Strategy Explanation**:
|
|
1094
|
+
- Uses **exponential backoff** algorithm: 1st retry waits 1 second, 2nd waits 2 seconds, 3rd waits 4 seconds
|
|
1095
|
+
- HTTP status codes that trigger auto-retry:
|
|
1096
|
+
- `408` - Request Timeout
|
|
1097
|
+
- `429` - Too Many Requests (rate limiting)
|
|
1098
|
+
- `500` - Internal Server Error
|
|
1099
|
+
- `502` - Bad Gateway
|
|
1100
|
+
- `503` - Service Unavailable
|
|
1101
|
+
- `504` - Gateway Timeout
|
|
1102
|
+
- Network errors (no response) will also trigger retry
|
|
1103
1103
|
|
|
1104
|
-
####
|
|
1104
|
+
#### Custom Retry Status Codes
|
|
1105
1105
|
|
|
1106
1106
|
```typescript
|
|
1107
1107
|
const client = new SeaVerseBackendAPIClient({
|
|
@@ -1109,12 +1109,12 @@ const client = new SeaVerseBackendAPIClient({
|
|
|
1109
1109
|
retryOptions: {
|
|
1110
1110
|
maxRetries: 5,
|
|
1111
1111
|
retryDelay: 2000,
|
|
1112
|
-
retryStatusCodes: [503, 504], //
|
|
1112
|
+
retryStatusCodes: [503, 504], // Only retry on 503 and 504
|
|
1113
1113
|
},
|
|
1114
1114
|
});
|
|
1115
1115
|
```
|
|
1116
1116
|
|
|
1117
|
-
####
|
|
1117
|
+
#### Custom Retry Logic
|
|
1118
1118
|
|
|
1119
1119
|
```typescript
|
|
1120
1120
|
import type { RetryOptions } from '@seaverse/auth-sdk';
|
|
@@ -1122,13 +1122,13 @@ import type { RetryOptions } from '@seaverse/auth-sdk';
|
|
|
1122
1122
|
const retryOptions: RetryOptions = {
|
|
1123
1123
|
maxRetries: 3,
|
|
1124
1124
|
retryDelay: 1000,
|
|
1125
|
-
//
|
|
1125
|
+
// Custom judgment logic: only retry on specific errors
|
|
1126
1126
|
shouldRetry: (error) => {
|
|
1127
|
-
//
|
|
1127
|
+
// Only retry on service unavailable errors
|
|
1128
1128
|
if (error.response?.status === 503) {
|
|
1129
1129
|
return true;
|
|
1130
1130
|
}
|
|
1131
|
-
//
|
|
1131
|
+
// Retry on network errors with no response
|
|
1132
1132
|
if (!error.response) {
|
|
1133
1133
|
return true;
|
|
1134
1134
|
}
|
|
@@ -1142,153 +1142,153 @@ const client = new SeaVerseBackendAPIClient({
|
|
|
1142
1142
|
});
|
|
1143
1143
|
```
|
|
1144
1144
|
|
|
1145
|
-
#### RetryOptions
|
|
1145
|
+
#### RetryOptions Type Definition
|
|
1146
1146
|
|
|
1147
1147
|
```typescript
|
|
1148
1148
|
interface RetryOptions {
|
|
1149
|
-
maxRetries?: number; //
|
|
1150
|
-
retryDelay?: number; //
|
|
1151
|
-
retryStatusCodes?: number[]; //
|
|
1152
|
-
shouldRetry?: (error: AxiosError) => boolean; //
|
|
1149
|
+
maxRetries?: number; // Maximum retry count, default 0 (disabled)
|
|
1150
|
+
retryDelay?: number; // Initial retry delay (milliseconds), default 1000
|
|
1151
|
+
retryStatusCodes?: number[]; // List of status codes that trigger retry
|
|
1152
|
+
shouldRetry?: (error: AxiosError) => boolean; // Custom retry judgment function
|
|
1153
1153
|
}
|
|
1154
1154
|
```
|
|
1155
1155
|
|
|
1156
|
-
|
|
1157
|
-
- ⚠️
|
|
1158
|
-
- ✅
|
|
1159
|
-
- ✅
|
|
1160
|
-
- ✅
|
|
1156
|
+
**Usage Recommendations**:
|
|
1157
|
+
- ⚠️ **Recommended to disable retry in production** (default behavior), avoid duplicate requests on business logic errors
|
|
1158
|
+
- ✅ Only enable retry in unstable network scenarios, such as mobile apps, weak network environments
|
|
1159
|
+
- ✅ Ensure backend APIs support idempotent operations, avoid side effects from retries
|
|
1160
|
+
- ✅ For critical business (such as payments), recommended to use custom `shouldRetry` to carefully control retry logic
|
|
1161
1161
|
|
|
1162
|
-
###
|
|
1162
|
+
### Environment Configuration
|
|
1163
1163
|
|
|
1164
|
-
SDK
|
|
1164
|
+
The SDK supports the following environments:
|
|
1165
1165
|
|
|
1166
|
-
|
|
|
1166
|
+
| Environment | Description | BaseURL |
|
|
1167
1167
|
|------|------|---------|
|
|
1168
|
-
| `production` |
|
|
1169
|
-
| `staging` |
|
|
1170
|
-
| `development` |
|
|
1171
|
-
| `local` |
|
|
1168
|
+
| `production` | Production environment | `https://account-hub.seaverse.ai` |
|
|
1169
|
+
| `staging` | Staging environment | `https://api.staging.seaverse.dev` |
|
|
1170
|
+
| `development` | Development environment | `https://api.dev.seaverse.dev` |
|
|
1171
|
+
| `local` | Local environment | `http://localhost:3000` |
|
|
1172
1172
|
|
|
1173
|
-
|
|
1173
|
+
Auto-detection rules:
|
|
1174
1174
|
- `*.seaverse.com` → production
|
|
1175
1175
|
- `*.staging.seaverse.dev` → staging
|
|
1176
1176
|
- `*.dev.seaverse.dev` → development
|
|
1177
1177
|
- `localhost` → local
|
|
1178
1178
|
|
|
1179
|
-
## API
|
|
1179
|
+
## API Reference
|
|
1180
1180
|
|
|
1181
|
-
###
|
|
1181
|
+
### Authentication Related
|
|
1182
1182
|
|
|
1183
|
-
|
|
|
1183
|
+
| Method | Parameters | Return Value | Description |
|
|
1184
1184
|
|------|------|--------|------|
|
|
1185
|
-
| `register()` | `{ email, password, username?, invitation_code?, frontend_url? }` | `RegisterResponse` |
|
|
1186
|
-
| `login()` | `{ email, password, frontend_url? }` | `LoginResponse` |
|
|
1187
|
-
| `getCurrentUser()` | - | `User` |
|
|
1188
|
-
| `logout()` | - | `SuccessResponse` |
|
|
1189
|
-
| `verifyEmail()` | `verifyToken: string` | `EmailVerificationResponse` |
|
|
1190
|
-
| `bindInviteCode()` | `{ user_id, invite_code }` | `BindInviteCodeResponse` |
|
|
1191
|
-
| `forgotPassword()` | `{ email, frontend_url? }` | `SuccessResponse` |
|
|
1192
|
-
| `resetPassword()` | `{ token, new_password }` | `SuccessResponse` |
|
|
1193
|
-
| `setToken()` | `token: string` | `void` |
|
|
1194
|
-
| `isInIframe()` | - | `boolean` |
|
|
1195
|
-
| `getIframeToken()` | `{ timeout? }` | `Promise<string>` |
|
|
1196
|
-
| `getApiServiceToken()` | - | `ApiServiceTokenResponse` |
|
|
1197
|
-
|
|
1198
|
-
####
|
|
1199
|
-
|
|
1200
|
-
`register()`
|
|
1201
|
-
|
|
1202
|
-
|
|
1185
|
+
| `register()` | `{ email, password, username?, invitation_code?, frontend_url? }` | `RegisterResponse` | Register new user, frontend_url is frontend URL for email verification link, defaults to window.location.href |
|
|
1186
|
+
| `login()` | `{ email, password, frontend_url? }` | `LoginResponse` | User login, frontend_url used for sending verification email when email not verified, defaults to https://seaverse.ai/. ⚠️ If returns INVITE_CODE_REQUIRED with redirectUrl, will automatically redirect |
|
|
1187
|
+
| `getCurrentUser()` | - | `User` | Get current user |
|
|
1188
|
+
| `logout()` | - | `SuccessResponse` | Logout |
|
|
1189
|
+
| `verifyEmail()` | `verifyToken: string` | `EmailVerificationResponse` | Verify email and return auto-login token |
|
|
1190
|
+
| `bindInviteCode()` | `{ user_id, invite_code }` | `BindInviteCodeResponse` | Bind invitation code to activate temporary account and auto-login |
|
|
1191
|
+
| `forgotPassword()` | `{ email, frontend_url? }` | `SuccessResponse` | Forgot password, frontend_url defaults to window.location.href |
|
|
1192
|
+
| `resetPassword()` | `{ token, new_password }` | `SuccessResponse` | Reset password |
|
|
1193
|
+
| `setToken()` | `token: string` | `void` | Set authentication token (used after OAuth login) |
|
|
1194
|
+
| `isInIframe()` | - | `boolean` | Check if application is running in iframe (supports static method and instance method) |
|
|
1195
|
+
| `getIframeToken()` | `{ timeout? }` | `Promise<string>` | Get token from parent page (iframe scenario only), timeout defaults to 30000ms |
|
|
1196
|
+
| `getApiServiceToken()` | - | `ApiServiceTokenResponse` | Get API Token |
|
|
1197
|
+
|
|
1198
|
+
#### Registration Flow Description
|
|
1199
|
+
|
|
1200
|
+
The `register()` method supports three registration flows, SDK automatically handles based on backend response:
|
|
1201
|
+
|
|
1202
|
+
**Flow 1: Immediate Activation (Default)**
|
|
1203
1203
|
```typescript
|
|
1204
1204
|
const response = await client.register({ email, password });
|
|
1205
1205
|
// response.success === true
|
|
1206
|
-
// response.requiresEmailVerification === undefined (
|
|
1207
|
-
// response.requiresInvitationCode === undefined (
|
|
1208
|
-
// → SDK
|
|
1206
|
+
// response.requiresEmailVerification === undefined (or false)
|
|
1207
|
+
// response.requiresInvitationCode === undefined (or false)
|
|
1208
|
+
// → SDK automatically calls login() and triggers onSignupSuccess callback
|
|
1209
1209
|
```
|
|
1210
1210
|
|
|
1211
|
-
|
|
1211
|
+
**Flow 2: Requires Email Verification**
|
|
1212
1212
|
```typescript
|
|
1213
1213
|
const response = await client.register({ email, password });
|
|
1214
1214
|
// response.success === true
|
|
1215
1215
|
// response.requiresEmailVerification === true
|
|
1216
|
-
// → SDK
|
|
1217
|
-
// →
|
|
1216
|
+
// → SDK shows Toast prompting user to check email
|
|
1217
|
+
// → Does not auto-call login(), user needs to click verification link in email
|
|
1218
1218
|
```
|
|
1219
1219
|
|
|
1220
|
-
|
|
1220
|
+
**Flow 3: Requires Invitation Code Activation**
|
|
1221
1221
|
```typescript
|
|
1222
1222
|
const response = await client.register({ email, password });
|
|
1223
1223
|
// response.success === true
|
|
1224
1224
|
// response.requiresInvitationCode === true
|
|
1225
1225
|
// response.tempUserId === "temp_xxx"
|
|
1226
|
-
// → SDK
|
|
1227
|
-
// →
|
|
1226
|
+
// → SDK shows invitation code input form
|
|
1227
|
+
// → Does not auto-call login(), user needs to input invitation code
|
|
1228
1228
|
```
|
|
1229
1229
|
|
|
1230
|
-
|
|
1231
|
-
- AuthModal
|
|
1232
|
-
-
|
|
1233
|
-
-
|
|
1230
|
+
**Important Note**:
|
|
1231
|
+
- AuthModal automatically handles these three flows, no manual judgment needed
|
|
1232
|
+
- Only when no verification or activation is needed, will `login()` be automatically called
|
|
1233
|
+
- This avoids unnecessary login requests when email is not verified or account is not activated
|
|
1234
1234
|
|
|
1235
|
-
### OAuth
|
|
1235
|
+
### OAuth Related
|
|
1236
1236
|
|
|
1237
|
-
|
|
|
1237
|
+
| Method | Parameters | Return Value | Description |
|
|
1238
1238
|
|------|------|--------|------|
|
|
1239
|
-
| `googleAuthorize()` | `{ return_url? }` | `OAuthAuthorizeResponse` | Google OAuth
|
|
1240
|
-
| `discordAuthorize()` | `{ return_url? }` | `OAuthAuthorizeResponse` | Discord OAuth
|
|
1241
|
-
| `githubAuthorize()` | `{ return_url? }` | `OAuthAuthorizeResponse` | GitHub OAuth
|
|
1242
|
-
| `unlinkGoogle()` | - | `SuccessResponse` |
|
|
1243
|
-
| `unlinkDiscord()` | - | `SuccessResponse` |
|
|
1244
|
-
| `unlinkGithub()` | - | `SuccessResponse` |
|
|
1239
|
+
| `googleAuthorize()` | `{ return_url? }` | `OAuthAuthorizeResponse` | Google OAuth authorization URL |
|
|
1240
|
+
| `discordAuthorize()` | `{ return_url? }` | `OAuthAuthorizeResponse` | Discord OAuth authorization URL |
|
|
1241
|
+
| `githubAuthorize()` | `{ return_url? }` | `OAuthAuthorizeResponse` | GitHub OAuth authorization URL |
|
|
1242
|
+
| `unlinkGoogle()` | - | `SuccessResponse` | Unlink Google |
|
|
1243
|
+
| `unlinkDiscord()` | - | `SuccessResponse` | Unlink Discord |
|
|
1244
|
+
| `unlinkGithub()` | - | `SuccessResponse` | Unlink GitHub |
|
|
1245
1245
|
|
|
1246
|
-
###
|
|
1246
|
+
### Container Management
|
|
1247
1247
|
|
|
1248
|
-
|
|
|
1248
|
+
| Method | Parameters | Return Value | Description |
|
|
1249
1249
|
|------|------|--------|------|
|
|
1250
|
-
| `listContainers()` | - | `ContainerListResponse` |
|
|
1251
|
-
| `registerContainer()` | `{ containerId, metadata }` | `SuccessResponse` |
|
|
1252
|
-
| `getContainer()` | `{ containerId }` | `Container` |
|
|
1253
|
-
| `unregisterContainer()` | `{ containerId }` | `SuccessResponse` |
|
|
1254
|
-
| `containerHeartbeat()` | `{ containerId, status }` | `SuccessResponse` |
|
|
1255
|
-
| `getContainerStats()` | - | `ContainerStatsResponse` |
|
|
1250
|
+
| `listContainers()` | - | `ContainerListResponse` | List containers |
|
|
1251
|
+
| `registerContainer()` | `{ containerId, metadata }` | `SuccessResponse` | Register container |
|
|
1252
|
+
| `getContainer()` | `{ containerId }` | `Container` | Get container info |
|
|
1253
|
+
| `unregisterContainer()` | `{ containerId }` | `SuccessResponse` | Unregister container |
|
|
1254
|
+
| `containerHeartbeat()` | `{ containerId, status }` | `SuccessResponse` | Container heartbeat |
|
|
1255
|
+
| `getContainerStats()` | - | `ContainerStatsResponse` | Container stats |
|
|
1256
1256
|
|
|
1257
|
-
###
|
|
1257
|
+
### Skill Marketplace
|
|
1258
1258
|
|
|
1259
|
-
|
|
|
1259
|
+
| Method | Parameters | Return Value | Description |
|
|
1260
1260
|
|------|------|--------|------|
|
|
1261
|
-
| `listMarketplaceSkills()` | `{ category?, page?, pageSize? }` | `MarketplaceSkillsListResponse` |
|
|
1262
|
-
| `getMarketplaceSkill()` | `{ skillId }` | `MarketplaceSkill` |
|
|
1263
|
-
| `installSkill()` | `{ skillId }` | `SuccessResponse` |
|
|
1264
|
-
| `listUserSkills()` | - | `UserInstalledSkillsListResponse` |
|
|
1265
|
-
| `uninstallSkill()` | `{ skillId }` | `SuccessResponse` |
|
|
1261
|
+
| `listMarketplaceSkills()` | `{ category?, page?, pageSize? }` | `MarketplaceSkillsListResponse` | List marketplace skills |
|
|
1262
|
+
| `getMarketplaceSkill()` | `{ skillId }` | `MarketplaceSkill` | Get skill details |
|
|
1263
|
+
| `installSkill()` | `{ skillId }` | `SuccessResponse` | Install skill |
|
|
1264
|
+
| `listUserSkills()` | - | `UserInstalledSkillsListResponse` | List installed skills |
|
|
1265
|
+
| `uninstallSkill()` | `{ skillId }` | `SuccessResponse` | Uninstall skill |
|
|
1266
1266
|
|
|
1267
|
-
###
|
|
1267
|
+
### Invitation Code Management
|
|
1268
1268
|
|
|
1269
|
-
|
|
|
1269
|
+
| Method | Parameters | Return Value | Description |
|
|
1270
1270
|
|------|------|--------|------|
|
|
1271
|
-
| `applyInvite()` | `{ email, reason }` | `ApplyInviteResponse` |
|
|
1272
|
-
| `listInvites()` | `{ page?, page_size?, status? }` | `ListInvitesResponse` |
|
|
1273
|
-
| `getInviteStats()` | - | `InviteStatsResponse` |
|
|
1274
|
-
| `getInvite()` | `inviteId: string` | `InviteCodeDetailResponse` |
|
|
1275
|
-
| `getInviteUsages()` | `inviteId: string, { page?, page_size? }` | `ListInviteUsagesResponse` |
|
|
1271
|
+
| `applyInvite()` | `{ email, reason }` | `ApplyInviteResponse` | Apply for invitation code (reason needs 10-500 characters)|
|
|
1272
|
+
| `listInvites()` | `{ page?, page_size?, status? }` | `ListInvitesResponse` | List my invitation codes |
|
|
1273
|
+
| `getInviteStats()` | - | `InviteStatsResponse` | Get invitation code stats |
|
|
1274
|
+
| `getInvite()` | `inviteId: string` | `InviteCodeDetailResponse` | Get invitation code details |
|
|
1275
|
+
| `getInviteUsages()` | `inviteId: string, { page?, page_size? }` | `ListInviteUsagesResponse` | Get invitation code usage records |
|
|
1276
1276
|
|
|
1277
|
-
###
|
|
1277
|
+
### Others
|
|
1278
1278
|
|
|
1279
|
-
|
|
|
1279
|
+
| Method | Parameters | Return Value | Description |
|
|
1280
1280
|
|------|------|--------|------|
|
|
1281
|
-
| `getHealth()` | - | `HealthResponse` |
|
|
1282
|
-
| `getConversationStatus()` | `{ conversationId }` | `ConversationStatusResponse` |
|
|
1283
|
-
| `getSpeechToken()` | - | `SpeechTokenResponse` |
|
|
1281
|
+
| `getHealth()` | - | `HealthResponse` | Health check |
|
|
1282
|
+
| `getConversationStatus()` | `{ conversationId }` | `ConversationStatusResponse` | Get conversation status |
|
|
1283
|
+
| `getSpeechToken()` | - | `SpeechTokenResponse` | Get speech token |
|
|
1284
1284
|
|
|
1285
|
-
##
|
|
1285
|
+
## Response Format Compatibility
|
|
1286
1286
|
|
|
1287
|
-
###
|
|
1287
|
+
### Auto Response Unwrapping
|
|
1288
1288
|
|
|
1289
|
-
|
|
1289
|
+
Starting from v0.2.5, SDK automatically supports two API response formats, no manual handling needed:
|
|
1290
1290
|
|
|
1291
|
-
|
|
1291
|
+
**Format 1: Wrapped Format** (Recommended)
|
|
1292
1292
|
```json
|
|
1293
1293
|
{
|
|
1294
1294
|
"data": {
|
|
@@ -1299,7 +1299,7 @@ const response = await client.register({ email, password });
|
|
|
1299
1299
|
}
|
|
1300
1300
|
```
|
|
1301
1301
|
|
|
1302
|
-
|
|
1302
|
+
**Format 2: Flat Format** (Backward Compatible)
|
|
1303
1303
|
```json
|
|
1304
1304
|
{
|
|
1305
1305
|
"token": "eyJhbGc...",
|
|
@@ -1307,101 +1307,101 @@ const response = await client.register({ email, password });
|
|
|
1307
1307
|
}
|
|
1308
1308
|
```
|
|
1309
1309
|
|
|
1310
|
-
SDK
|
|
1310
|
+
SDK automatically detects response format and extracts correct data:
|
|
1311
1311
|
|
|
1312
1312
|
```typescript
|
|
1313
|
-
//
|
|
1313
|
+
// Regardless of which format backend returns, the following code works
|
|
1314
1314
|
const loginResult = await client.login({
|
|
1315
1315
|
email: 'user@example.com',
|
|
1316
1316
|
password: 'password123',
|
|
1317
1317
|
});
|
|
1318
1318
|
|
|
1319
|
-
console.log(loginResult.token); // ✅
|
|
1320
|
-
console.log(loginResult.user); // ✅
|
|
1319
|
+
console.log(loginResult.token); // ✅ Always gets token correctly
|
|
1320
|
+
console.log(loginResult.user); // ✅ Always gets user correctly
|
|
1321
1321
|
```
|
|
1322
1322
|
|
|
1323
|
-
###
|
|
1323
|
+
### Affected Methods
|
|
1324
1324
|
|
|
1325
|
-
|
|
1325
|
+
The following methods have implemented auto response unwrapping:
|
|
1326
1326
|
|
|
1327
|
-
- ✅ `login()` -
|
|
1328
|
-
- ✅ `register()` -
|
|
1329
|
-
- ✅ `getCurrentUser()` -
|
|
1327
|
+
- ✅ `login()` - Login API
|
|
1328
|
+
- ✅ `register()` - Registration API
|
|
1329
|
+
- ✅ `getCurrentUser()` - Get current user
|
|
1330
1330
|
|
|
1331
|
-
###
|
|
1331
|
+
### Implementation Principle
|
|
1332
1332
|
|
|
1333
|
-
SDK
|
|
1333
|
+
SDK internally handles response format through the following logic:
|
|
1334
1334
|
|
|
1335
1335
|
```typescript
|
|
1336
1336
|
const response = await httpClient.request(config);
|
|
1337
1337
|
const responseData = response.data;
|
|
1338
1338
|
|
|
1339
|
-
//
|
|
1339
|
+
// Detect and unwrap
|
|
1340
1340
|
if (responseData.data && typeof responseData.data === 'object') {
|
|
1341
|
-
//
|
|
1341
|
+
// Wrapped format: extract data field
|
|
1342
1342
|
return responseData.data;
|
|
1343
1343
|
}
|
|
1344
|
-
//
|
|
1344
|
+
// Flat format: return directly
|
|
1345
1345
|
return responseData;
|
|
1346
1346
|
```
|
|
1347
1347
|
|
|
1348
|
-
|
|
1349
|
-
- 🔄
|
|
1350
|
-
- 🔧
|
|
1351
|
-
- ✅
|
|
1348
|
+
This means:
|
|
1349
|
+
- 🔄 **Backend format changes don't require frontend modifications** - Backend can freely adjust response format
|
|
1350
|
+
- 🔧 **Progressive migration** - Different APIs can be migrated to new format gradually
|
|
1351
|
+
- ✅ **Backward compatible** - Old code continues to work without modifications
|
|
1352
1352
|
|
|
1353
|
-
##
|
|
1353
|
+
## Error Handling
|
|
1354
1354
|
|
|
1355
|
-
###
|
|
1355
|
+
### Error Response Format
|
|
1356
1356
|
|
|
1357
|
-
|
|
1357
|
+
All API errors follow a unified response format:
|
|
1358
1358
|
|
|
1359
1359
|
```typescript
|
|
1360
1360
|
interface ApiError {
|
|
1361
|
-
success: false; //
|
|
1362
|
-
error: string; //
|
|
1363
|
-
code?: string; //
|
|
1364
|
-
details?: any; //
|
|
1361
|
+
success: false; // Always false on error
|
|
1362
|
+
error: string; // Human-readable error message
|
|
1363
|
+
code?: string; // Machine-readable error code
|
|
1364
|
+
details?: any; // Additional error details
|
|
1365
1365
|
}
|
|
1366
1366
|
```
|
|
1367
1367
|
|
|
1368
|
-
###
|
|
1368
|
+
### Error Codes
|
|
1369
1369
|
|
|
1370
|
-
SDK
|
|
1370
|
+
SDK provides standard error code enums:
|
|
1371
1371
|
|
|
1372
1372
|
```typescript
|
|
1373
1373
|
import { ErrorCode } from '@seaverse/auth-sdk';
|
|
1374
1374
|
|
|
1375
|
-
//
|
|
1376
|
-
ErrorCode.ACCOUNT_EXISTS //
|
|
1377
|
-
ErrorCode.ACCOUNT_NOT_FOUND //
|
|
1378
|
-
ErrorCode.ACCOUNT_SUSPENDED //
|
|
1379
|
-
ErrorCode.INVALID_CREDENTIALS //
|
|
1380
|
-
ErrorCode.EMAIL_NOT_VERIFIED //
|
|
1381
|
-
|
|
1382
|
-
//
|
|
1383
|
-
ErrorCode.INVALID_EMAIL //
|
|
1384
|
-
ErrorCode.INVALID_PASSWORD //
|
|
1385
|
-
ErrorCode.PASSWORD_TOO_WEAK //
|
|
1386
|
-
|
|
1387
|
-
//
|
|
1388
|
-
ErrorCode.INVALID_INVITATION_CODE //
|
|
1389
|
-
ErrorCode.INVITATION_REQUIRED //
|
|
1390
|
-
ErrorCode.INVITE_CODE_REQUIRED //
|
|
1391
|
-
|
|
1392
|
-
// Token
|
|
1393
|
-
ErrorCode.INVALID_TOKEN //
|
|
1394
|
-
ErrorCode.TOKEN_EXPIRED // Token
|
|
1395
|
-
|
|
1396
|
-
//
|
|
1397
|
-
ErrorCode.INTERNAL_ERROR //
|
|
1375
|
+
// Account-related errors
|
|
1376
|
+
ErrorCode.ACCOUNT_EXISTS // Account already exists
|
|
1377
|
+
ErrorCode.ACCOUNT_NOT_FOUND // Account not found
|
|
1378
|
+
ErrorCode.ACCOUNT_SUSPENDED // Account suspended
|
|
1379
|
+
ErrorCode.INVALID_CREDENTIALS // Invalid login credentials
|
|
1380
|
+
ErrorCode.EMAIL_NOT_VERIFIED // Email not verified
|
|
1381
|
+
|
|
1382
|
+
// Validation-related errors
|
|
1383
|
+
ErrorCode.INVALID_EMAIL // Invalid email address
|
|
1384
|
+
ErrorCode.INVALID_PASSWORD // Invalid password
|
|
1385
|
+
ErrorCode.PASSWORD_TOO_WEAK // Password too weak
|
|
1386
|
+
|
|
1387
|
+
// Invitation code-related errors
|
|
1388
|
+
ErrorCode.INVALID_INVITATION_CODE // Invalid invitation code
|
|
1389
|
+
ErrorCode.INVITATION_REQUIRED // Invitation code required
|
|
1390
|
+
ErrorCode.INVITE_CODE_REQUIRED // Invitation code required (will auto-redirect to redirectUrl)
|
|
1391
|
+
|
|
1392
|
+
// Token-related errors
|
|
1393
|
+
ErrorCode.INVALID_TOKEN // Invalid token
|
|
1394
|
+
ErrorCode.TOKEN_EXPIRED // Token expired
|
|
1395
|
+
|
|
1396
|
+
// Internal errors
|
|
1397
|
+
ErrorCode.INTERNAL_ERROR // Server internal error
|
|
1398
1398
|
```
|
|
1399
1399
|
|
|
1400
|
-
###
|
|
1400
|
+
### Error Handling Best Practices
|
|
1401
1401
|
|
|
1402
|
-
#### 1.
|
|
1402
|
+
#### 1. Handle Duplicate Users During Registration
|
|
1403
1403
|
|
|
1404
|
-
|
|
1404
|
+
Since backend returns 200 OK when account exists (success response), frontend won't throw exception, but includes error info in response body.
|
|
1405
1405
|
|
|
1406
1406
|
```typescript
|
|
1407
1407
|
import { ErrorCode } from '@seaverse/auth-sdk';
|
|
@@ -1411,27 +1411,27 @@ const result = await client.register({
|
|
|
1411
1411
|
password: 'password123',
|
|
1412
1412
|
});
|
|
1413
1413
|
|
|
1414
|
-
//
|
|
1414
|
+
// Check success field in response
|
|
1415
1415
|
if (result.success) {
|
|
1416
|
-
console.log('
|
|
1417
|
-
//
|
|
1416
|
+
console.log('Registration successful:', result);
|
|
1417
|
+
// Proceed with follow-up actions, such as auto-login
|
|
1418
1418
|
} else if (result.code === ErrorCode.ACCOUNT_EXISTS) {
|
|
1419
|
-
//
|
|
1419
|
+
// Account exists, prompt user
|
|
1420
1420
|
const { email, app_id } = result.details;
|
|
1421
|
-
console.log(
|
|
1421
|
+
console.log(`Account ${email} already exists in application ${app_id}`);
|
|
1422
1422
|
|
|
1423
|
-
//
|
|
1423
|
+
// Show friendly prompt
|
|
1424
1424
|
alert('This email is already registered. Please login instead.');
|
|
1425
1425
|
|
|
1426
|
-
//
|
|
1426
|
+
// Or guide user to login
|
|
1427
1427
|
showLoginModal();
|
|
1428
1428
|
} else {
|
|
1429
|
-
//
|
|
1430
|
-
console.error('
|
|
1429
|
+
// Handle other errors
|
|
1430
|
+
console.error('Registration failed:', result.error);
|
|
1431
1431
|
}
|
|
1432
1432
|
```
|
|
1433
1433
|
|
|
1434
|
-
#### 2.
|
|
1434
|
+
#### 2. Handle Various Errors During Login
|
|
1435
1435
|
|
|
1436
1436
|
```typescript
|
|
1437
1437
|
try {
|
|
@@ -1439,28 +1439,28 @@ try {
|
|
|
1439
1439
|
email: 'user@example.com',
|
|
1440
1440
|
password: 'wrong-password',
|
|
1441
1441
|
});
|
|
1442
|
-
console.log('
|
|
1442
|
+
console.log('Login successful:', result);
|
|
1443
1443
|
} catch (error) {
|
|
1444
1444
|
const errorCode = error.response?.data?.code;
|
|
1445
1445
|
|
|
1446
1446
|
switch (errorCode) {
|
|
1447
1447
|
case ErrorCode.INVALID_CREDENTIALS:
|
|
1448
|
-
showError('
|
|
1448
|
+
showError('Incorrect username or password');
|
|
1449
1449
|
break;
|
|
1450
1450
|
case ErrorCode.EMAIL_NOT_VERIFIED:
|
|
1451
|
-
showError('
|
|
1451
|
+
showError('Please verify your email first');
|
|
1452
1452
|
showResendVerificationButton();
|
|
1453
1453
|
break;
|
|
1454
1454
|
case ErrorCode.ACCOUNT_SUSPENDED:
|
|
1455
|
-
showError('
|
|
1455
|
+
showError('Your account has been suspended, please contact administrator');
|
|
1456
1456
|
break;
|
|
1457
1457
|
default:
|
|
1458
|
-
showError('
|
|
1458
|
+
showError('Login failed, please try again later');
|
|
1459
1459
|
}
|
|
1460
1460
|
}
|
|
1461
1461
|
```
|
|
1462
1462
|
|
|
1463
|
-
#### 3.
|
|
1463
|
+
#### 3. Generic Error Handling Function
|
|
1464
1464
|
|
|
1465
1465
|
```typescript
|
|
1466
1466
|
import { ErrorCode } from '@seaverse/auth-sdk';
|
|
@@ -1469,44 +1469,44 @@ function handleApiError(error: any) {
|
|
|
1469
1469
|
const apiError = error.response?.data;
|
|
1470
1470
|
|
|
1471
1471
|
if (!apiError) {
|
|
1472
|
-
//
|
|
1472
|
+
// Network error or other unknown error
|
|
1473
1473
|
return {
|
|
1474
|
-
title: '
|
|
1475
|
-
message: '
|
|
1474
|
+
title: 'Network Error',
|
|
1475
|
+
message: 'Please check your network connection',
|
|
1476
1476
|
};
|
|
1477
1477
|
}
|
|
1478
1478
|
|
|
1479
|
-
//
|
|
1479
|
+
// Return user-friendly messages based on error code
|
|
1480
1480
|
const errorMessages: Record<string, { title: string; message: string }> = {
|
|
1481
1481
|
[ErrorCode.ACCOUNT_EXISTS]: {
|
|
1482
|
-
title: '
|
|
1483
|
-
message: '
|
|
1482
|
+
title: 'Account Exists',
|
|
1483
|
+
message: 'This email is already registered, please login directly',
|
|
1484
1484
|
},
|
|
1485
1485
|
[ErrorCode.INVALID_CREDENTIALS]: {
|
|
1486
|
-
title: '
|
|
1487
|
-
message: '
|
|
1486
|
+
title: 'Login Failed',
|
|
1487
|
+
message: 'Incorrect username or password',
|
|
1488
1488
|
},
|
|
1489
1489
|
[ErrorCode.EMAIL_NOT_VERIFIED]: {
|
|
1490
|
-
title: '
|
|
1491
|
-
message: '
|
|
1490
|
+
title: 'Email Not Verified',
|
|
1491
|
+
message: 'Please verify your email address first',
|
|
1492
1492
|
},
|
|
1493
1493
|
[ErrorCode.INVALID_INVITATION_CODE]: {
|
|
1494
|
-
title: '
|
|
1495
|
-
message: '
|
|
1494
|
+
title: 'Invalid Invitation Code',
|
|
1495
|
+
message: 'Please check if your invitation code is correct',
|
|
1496
1496
|
},
|
|
1497
1497
|
[ErrorCode.TOKEN_EXPIRED]: {
|
|
1498
|
-
title: '
|
|
1499
|
-
message: '
|
|
1498
|
+
title: 'Login Expired',
|
|
1499
|
+
message: 'Please login again',
|
|
1500
1500
|
},
|
|
1501
1501
|
};
|
|
1502
1502
|
|
|
1503
1503
|
return errorMessages[apiError.code] || {
|
|
1504
|
-
title: '
|
|
1505
|
-
message: apiError.error || '
|
|
1504
|
+
title: 'Operation Failed',
|
|
1505
|
+
message: apiError.error || 'Unknown error occurred',
|
|
1506
1506
|
};
|
|
1507
1507
|
}
|
|
1508
1508
|
|
|
1509
|
-
//
|
|
1509
|
+
// Usage example
|
|
1510
1510
|
try {
|
|
1511
1511
|
await client.register({ email, password });
|
|
1512
1512
|
} catch (error) {
|
|
@@ -1515,7 +1515,7 @@ try {
|
|
|
1515
1515
|
}
|
|
1516
1516
|
```
|
|
1517
1517
|
|
|
1518
|
-
#### 4. TypeScript
|
|
1518
|
+
#### 4. TypeScript Type-Safe Error Handling
|
|
1519
1519
|
|
|
1520
1520
|
```typescript
|
|
1521
1521
|
import { ErrorCode, models } from '@seaverse/auth-sdk';
|
|
@@ -1555,164 +1555,164 @@ if (registerResult.success) {
|
|
|
1555
1555
|
}
|
|
1556
1556
|
```
|
|
1557
1557
|
|
|
1558
|
-
### HTTP
|
|
1558
|
+
### HTTP Status Code Reference
|
|
1559
1559
|
|
|
1560
|
-
| HTTP
|
|
1560
|
+
| HTTP Status Code | Error Code Example | Description |
|
|
1561
1561
|
|-----------|----------|------|
|
|
1562
|
-
| 200 OK | `ACCOUNT_EXISTS` |
|
|
1563
|
-
| 400 Bad Request | `INVALID_EMAIL`, `PASSWORD_TOO_WEAK` |
|
|
1564
|
-
| 401 Unauthorized | `INVALID_CREDENTIALS`, `TOKEN_EXPIRED` |
|
|
1565
|
-
| 403 Forbidden | `EMAIL_NOT_VERIFIED`, `ACCOUNT_SUSPENDED` |
|
|
1566
|
-
| 500 Internal Server Error | `INTERNAL_ERROR` |
|
|
1562
|
+
| 200 OK | `ACCOUNT_EXISTS` | Account exists (business error but returns success response) |
|
|
1563
|
+
| 400 Bad Request | `INVALID_EMAIL`, `PASSWORD_TOO_WEAK` | Invalid request parameters |
|
|
1564
|
+
| 401 Unauthorized | `INVALID_CREDENTIALS`, `TOKEN_EXPIRED` | Authentication failed |
|
|
1565
|
+
| 403 Forbidden | `EMAIL_NOT_VERIFIED`, `ACCOUNT_SUSPENDED` | Insufficient permissions |
|
|
1566
|
+
| 500 Internal Server Error | `INTERNAL_ERROR` | Server internal error |
|
|
1567
1567
|
|
|
1568
|
-
|
|
1568
|
+
**Note**: For registration API, account exists case returns 200 OK, but in response body `success: false` and `code: "ACCOUNT_EXISTS"`. This design makes it easier for frontend to handle this common business scenario.
|
|
1569
1569
|
|
|
1570
|
-
##
|
|
1570
|
+
## Type Definitions
|
|
1571
1571
|
|
|
1572
1572
|
```typescript
|
|
1573
|
-
//
|
|
1573
|
+
// User info
|
|
1574
1574
|
interface User {
|
|
1575
1575
|
id?: string;
|
|
1576
|
-
app_id?: string | null; //
|
|
1576
|
+
app_id?: string | null; // Application ID (multi-tenant support)
|
|
1577
1577
|
email?: string;
|
|
1578
1578
|
username?: string;
|
|
1579
|
-
created_at?: number; // Unix
|
|
1580
|
-
email_verified?: boolean; //
|
|
1581
|
-
google_id?: string | null; // Google
|
|
1582
|
-
discord_id?: string | null; // Discord
|
|
1583
|
-
github_id?: string | null; // GitHub
|
|
1584
|
-
|
|
1585
|
-
//
|
|
1586
|
-
createdAt?: number; // @deprecated
|
|
1587
|
-
emailVerified?: boolean; // @deprecated
|
|
1588
|
-
googleId?: string; // @deprecated
|
|
1589
|
-
discordId?: string; // @deprecated
|
|
1590
|
-
githubId?: string; // @deprecated
|
|
1579
|
+
created_at?: number; // Unix timestamp
|
|
1580
|
+
email_verified?: boolean; // Email verification status
|
|
1581
|
+
google_id?: string | null; // Google account ID
|
|
1582
|
+
discord_id?: string | null; // Discord account ID
|
|
1583
|
+
github_id?: string | null; // GitHub account ID
|
|
1584
|
+
|
|
1585
|
+
// Deprecated fields (backward compatible)
|
|
1586
|
+
createdAt?: number; // @deprecated Use created_at
|
|
1587
|
+
emailVerified?: boolean; // @deprecated Use email_verified
|
|
1588
|
+
googleId?: string; // @deprecated Use google_id
|
|
1589
|
+
discordId?: string; // @deprecated Use discord_id
|
|
1590
|
+
githubId?: string; // @deprecated Use github_id
|
|
1591
1591
|
}
|
|
1592
1592
|
|
|
1593
|
-
//
|
|
1593
|
+
// Login response
|
|
1594
1594
|
interface LoginResponse {
|
|
1595
1595
|
token: string;
|
|
1596
1596
|
user: User;
|
|
1597
1597
|
}
|
|
1598
1598
|
|
|
1599
|
-
//
|
|
1599
|
+
// Registration response
|
|
1600
1600
|
interface RegisterResponse {
|
|
1601
1601
|
success: boolean;
|
|
1602
1602
|
message?: string;
|
|
1603
1603
|
userId?: string;
|
|
1604
|
-
//
|
|
1605
|
-
requiresEmailVerification?: boolean; //
|
|
1606
|
-
//
|
|
1607
|
-
requiresInvitationCode?: boolean; //
|
|
1608
|
-
tempUserId?: string; //
|
|
1609
|
-
//
|
|
1604
|
+
// Email verification related
|
|
1605
|
+
requiresEmailVerification?: boolean; // Whether email verification is required
|
|
1606
|
+
// Invitation code activation related
|
|
1607
|
+
requiresInvitationCode?: boolean; // Whether invitation code activation is required
|
|
1608
|
+
tempUserId?: string; // Temporary user ID (needs activation)
|
|
1609
|
+
// Error info
|
|
1610
1610
|
error?: string;
|
|
1611
|
-
code?: string; //
|
|
1611
|
+
code?: string; // E.g. 'ACCOUNT_EXISTS'
|
|
1612
1612
|
details?: Record<string, any>;
|
|
1613
1613
|
}
|
|
1614
1614
|
|
|
1615
|
-
// AuthModal
|
|
1615
|
+
// AuthModal options
|
|
1616
1616
|
interface AuthModalOptions {
|
|
1617
1617
|
client: SeaVerseBackendAPIClient;
|
|
1618
1618
|
theme?: 'dark' | 'light';
|
|
1619
1619
|
onLoginSuccess?: (token: string, user: any) => void;
|
|
1620
1620
|
onSignupSuccess?: (token: string, user: any) => void;
|
|
1621
|
-
onInviteCodeRequired?: (userId: string, email: string) => void; //
|
|
1622
|
-
onApplyInviteSuccess?: (applicationId: string, email: string) => void; //
|
|
1621
|
+
onInviteCodeRequired?: (userId: string, email: string) => void; // Callback when invitation code activation is needed
|
|
1622
|
+
onApplyInviteSuccess?: (applicationId: string, email: string) => void; // Callback when invitation code application succeeds
|
|
1623
1623
|
onError?: (error: Error) => void;
|
|
1624
|
-
returnUrl?: string; // OAuth
|
|
1624
|
+
returnUrl?: string; // URL to return after OAuth login, optional, defaults to window.location.href
|
|
1625
1625
|
enableOAuth?: {
|
|
1626
|
-
google?: boolean; //
|
|
1627
|
-
discord?: boolean; //
|
|
1628
|
-
github?: boolean; //
|
|
1626
|
+
google?: boolean; // Enable Google login
|
|
1627
|
+
discord?: boolean; // Enable Discord login
|
|
1628
|
+
github?: boolean; // Enable GitHub login
|
|
1629
1629
|
};
|
|
1630
1630
|
}
|
|
1631
1631
|
```
|
|
1632
1632
|
|
|
1633
|
-
##
|
|
1633
|
+
## Complete Examples
|
|
1634
1634
|
|
|
1635
|
-
|
|
1635
|
+
Check [examples/auth-sdk-demo](../../examples/auth-sdk-demo) directory for complete runnable examples.
|
|
1636
1636
|
|
|
1637
1637
|
```bash
|
|
1638
|
-
#
|
|
1638
|
+
# Run example
|
|
1639
1639
|
cd examples/auth-sdk-demo
|
|
1640
1640
|
pnpm install
|
|
1641
1641
|
pnpm dev
|
|
1642
1642
|
```
|
|
1643
1643
|
|
|
1644
|
-
##
|
|
1644
|
+
## Migration from Old Versions
|
|
1645
1645
|
|
|
1646
|
-
### v0.1.x → v0.2.0
|
|
1646
|
+
### v0.1.x → v0.2.0 Upgrade Guide
|
|
1647
1647
|
|
|
1648
|
-
#### 1.
|
|
1649
|
-
|
|
1648
|
+
#### 1. Backward Compatibility
|
|
1649
|
+
Good news! v0.2.0 is fully backward compatible, existing code **works without modifications**.
|
|
1650
1650
|
|
|
1651
|
-
#### 2.
|
|
1651
|
+
#### 2. Recommended Migration Steps
|
|
1652
1652
|
|
|
1653
|
-
|
|
1653
|
+
While not required, we recommend gradually migrating to new field naming conventions:
|
|
1654
1654
|
|
|
1655
1655
|
```typescript
|
|
1656
|
-
//
|
|
1656
|
+
// Old code (still works)
|
|
1657
1657
|
const user = await client.getCurrentUser();
|
|
1658
|
-
console.log(user.emailVerified); // ⚠️
|
|
1659
|
-
console.log(user.createdAt); // ⚠️
|
|
1658
|
+
console.log(user.emailVerified); // ⚠️ Deprecated but works
|
|
1659
|
+
console.log(user.createdAt); // ⚠️ Deprecated but works
|
|
1660
1660
|
|
|
1661
|
-
//
|
|
1661
|
+
// New code (recommended)
|
|
1662
1662
|
const user = await client.getCurrentUser();
|
|
1663
|
-
console.log(user.email_verified); // ✅
|
|
1664
|
-
console.log(user.created_at); // ✅
|
|
1665
|
-
console.log(user.app_id); // ✅
|
|
1663
|
+
console.log(user.email_verified); // ✅ Recommended
|
|
1664
|
+
console.log(user.created_at); // ✅ Recommended
|
|
1665
|
+
console.log(user.app_id); // ✅ New field: multi-tenant support
|
|
1666
1666
|
```
|
|
1667
1667
|
|
|
1668
|
-
#### 3.
|
|
1668
|
+
#### 3. Registration API Update
|
|
1669
1669
|
|
|
1670
1670
|
```typescript
|
|
1671
|
-
//
|
|
1671
|
+
// Old code (still works)
|
|
1672
1672
|
await client.register({
|
|
1673
1673
|
email: 'user@example.com',
|
|
1674
1674
|
password: 'password123',
|
|
1675
|
-
invitationCode: 'INVITE123', // ⚠️
|
|
1675
|
+
invitationCode: 'INVITE123', // ⚠️ Deprecated but works
|
|
1676
1676
|
});
|
|
1677
1677
|
|
|
1678
|
-
//
|
|
1678
|
+
// New code (recommended)
|
|
1679
1679
|
await client.register({
|
|
1680
1680
|
email: 'user@example.com',
|
|
1681
1681
|
password: 'password123',
|
|
1682
|
-
username: 'myusername', // ✨
|
|
1683
|
-
invitation_code: 'INVITE123', // ✅
|
|
1682
|
+
username: 'myusername', // ✨ New feature
|
|
1683
|
+
invitation_code: 'INVITE123', // ✅ Recommended
|
|
1684
1684
|
});
|
|
1685
1685
|
```
|
|
1686
1686
|
|
|
1687
|
-
#### 4.
|
|
1687
|
+
#### 4. Password Reset API Update
|
|
1688
1688
|
|
|
1689
1689
|
```typescript
|
|
1690
|
-
//
|
|
1690
|
+
// Old code (needs update)
|
|
1691
1691
|
await client.resetPassword({
|
|
1692
1692
|
token: 'reset-token',
|
|
1693
|
-
newPassword: 'NewPass123', // ⚠️
|
|
1693
|
+
newPassword: 'NewPass123', // ⚠️ Deprecated
|
|
1694
1694
|
});
|
|
1695
1695
|
|
|
1696
|
-
//
|
|
1696
|
+
// New code (recommended)
|
|
1697
1697
|
await client.resetPassword({
|
|
1698
1698
|
token: 'reset-token',
|
|
1699
|
-
new_password: 'NewPass123', // ✅
|
|
1699
|
+
new_password: 'NewPass123', // ✅ Recommended
|
|
1700
1700
|
});
|
|
1701
1701
|
```
|
|
1702
1702
|
|
|
1703
|
-
#### 5. TypeScript
|
|
1703
|
+
#### 5. TypeScript Type Hints
|
|
1704
1704
|
|
|
1705
|
-
|
|
1705
|
+
If you use TypeScript, compiler will automatically hint deprecated fields:
|
|
1706
1706
|
|
|
1707
1707
|
```typescript
|
|
1708
1708
|
const user = await client.getCurrentUser();
|
|
1709
|
-
user.emailVerified; // TypeScript
|
|
1710
|
-
user.email_verified; // ✅
|
|
1709
|
+
user.emailVerified; // TypeScript will show strikethrough and deprecation warning
|
|
1710
|
+
user.email_verified; // ✅ No warning
|
|
1711
1711
|
```
|
|
1712
1712
|
|
|
1713
|
-
#### 6.
|
|
1713
|
+
#### 6. Multi-Tenant Feature (New)
|
|
1714
1714
|
|
|
1715
|
-
|
|
1715
|
+
If your application needs multi-tenant architecture support, you can use the new `app_id` field:
|
|
1716
1716
|
|
|
1717
1717
|
```typescript
|
|
1718
1718
|
const user = await client.getCurrentUser();
|
|
@@ -1720,20 +1720,20 @@ const user = await client.getCurrentUser();
|
|
|
1720
1720
|
console.log('your app id:', user.app_id);
|
|
1721
1721
|
```
|
|
1722
1722
|
|
|
1723
|
-
##
|
|
1723
|
+
## FAQ
|
|
1724
1724
|
|
|
1725
|
-
###
|
|
1725
|
+
### How to Handle Authentication Token?
|
|
1726
1726
|
|
|
1727
1727
|
```typescript
|
|
1728
|
-
//
|
|
1728
|
+
// Save token after successful login
|
|
1729
1729
|
const loginResult = await client.login({ email, password });
|
|
1730
1730
|
localStorage.setItem('token', loginResult.token);
|
|
1731
1731
|
|
|
1732
|
-
//
|
|
1732
|
+
// Create authenticated client
|
|
1733
1733
|
import { AuthFactory } from '@seaverse/auth-sdk';
|
|
1734
1734
|
|
|
1735
1735
|
const authenticatedClient = new SeaVerseBackendAPIClient({
|
|
1736
|
-
appId: 'your app id', //
|
|
1736
|
+
appId: 'your app id', // Required: Application ID
|
|
1737
1737
|
environment: 'production',
|
|
1738
1738
|
auth: AuthFactory.create({
|
|
1739
1739
|
type: 'jwt',
|
|
@@ -1745,49 +1745,49 @@ const authenticatedClient = new SeaVerseBackendAPIClient({
|
|
|
1745
1745
|
});
|
|
1746
1746
|
```
|
|
1747
1747
|
|
|
1748
|
-
### OAuth redirect_uri_mismatch
|
|
1748
|
+
### OAuth redirect_uri_mismatch Error?
|
|
1749
1749
|
|
|
1750
|
-
|
|
1750
|
+
Ensure the redirect URI in OAuth application configuration exactly matches the `redirectUri` in code (including protocol, domain, port, path).
|
|
1751
1751
|
|
|
1752
|
-
###
|
|
1752
|
+
### How to Customize Request Timeout?
|
|
1753
1753
|
|
|
1754
1754
|
```typescript
|
|
1755
1755
|
const client = new SeaVerseBackendAPIClient({
|
|
1756
|
-
appId: 'your app id', //
|
|
1756
|
+
appId: 'your app id', // Required: Application ID
|
|
1757
1757
|
environment: 'production',
|
|
1758
|
-
timeout: 30000, // 30
|
|
1758
|
+
timeout: 30000, // 30 seconds
|
|
1759
1759
|
});
|
|
1760
1760
|
```
|
|
1761
1761
|
|
|
1762
|
-
###
|
|
1762
|
+
### How to Connect to Local API in Local Development?
|
|
1763
1763
|
|
|
1764
1764
|
```typescript
|
|
1765
1765
|
const client = new SeaVerseBackendAPIClient({
|
|
1766
|
-
appId: 'your app id', //
|
|
1767
|
-
environment: 'local', //
|
|
1766
|
+
appId: 'your app id', // Required: Application ID
|
|
1767
|
+
environment: 'local', // Or use baseURL: 'http://localhost:3000'
|
|
1768
1768
|
});
|
|
1769
1769
|
```
|
|
1770
1770
|
|
|
1771
|
-
##
|
|
1771
|
+
## Development
|
|
1772
1772
|
|
|
1773
1773
|
```bash
|
|
1774
|
-
#
|
|
1774
|
+
# Install dependencies
|
|
1775
1775
|
pnpm install
|
|
1776
1776
|
|
|
1777
|
-
#
|
|
1777
|
+
# Build
|
|
1778
1778
|
pnpm build
|
|
1779
1779
|
|
|
1780
|
-
# Watch
|
|
1780
|
+
# Watch mode
|
|
1781
1781
|
pnpm dev
|
|
1782
1782
|
|
|
1783
|
-
#
|
|
1783
|
+
# Test
|
|
1784
1784
|
pnpm test
|
|
1785
1785
|
```
|
|
1786
1786
|
|
|
1787
|
-
##
|
|
1787
|
+
## Related Links
|
|
1788
1788
|
|
|
1789
1789
|
- 📦 [NPM Package](https://www.npmjs.com/package/@seaverse/auth-sdk)
|
|
1790
|
-
- 📖 [
|
|
1790
|
+
- 📖 [Example Project](../../examples/auth-sdk-demo)
|
|
1791
1791
|
- 🐛 [Issues](https://github.com/seaverseai/sv-sdk/issues)
|
|
1792
1792
|
- 💬 [Discussions](https://github.com/seaverseai/sv-sdk/discussions)
|
|
1793
1793
|
|
|
@@ -1795,48 +1795,48 @@ pnpm test
|
|
|
1795
1795
|
|
|
1796
1796
|
MIT © [SeaVerse Team](mailto:support@seaverse.com)
|
|
1797
1797
|
|
|
1798
|
-
##
|
|
1798
|
+
## Changelog
|
|
1799
1799
|
|
|
1800
|
-
### v0.3.6 (
|
|
1801
|
-
- 🧹
|
|
1802
|
-
-
|
|
1803
|
-
-
|
|
1804
|
-
-
|
|
1800
|
+
### v0.3.6 (Current Version)
|
|
1801
|
+
- 🧹 **Code Cleanup**: Remove desktop application OAuth callback related functionality
|
|
1802
|
+
- Remove `oauthDesktopURL` configuration option
|
|
1803
|
+
- Simplify OAuth flow, unified use of `returnUrl` parameter
|
|
1804
|
+
- Clean up related documentation and code comments
|
|
1805
1805
|
|
|
1806
1806
|
### v0.2.5
|
|
1807
|
-
- 🔄
|
|
1808
|
-
-
|
|
1809
|
-
- `login()`, `register()`, `getCurrentUser()`
|
|
1810
|
-
-
|
|
1811
|
-
- 📝
|
|
1807
|
+
- 🔄 **Response Format Compatibility**: Automatic compatibility with wrapped and flat API response formats
|
|
1808
|
+
- Fix "Invalid response from server" prompt during login
|
|
1809
|
+
- `login()`, `register()`, `getCurrentUser()` methods auto-unwrap `data` field
|
|
1810
|
+
- Support two formats: `{ data: {...}, success: true }` and `{ ... }`
|
|
1811
|
+
- 📝 **Documentation Update**: Add response format compatibility section
|
|
1812
1812
|
|
|
1813
1813
|
### v0.2.0
|
|
1814
|
-
- 🔄 **API
|
|
1815
|
-
- 🏢
|
|
1816
|
-
- 🔑
|
|
1817
|
-
- ✨
|
|
1818
|
-
-
|
|
1819
|
-
-
|
|
1820
|
-
- 📝
|
|
1821
|
-
- `SeaVerseBackendAPIClientOptions`:
|
|
1822
|
-
- `User`:
|
|
1823
|
-
- `RegisterRequest`:
|
|
1814
|
+
- 🔄 **API Path Update**: All authentication APIs migrated from `/api/auth/*` to `/sdk/v1/auth/*`
|
|
1815
|
+
- 🏢 **Multi-Tenant Support**: User model adds `app_id` field, supports multi-application isolation
|
|
1816
|
+
- 🔑 **Important Change**: `appId` is now a **required parameter**, SDK automatically adds `X-App-ID` request header to all requests
|
|
1817
|
+
- ✨ **New Features**:
|
|
1818
|
+
- Registration supports custom `username` (optional)
|
|
1819
|
+
- All fields standardized to snake_case (retains camelCase backward compatibility)
|
|
1820
|
+
- 📝 **Field Updates**:
|
|
1821
|
+
- `SeaVerseBackendAPIClientOptions`: Add required field `appId`
|
|
1822
|
+
- `User`: Add `app_id`, `created_at`, `email_verified`, `google_id`, `discord_id`, `github_id`
|
|
1823
|
+
- `RegisterRequest`: Add `username`, `invitation_code`
|
|
1824
1824
|
- `ResetPasswordRequest`: `newPassword` → `new_password`
|
|
1825
1825
|
- ⚠️ **Breaking Changes**:
|
|
1826
|
-
-
|
|
1827
|
-
- API
|
|
1828
|
-
- ✅
|
|
1826
|
+
- Must provide `appId` parameter to initialize client
|
|
1827
|
+
- API path changes require backend support
|
|
1828
|
+
- ✅ **Backward Compatible**: Except for `appId`, all old fields still work
|
|
1829
1829
|
|
|
1830
1830
|
### v0.1.5
|
|
1831
|
-
-
|
|
1832
|
-
-
|
|
1831
|
+
- Fix OAuth state management, switch from sessionStorage to localStorage
|
|
1832
|
+
- Enhance API response handling
|
|
1833
1833
|
|
|
1834
1834
|
### v0.1.4
|
|
1835
|
-
-
|
|
1836
|
-
-
|
|
1835
|
+
- Enhance API response handling
|
|
1836
|
+
- Optimize error handling
|
|
1837
1837
|
|
|
1838
1838
|
### v0.1.2
|
|
1839
|
-
-
|
|
1840
|
-
-
|
|
1839
|
+
- Update package name to @seaverse/auth-sdk
|
|
1840
|
+
- Add multi-environment support
|
|
1841
1841
|
|
|
1842
|
-
|
|
1842
|
+
View complete changelog: [CHANGELOG.md](./CHANGELOG.md)
|