@seaverse/auth-sdk 0.4.1 → 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 -688
- package/dist/auth-modal.css +1 -1
- package/dist/index.cjs +8 -139
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.js +8 -118
- 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,342 +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('
|
|
573
|
-
|
|
574
|
-
// 自定义显示时长(默认 3000ms)
|
|
575
|
-
Toast.success('操作成功', '数据已保存', 5000);
|
|
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');
|
|
576
573
|
```
|
|
577
574
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
Toast 提供了智能的用户交互体验:
|
|
581
|
-
|
|
582
|
-
- **Hover 暂停**:当鼠标悬停在 Toast 上时,自动暂停倒计时,让用户有足够时间阅读消息
|
|
583
|
-
- **离开继续**:鼠标移开后,继续倒计时并自动消失
|
|
584
|
-
- **手动关闭**:点击右上角的关闭按钮可立即关闭通知
|
|
585
|
-
- **自动消失**:默认 3 秒后自动消失(可自定义时长)
|
|
586
|
-
|
|
587
|
-
这些特性确保用户不会错过重要提示信息。
|
|
588
|
-
|
|
589
|
-
#### 重置密码流程
|
|
575
|
+
#### Password Reset Flow
|
|
590
576
|
|
|
591
|
-
AuthModal
|
|
577
|
+
AuthModal supports complete password reset flow:
|
|
592
578
|
|
|
593
|
-
1.
|
|
594
|
-
2.
|
|
595
|
-
3.
|
|
596
|
-
4.
|
|
597
|
-
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
|
|
598
584
|
|
|
599
|
-
|
|
585
|
+
The entire process requires no extra code, AuthModal handles it automatically:
|
|
600
586
|
|
|
601
587
|
```typescript
|
|
602
|
-
// 1.
|
|
588
|
+
// 1. Initialize AuthModal (only once)
|
|
603
589
|
const authModal = new AuthModal({
|
|
604
590
|
client,
|
|
605
|
-
// ...
|
|
591
|
+
// ... other configuration
|
|
606
592
|
});
|
|
607
593
|
|
|
608
|
-
// 2.
|
|
609
|
-
// -
|
|
610
|
-
// -
|
|
611
|
-
// -
|
|
612
|
-
// -
|
|
613
|
-
// -
|
|
614
|
-
// -
|
|
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
|
|
615
601
|
```
|
|
616
602
|
|
|
617
|
-
#### OAuth
|
|
603
|
+
#### OAuth Configuration Instructions
|
|
618
604
|
|
|
619
|
-
`enableOAuth`
|
|
605
|
+
The `enableOAuth` parameter is **completely optional**:
|
|
620
606
|
|
|
621
|
-
-
|
|
622
|
-
-
|
|
623
|
-
-
|
|
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
|
|
624
610
|
|
|
625
|
-
|
|
626
|
-
- `returnUrl
|
|
627
|
-
- `enableOAuth.google
|
|
628
|
-
- `enableOAuth.discord
|
|
629
|
-
- `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
|
|
630
616
|
|
|
631
617
|
```typescript
|
|
632
|
-
//
|
|
618
|
+
// Example 1: No OAuth buttons
|
|
633
619
|
const authModal1 = new AuthModal({
|
|
634
620
|
client,
|
|
635
621
|
theme: 'dark',
|
|
636
|
-
//
|
|
622
|
+
// No enableOAuth, no OAuth buttons shown
|
|
637
623
|
});
|
|
638
624
|
|
|
639
|
-
//
|
|
625
|
+
// Example 2: Only show Google login
|
|
640
626
|
const authModal2 = new AuthModal({
|
|
641
627
|
client,
|
|
642
628
|
theme: 'light',
|
|
643
629
|
returnUrl: 'https://mygame.com/dashboard',
|
|
644
630
|
enableOAuth: {
|
|
645
631
|
google: true,
|
|
646
|
-
// Discord
|
|
632
|
+
// Discord and GitHub not enabled, won't show these buttons
|
|
647
633
|
},
|
|
648
634
|
});
|
|
649
635
|
|
|
650
|
-
//
|
|
636
|
+
// Example 3: Show all OAuth buttons
|
|
651
637
|
const authModal3 = new AuthModal({
|
|
652
638
|
client,
|
|
653
639
|
theme: 'dark',
|
|
@@ -659,58 +645,58 @@ const authModal3 = new AuthModal({
|
|
|
659
645
|
});
|
|
660
646
|
```
|
|
661
647
|
|
|
662
|
-
####
|
|
648
|
+
#### Handling OAuth Callback
|
|
663
649
|
|
|
664
|
-
|
|
650
|
+
In Backend Proxy Mode, after OAuth login it redirects to `returnUrl?token=xxx`. Check and handle token on page load:
|
|
665
651
|
|
|
666
652
|
```typescript
|
|
667
|
-
//
|
|
653
|
+
// Automatically handle OAuth callback on page load
|
|
668
654
|
const result = AuthModal.handleOAuthCallback({
|
|
669
655
|
client,
|
|
670
656
|
onLoginSuccess: (token) => {
|
|
671
657
|
localStorage.setItem('token', token);
|
|
672
|
-
console.log('OAuth
|
|
658
|
+
console.log('OAuth login successful');
|
|
673
659
|
|
|
674
|
-
//
|
|
675
|
-
// handleOAuthCallback
|
|
660
|
+
// Can now call authenticated APIs directly
|
|
661
|
+
// handleOAuthCallback has automatically called client.setToken()
|
|
676
662
|
client.getCurrentUser()
|
|
677
|
-
.then(user => console.log('
|
|
663
|
+
.then(user => console.log('User info:', user));
|
|
678
664
|
},
|
|
679
665
|
});
|
|
680
666
|
|
|
681
667
|
if (result) {
|
|
682
|
-
console.log('
|
|
668
|
+
console.log('Handled OAuth callback, token:', result.token);
|
|
683
669
|
}
|
|
684
670
|
```
|
|
685
671
|
|
|
686
|
-
|
|
687
|
-
- `handleOAuthCallback()`
|
|
688
|
-
-
|
|
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
|
|
689
675
|
|
|
690
|
-
####
|
|
676
|
+
#### Manual Token Setting
|
|
691
677
|
|
|
692
|
-
|
|
678
|
+
If you don't use `AuthModal.handleOAuthCallback()`, you can also set the token manually:
|
|
693
679
|
|
|
694
680
|
```typescript
|
|
695
|
-
//
|
|
681
|
+
// Extract token from URL
|
|
696
682
|
const token = new URLSearchParams(window.location.search).get('token');
|
|
697
683
|
if (token) {
|
|
698
|
-
//
|
|
684
|
+
// Manually set token
|
|
699
685
|
client.setToken(token);
|
|
700
686
|
localStorage.setItem('token', token);
|
|
701
687
|
|
|
702
|
-
//
|
|
688
|
+
// Now can call authenticated APIs
|
|
703
689
|
const user = await client.getCurrentUser();
|
|
704
690
|
}
|
|
705
691
|
```
|
|
706
692
|
|
|
707
|
-
### 6.
|
|
693
|
+
### 6. Container Management
|
|
708
694
|
|
|
709
695
|
```typescript
|
|
710
|
-
//
|
|
696
|
+
// List all containers
|
|
711
697
|
const containers = await client.listContainers();
|
|
712
698
|
|
|
713
|
-
//
|
|
699
|
+
// Register new container
|
|
714
700
|
const result = await client.registerContainer({
|
|
715
701
|
containerId: 'container-123',
|
|
716
702
|
metadata: {
|
|
@@ -719,226 +705,226 @@ const result = await client.registerContainer({
|
|
|
719
705
|
},
|
|
720
706
|
});
|
|
721
707
|
|
|
722
|
-
//
|
|
708
|
+
// Get container info
|
|
723
709
|
const container = await client.getContainer({
|
|
724
710
|
containerId: 'container-123',
|
|
725
711
|
});
|
|
726
712
|
|
|
727
|
-
//
|
|
713
|
+
// Container heartbeat
|
|
728
714
|
await client.containerHeartbeat({
|
|
729
715
|
containerId: 'container-123',
|
|
730
716
|
status: 'healthy',
|
|
731
717
|
});
|
|
732
718
|
|
|
733
|
-
//
|
|
719
|
+
// Unregister container
|
|
734
720
|
await client.unregisterContainer({
|
|
735
721
|
containerId: 'container-123',
|
|
736
722
|
});
|
|
737
723
|
|
|
738
|
-
//
|
|
724
|
+
// Get container stats
|
|
739
725
|
const stats = await client.getContainerStats();
|
|
740
726
|
```
|
|
741
727
|
|
|
742
|
-
### 7.
|
|
728
|
+
### 7. Skill Marketplace
|
|
743
729
|
|
|
744
730
|
```typescript
|
|
745
|
-
//
|
|
731
|
+
// List marketplace skills
|
|
746
732
|
const skills = await client.listMarketplaceSkills({
|
|
747
733
|
category: 'productivity',
|
|
748
734
|
page: 1,
|
|
749
735
|
pageSize: 20,
|
|
750
736
|
});
|
|
751
737
|
|
|
752
|
-
//
|
|
738
|
+
// Get skill details
|
|
753
739
|
const skill = await client.getMarketplaceSkill({
|
|
754
740
|
skillId: 'skill-123',
|
|
755
741
|
});
|
|
756
742
|
|
|
757
|
-
//
|
|
743
|
+
// Install skill
|
|
758
744
|
await client.installSkill({
|
|
759
745
|
skillId: 'skill-123',
|
|
760
746
|
});
|
|
761
747
|
|
|
762
|
-
//
|
|
748
|
+
// List installed skills
|
|
763
749
|
const userSkills = await client.listUserSkills();
|
|
764
750
|
|
|
765
|
-
//
|
|
751
|
+
// Uninstall skill
|
|
766
752
|
await client.uninstallSkill({
|
|
767
753
|
skillId: 'skill-123',
|
|
768
754
|
});
|
|
769
755
|
```
|
|
770
756
|
|
|
771
|
-
### 8.
|
|
757
|
+
### 8. Invitation Code Management
|
|
772
758
|
|
|
773
759
|
```typescript
|
|
774
|
-
//
|
|
760
|
+
// Apply for invitation code (when user doesn't have one)
|
|
775
761
|
const application = await client.applyInvite({
|
|
776
762
|
email: 'player@example.com',
|
|
777
763
|
reason: 'I want to join this amazing platform to build innovative applications and connect with the community.'
|
|
778
764
|
});
|
|
779
765
|
|
|
780
766
|
if (application.success) {
|
|
781
|
-
console.log('
|
|
782
|
-
console.log('
|
|
783
|
-
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'
|
|
784
770
|
} else {
|
|
785
|
-
//
|
|
771
|
+
// Handle error
|
|
786
772
|
if (application.code === 'APPLICATION_DUPLICATE') {
|
|
787
|
-
console.log('
|
|
773
|
+
console.log('You have already submitted an application in the past 24 hours');
|
|
788
774
|
} else {
|
|
789
|
-
console.error('
|
|
775
|
+
console.error('Application failed:', application.error);
|
|
790
776
|
}
|
|
791
777
|
}
|
|
792
778
|
|
|
793
|
-
//
|
|
779
|
+
// List my invitation codes
|
|
794
780
|
const invites = await client.listInvites({
|
|
795
781
|
status: 'active',
|
|
796
782
|
page: 1,
|
|
797
783
|
page_size: 20,
|
|
798
784
|
});
|
|
799
|
-
console.log('
|
|
785
|
+
console.log('My invitation codes:', invites.data.invites);
|
|
800
786
|
|
|
801
|
-
//
|
|
787
|
+
// Get invitation code stats
|
|
802
788
|
const stats = await client.getInviteStats();
|
|
803
|
-
console.log('
|
|
804
|
-
console.log('
|
|
805
|
-
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);
|
|
806
792
|
|
|
807
|
-
//
|
|
793
|
+
// Get invitation code details
|
|
808
794
|
const invite = await client.getInvite('inv_abc123');
|
|
809
|
-
console.log('
|
|
810
|
-
console.log('
|
|
811
|
-
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);
|
|
812
798
|
|
|
813
|
-
//
|
|
799
|
+
// Get invitation code usage records
|
|
814
800
|
const usages = await client.getInviteUsages('inv_abc123', {
|
|
815
801
|
page: 1,
|
|
816
802
|
page_size: 20,
|
|
817
803
|
});
|
|
818
|
-
console.log('
|
|
804
|
+
console.log('Usage records:', usages.data.usages);
|
|
819
805
|
```
|
|
820
806
|
|
|
821
|
-
### 9.
|
|
807
|
+
### 9. Email Verification and Invitation Code Binding
|
|
822
808
|
|
|
823
|
-
####
|
|
809
|
+
#### Email Verification (Auto Login)
|
|
824
810
|
|
|
825
|
-
|
|
811
|
+
After registration, users receive a verification email containing a verification link in the format: `frontend_url?verify_token=xxx`
|
|
826
812
|
|
|
827
|
-
|
|
813
|
+
**Method 1: Use AuthModal Auto-Handling (Recommended)**
|
|
828
814
|
|
|
829
815
|
```typescript
|
|
830
816
|
import { AuthModal } from '@seaverse/auth-sdk';
|
|
831
817
|
|
|
832
|
-
//
|
|
818
|
+
// Create AuthModal instance
|
|
833
819
|
const modal = new AuthModal({
|
|
834
820
|
client,
|
|
835
821
|
onLoginSuccess: (token, user) => {
|
|
836
822
|
localStorage.setItem('token', token);
|
|
837
|
-
console.log('
|
|
823
|
+
console.log('Email verified and logged in successfully:', user);
|
|
838
824
|
},
|
|
839
825
|
onError: (error) => {
|
|
840
|
-
console.error('
|
|
826
|
+
console.error('Email verification failed:', error);
|
|
841
827
|
}
|
|
842
828
|
});
|
|
843
829
|
|
|
844
|
-
// AuthModal
|
|
845
|
-
//
|
|
846
|
-
// 1.
|
|
847
|
-
// 2.
|
|
848
|
-
// 3.
|
|
849
|
-
// 4.
|
|
850
|
-
// 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
|
|
851
837
|
```
|
|
852
838
|
|
|
853
|
-
|
|
839
|
+
**Debugging Tips**:
|
|
854
840
|
|
|
855
|
-
|
|
841
|
+
If email verification has issues, check browser console logs:
|
|
856
842
|
|
|
857
843
|
```javascript
|
|
858
|
-
//
|
|
844
|
+
// Normal case should show:
|
|
859
845
|
[AuthModal] Detected verify_token, starting email verification...
|
|
860
846
|
[AuthModal] Email verification successful: { id: "xxx", email: "user@example.com", ... }
|
|
861
847
|
|
|
862
|
-
//
|
|
848
|
+
// If verification fails, will show:
|
|
863
849
|
[AuthModal] Email verification failed: Error: ...
|
|
864
850
|
```
|
|
865
851
|
|
|
866
|
-
|
|
852
|
+
**Method 2: Manual Email Verification Handling**
|
|
867
853
|
|
|
868
854
|
```typescript
|
|
869
|
-
//
|
|
855
|
+
// Verify email (get verify_token from email link)
|
|
870
856
|
const urlParams = new URLSearchParams(window.location.search);
|
|
871
857
|
const verifyToken = urlParams.get('verify_token');
|
|
872
858
|
|
|
873
859
|
if (verifyToken) {
|
|
874
860
|
const result = await client.verifyEmail(verifyToken);
|
|
875
861
|
|
|
876
|
-
//
|
|
862
|
+
// Auto-login: save returned token
|
|
877
863
|
localStorage.setItem('token', result.data.token);
|
|
878
864
|
localStorage.setItem('refreshToken', result.data.refreshToken);
|
|
879
865
|
|
|
880
|
-
console.log('
|
|
866
|
+
console.log('Email verified successfully, auto-logged in:', result.data.user);
|
|
881
867
|
|
|
882
|
-
//
|
|
868
|
+
// Redirect to homepage
|
|
883
869
|
window.location.href = '/';
|
|
884
870
|
}
|
|
885
871
|
```
|
|
886
872
|
|
|
887
|
-
####
|
|
873
|
+
#### Invitation Code Binding (Account Activation)
|
|
888
874
|
|
|
889
|
-
|
|
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.
|
|
890
876
|
|
|
891
|
-
|
|
877
|
+
**Apply for Invitation Code Feature**:
|
|
892
878
|
|
|
893
|
-
|
|
894
|
-
1.
|
|
895
|
-
2.
|
|
896
|
-
3.
|
|
897
|
-
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
|
|
898
884
|
|
|
899
|
-
|
|
900
|
-
-
|
|
901
|
-
- redirectUrl
|
|
902
|
-
-
|
|
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
|
|
903
889
|
|
|
904
|
-
|
|
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`.
|
|
905
891
|
|
|
906
|
-
|
|
892
|
+
**Method 1: Use AuthModal Auto-Handling (Recommended)**
|
|
907
893
|
|
|
908
894
|
```typescript
|
|
909
895
|
import { AuthModal } from '@seaverse/auth-sdk';
|
|
910
896
|
|
|
911
|
-
//
|
|
897
|
+
// Create AuthModal instance
|
|
912
898
|
const modal = new AuthModal({
|
|
913
899
|
client,
|
|
914
900
|
onLoginSuccess: (token, user) => {
|
|
915
901
|
localStorage.setItem('token', token);
|
|
916
|
-
console.log('
|
|
902
|
+
console.log('Login successful:', user);
|
|
917
903
|
},
|
|
918
904
|
onInviteCodeRequired: (userId, email) => {
|
|
919
|
-
//
|
|
920
|
-
console.log('
|
|
905
|
+
// Optional: custom handling when invitation code is needed
|
|
906
|
+
console.log('Need invitation code to activate account:', userId, email);
|
|
921
907
|
}
|
|
922
908
|
});
|
|
923
909
|
|
|
924
|
-
// AuthModal
|
|
910
|
+
// AuthModal will automatically detect the following parameter combinations in URL:
|
|
925
911
|
// - error_code=INVITE_CODE_REQUIRED
|
|
926
|
-
// - user_id
|
|
927
|
-
// - email
|
|
912
|
+
// - user_id or temp_user_id (user ID)
|
|
913
|
+
// - email (optional, user email)
|
|
928
914
|
//
|
|
929
|
-
//
|
|
930
|
-
// 1.
|
|
931
|
-
// 2.
|
|
932
|
-
// 3.
|
|
933
|
-
// 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
|
|
934
920
|
```
|
|
935
921
|
|
|
936
|
-
|
|
922
|
+
**Debugging Tips**:
|
|
937
923
|
|
|
938
|
-
|
|
924
|
+
If invitation code modal doesn't appear, check browser console for the following logs:
|
|
939
925
|
|
|
940
926
|
```javascript
|
|
941
|
-
//
|
|
927
|
+
// Normal case should show:
|
|
942
928
|
[AuthModal] Detected INVITE_CODE_REQUIRED: {
|
|
943
929
|
errorCode: "INVITE_CODE_REQUIRED",
|
|
944
930
|
userId: "xxx",
|
|
@@ -947,50 +933,50 @@ const modal = new AuthModal({
|
|
|
947
933
|
fullURL: "http://localhost:8001/?error_code=INVITE_CODE_REQUIRED&user_id=xxx&email=xxx"
|
|
948
934
|
}
|
|
949
935
|
|
|
950
|
-
//
|
|
936
|
+
// If user_id is missing, will show error:
|
|
951
937
|
[AuthModal] Missing user_id in URL parameters.
|
|
952
|
-
//
|
|
938
|
+
// And shows alert with full URL for troubleshooting backend redirect issues
|
|
953
939
|
```
|
|
954
940
|
|
|
955
|
-
|
|
941
|
+
**Method 2: Manual Invitation Code Binding Handling**
|
|
956
942
|
|
|
957
943
|
```typescript
|
|
958
|
-
// 1.
|
|
944
|
+
// 1. Check if invitation code is needed in URL
|
|
959
945
|
const urlParams = new URLSearchParams(window.location.search);
|
|
960
946
|
const errorCode = urlParams.get('error_code');
|
|
961
947
|
const userId = urlParams.get('user_id');
|
|
962
948
|
|
|
963
949
|
if (errorCode === 'INVITE_CODE_REQUIRED' && userId) {
|
|
964
|
-
// 2.
|
|
965
|
-
const inviteCode = await showInviteCodeInput(); //
|
|
950
|
+
// 2. Show invitation code input interface (custom UI)
|
|
951
|
+
const inviteCode = await showInviteCodeInput(); // Your custom UI
|
|
966
952
|
|
|
967
|
-
// 3.
|
|
953
|
+
// 3. Bind invitation code
|
|
968
954
|
const result = await client.bindInviteCode({
|
|
969
955
|
user_id: userId,
|
|
970
956
|
invite_code: inviteCode
|
|
971
957
|
});
|
|
972
958
|
|
|
973
|
-
// 4.
|
|
959
|
+
// 4. Activation successful, auto-login
|
|
974
960
|
localStorage.setItem('token', result.data.token);
|
|
975
961
|
localStorage.setItem('refreshToken', result.data.refreshToken);
|
|
976
|
-
console.log('
|
|
962
|
+
console.log('Account activated successfully:', result.data.user);
|
|
977
963
|
|
|
978
|
-
// 5.
|
|
964
|
+
// 5. Redirect to homepage
|
|
979
965
|
window.location.href = '/';
|
|
980
966
|
}
|
|
981
967
|
```
|
|
982
968
|
|
|
983
|
-
|
|
969
|
+
**Method 3: Use Static Method Handling**
|
|
984
970
|
|
|
985
971
|
```typescript
|
|
986
972
|
import { AuthModal } from '@seaverse/auth-sdk';
|
|
987
973
|
|
|
988
|
-
// AuthModal
|
|
974
|
+
// AuthModal provides static method to handle invitation code scenario
|
|
989
975
|
const inviteCodeInfo = AuthModal.handleInviteCodeRequired(
|
|
990
976
|
{ client },
|
|
991
977
|
(userId, email) => {
|
|
992
|
-
//
|
|
993
|
-
const code = prompt(
|
|
978
|
+
// Custom handling logic
|
|
979
|
+
const code = prompt(`Please enter invitation code to activate account (${email}):`);
|
|
994
980
|
if (code) {
|
|
995
981
|
client.bindInviteCode({ user_id: userId, invite_code: code })
|
|
996
982
|
.then(res => {
|
|
@@ -1002,33 +988,33 @@ const inviteCodeInfo = AuthModal.handleInviteCodeRequired(
|
|
|
1002
988
|
);
|
|
1003
989
|
```
|
|
1004
990
|
|
|
1005
|
-
### 10.
|
|
991
|
+
### 10. Other Features
|
|
1006
992
|
|
|
1007
993
|
```typescript
|
|
1008
|
-
//
|
|
994
|
+
// Get API Service Token
|
|
1009
995
|
const apiToken = await client.getApiServiceToken();
|
|
1010
996
|
|
|
1011
|
-
//
|
|
997
|
+
// Get conversation status
|
|
1012
998
|
const status = await client.getConversationStatus({
|
|
1013
999
|
conversationId: 'conv-123',
|
|
1014
1000
|
});
|
|
1015
1001
|
|
|
1016
|
-
//
|
|
1002
|
+
// Get speech token
|
|
1017
1003
|
const speechToken = await client.getSpeechToken();
|
|
1018
1004
|
```
|
|
1019
1005
|
|
|
1020
|
-
##
|
|
1006
|
+
## Advanced Configuration
|
|
1021
1007
|
|
|
1022
|
-
###
|
|
1008
|
+
### Custom Authentication
|
|
1023
1009
|
|
|
1024
1010
|
```typescript
|
|
1025
1011
|
import { AuthFactory } from '@seaverse/auth-sdk';
|
|
1026
1012
|
|
|
1027
1013
|
const client = new SeaVerseBackendAPIClient({
|
|
1028
|
-
appId: 'your app id', //
|
|
1014
|
+
appId: 'your app id', // Required: Application ID
|
|
1029
1015
|
environment: 'production',
|
|
1030
1016
|
|
|
1031
|
-
//
|
|
1017
|
+
// Use JWT authentication
|
|
1032
1018
|
auth: AuthFactory.create({
|
|
1033
1019
|
type: 'jwt',
|
|
1034
1020
|
credentials: {
|
|
@@ -1039,28 +1025,28 @@ const client = new SeaVerseBackendAPIClient({
|
|
|
1039
1025
|
});
|
|
1040
1026
|
```
|
|
1041
1027
|
|
|
1042
|
-
###
|
|
1028
|
+
### Custom Hooks
|
|
1043
1029
|
|
|
1044
1030
|
```typescript
|
|
1045
1031
|
import { BuiltInHooks } from '@seaverse/auth-sdk';
|
|
1046
1032
|
|
|
1047
1033
|
const client = new SeaVerseBackendAPIClient({
|
|
1048
|
-
appId: 'your app id', //
|
|
1034
|
+
appId: 'your app id', // Required: Application ID
|
|
1049
1035
|
environment: 'production',
|
|
1050
1036
|
|
|
1051
1037
|
hooks: {
|
|
1052
1038
|
hooks: [
|
|
1053
|
-
//
|
|
1039
|
+
// Logger Hook
|
|
1054
1040
|
BuiltInHooks.createLoggerHook({
|
|
1055
1041
|
logLevel: 'debug',
|
|
1056
1042
|
logRequestBody: true,
|
|
1057
1043
|
logResponseBody: true,
|
|
1058
1044
|
}),
|
|
1059
1045
|
|
|
1060
|
-
//
|
|
1046
|
+
// Request ID Hook
|
|
1061
1047
|
BuiltInHooks.createRequestIdHook(),
|
|
1062
1048
|
|
|
1063
|
-
//
|
|
1049
|
+
// Custom Hook
|
|
1064
1050
|
{
|
|
1065
1051
|
type: 'beforeRequest',
|
|
1066
1052
|
name: 'custom-hook',
|
|
@@ -1074,22 +1060,22 @@ const client = new SeaVerseBackendAPIClient({
|
|
|
1074
1060
|
});
|
|
1075
1061
|
```
|
|
1076
1062
|
|
|
1077
|
-
###
|
|
1063
|
+
### Request Retry Configuration
|
|
1078
1064
|
|
|
1079
|
-
SDK
|
|
1065
|
+
The SDK supports custom HTTP request retry strategies. **By default, retry functionality is disabled (maxRetries: 0)** to ensure request predictability.
|
|
1080
1066
|
|
|
1081
|
-
####
|
|
1067
|
+
#### Default Behavior (Retry Disabled)
|
|
1082
1068
|
|
|
1083
1069
|
```typescript
|
|
1084
1070
|
const client = new SeaVerseBackendAPIClient({
|
|
1085
1071
|
appId: 'your app id',
|
|
1086
|
-
// retryOptions
|
|
1072
|
+
// retryOptions not set, no retry by default
|
|
1087
1073
|
});
|
|
1088
1074
|
|
|
1089
|
-
//
|
|
1075
|
+
// If request fails, will return error immediately, no retry
|
|
1090
1076
|
```
|
|
1091
1077
|
|
|
1092
|
-
####
|
|
1078
|
+
#### Enable Basic Retry
|
|
1093
1079
|
|
|
1094
1080
|
```typescript
|
|
1095
1081
|
import { SeaVerseBackendAPIClient } from '@seaverse/auth-sdk';
|
|
@@ -1097,25 +1083,25 @@ import { SeaVerseBackendAPIClient } from '@seaverse/auth-sdk';
|
|
|
1097
1083
|
const client = new SeaVerseBackendAPIClient({
|
|
1098
1084
|
appId: 'your app id',
|
|
1099
1085
|
retryOptions: {
|
|
1100
|
-
maxRetries: 3, //
|
|
1101
|
-
retryDelay: 1000, //
|
|
1102
|
-
//
|
|
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]
|
|
1103
1089
|
},
|
|
1104
1090
|
});
|
|
1105
1091
|
```
|
|
1106
1092
|
|
|
1107
|
-
|
|
1108
|
-
-
|
|
1109
|
-
-
|
|
1110
|
-
- `408` - Request Timeout
|
|
1111
|
-
- `429` - Too Many Requests
|
|
1112
|
-
- `500` - Internal Server Error
|
|
1113
|
-
- `502` - Bad Gateway
|
|
1114
|
-
- `503` - Service Unavailable
|
|
1115
|
-
- `504` - Gateway Timeout
|
|
1116
|
-
-
|
|
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
|
|
1117
1103
|
|
|
1118
|
-
####
|
|
1104
|
+
#### Custom Retry Status Codes
|
|
1119
1105
|
|
|
1120
1106
|
```typescript
|
|
1121
1107
|
const client = new SeaVerseBackendAPIClient({
|
|
@@ -1123,12 +1109,12 @@ const client = new SeaVerseBackendAPIClient({
|
|
|
1123
1109
|
retryOptions: {
|
|
1124
1110
|
maxRetries: 5,
|
|
1125
1111
|
retryDelay: 2000,
|
|
1126
|
-
retryStatusCodes: [503, 504], //
|
|
1112
|
+
retryStatusCodes: [503, 504], // Only retry on 503 and 504
|
|
1127
1113
|
},
|
|
1128
1114
|
});
|
|
1129
1115
|
```
|
|
1130
1116
|
|
|
1131
|
-
####
|
|
1117
|
+
#### Custom Retry Logic
|
|
1132
1118
|
|
|
1133
1119
|
```typescript
|
|
1134
1120
|
import type { RetryOptions } from '@seaverse/auth-sdk';
|
|
@@ -1136,13 +1122,13 @@ import type { RetryOptions } from '@seaverse/auth-sdk';
|
|
|
1136
1122
|
const retryOptions: RetryOptions = {
|
|
1137
1123
|
maxRetries: 3,
|
|
1138
1124
|
retryDelay: 1000,
|
|
1139
|
-
//
|
|
1125
|
+
// Custom judgment logic: only retry on specific errors
|
|
1140
1126
|
shouldRetry: (error) => {
|
|
1141
|
-
//
|
|
1127
|
+
// Only retry on service unavailable errors
|
|
1142
1128
|
if (error.response?.status === 503) {
|
|
1143
1129
|
return true;
|
|
1144
1130
|
}
|
|
1145
|
-
//
|
|
1131
|
+
// Retry on network errors with no response
|
|
1146
1132
|
if (!error.response) {
|
|
1147
1133
|
return true;
|
|
1148
1134
|
}
|
|
@@ -1156,153 +1142,153 @@ const client = new SeaVerseBackendAPIClient({
|
|
|
1156
1142
|
});
|
|
1157
1143
|
```
|
|
1158
1144
|
|
|
1159
|
-
#### RetryOptions
|
|
1145
|
+
#### RetryOptions Type Definition
|
|
1160
1146
|
|
|
1161
1147
|
```typescript
|
|
1162
1148
|
interface RetryOptions {
|
|
1163
|
-
maxRetries?: number; //
|
|
1164
|
-
retryDelay?: number; //
|
|
1165
|
-
retryStatusCodes?: number[]; //
|
|
1166
|
-
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
|
|
1167
1153
|
}
|
|
1168
1154
|
```
|
|
1169
1155
|
|
|
1170
|
-
|
|
1171
|
-
- ⚠️
|
|
1172
|
-
- ✅
|
|
1173
|
-
- ✅
|
|
1174
|
-
- ✅
|
|
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
|
|
1175
1161
|
|
|
1176
|
-
###
|
|
1162
|
+
### Environment Configuration
|
|
1177
1163
|
|
|
1178
|
-
SDK
|
|
1164
|
+
The SDK supports the following environments:
|
|
1179
1165
|
|
|
1180
|
-
|
|
|
1166
|
+
| Environment | Description | BaseURL |
|
|
1181
1167
|
|------|------|---------|
|
|
1182
|
-
| `production` |
|
|
1183
|
-
| `staging` |
|
|
1184
|
-
| `development` |
|
|
1185
|
-
| `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` |
|
|
1186
1172
|
|
|
1187
|
-
|
|
1173
|
+
Auto-detection rules:
|
|
1188
1174
|
- `*.seaverse.com` → production
|
|
1189
1175
|
- `*.staging.seaverse.dev` → staging
|
|
1190
1176
|
- `*.dev.seaverse.dev` → development
|
|
1191
1177
|
- `localhost` → local
|
|
1192
1178
|
|
|
1193
|
-
## API
|
|
1179
|
+
## API Reference
|
|
1194
1180
|
|
|
1195
|
-
###
|
|
1181
|
+
### Authentication Related
|
|
1196
1182
|
|
|
1197
|
-
|
|
|
1183
|
+
| Method | Parameters | Return Value | Description |
|
|
1198
1184
|
|------|------|--------|------|
|
|
1199
|
-
| `register()` | `{ email, password, username?, invitation_code?, frontend_url? }` | `RegisterResponse` |
|
|
1200
|
-
| `login()` | `{ email, password, frontend_url? }` | `LoginResponse` |
|
|
1201
|
-
| `getCurrentUser()` | - | `User` |
|
|
1202
|
-
| `logout()` | - | `SuccessResponse` |
|
|
1203
|
-
| `verifyEmail()` | `verifyToken: string` | `EmailVerificationResponse` |
|
|
1204
|
-
| `bindInviteCode()` | `{ user_id, invite_code }` | `BindInviteCodeResponse` |
|
|
1205
|
-
| `forgotPassword()` | `{ email, frontend_url? }` | `SuccessResponse` |
|
|
1206
|
-
| `resetPassword()` | `{ token, new_password }` | `SuccessResponse` |
|
|
1207
|
-
| `setToken()` | `token: string` | `void` |
|
|
1208
|
-
| `isInIframe()` | - | `boolean` |
|
|
1209
|
-
| `getIframeToken()` | `{ timeout? }` | `Promise<string>` |
|
|
1210
|
-
| `getApiServiceToken()` | - | `ApiServiceTokenResponse` |
|
|
1211
|
-
|
|
1212
|
-
####
|
|
1213
|
-
|
|
1214
|
-
`register()`
|
|
1215
|
-
|
|
1216
|
-
|
|
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)**
|
|
1217
1203
|
```typescript
|
|
1218
1204
|
const response = await client.register({ email, password });
|
|
1219
1205
|
// response.success === true
|
|
1220
|
-
// response.requiresEmailVerification === undefined (
|
|
1221
|
-
// response.requiresInvitationCode === undefined (
|
|
1222
|
-
// → SDK
|
|
1206
|
+
// response.requiresEmailVerification === undefined (or false)
|
|
1207
|
+
// response.requiresInvitationCode === undefined (or false)
|
|
1208
|
+
// → SDK automatically calls login() and triggers onSignupSuccess callback
|
|
1223
1209
|
```
|
|
1224
1210
|
|
|
1225
|
-
|
|
1211
|
+
**Flow 2: Requires Email Verification**
|
|
1226
1212
|
```typescript
|
|
1227
1213
|
const response = await client.register({ email, password });
|
|
1228
1214
|
// response.success === true
|
|
1229
1215
|
// response.requiresEmailVerification === true
|
|
1230
|
-
// → SDK
|
|
1231
|
-
// →
|
|
1216
|
+
// → SDK shows Toast prompting user to check email
|
|
1217
|
+
// → Does not auto-call login(), user needs to click verification link in email
|
|
1232
1218
|
```
|
|
1233
1219
|
|
|
1234
|
-
|
|
1220
|
+
**Flow 3: Requires Invitation Code Activation**
|
|
1235
1221
|
```typescript
|
|
1236
1222
|
const response = await client.register({ email, password });
|
|
1237
1223
|
// response.success === true
|
|
1238
1224
|
// response.requiresInvitationCode === true
|
|
1239
1225
|
// response.tempUserId === "temp_xxx"
|
|
1240
|
-
// → SDK
|
|
1241
|
-
// →
|
|
1226
|
+
// → SDK shows invitation code input form
|
|
1227
|
+
// → Does not auto-call login(), user needs to input invitation code
|
|
1242
1228
|
```
|
|
1243
1229
|
|
|
1244
|
-
|
|
1245
|
-
- AuthModal
|
|
1246
|
-
-
|
|
1247
|
-
-
|
|
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
|
|
1248
1234
|
|
|
1249
|
-
### OAuth
|
|
1235
|
+
### OAuth Related
|
|
1250
1236
|
|
|
1251
|
-
|
|
|
1237
|
+
| Method | Parameters | Return Value | Description |
|
|
1252
1238
|
|------|------|--------|------|
|
|
1253
|
-
| `googleAuthorize()` | `{ return_url? }` | `OAuthAuthorizeResponse` | Google OAuth
|
|
1254
|
-
| `discordAuthorize()` | `{ return_url? }` | `OAuthAuthorizeResponse` | Discord OAuth
|
|
1255
|
-
| `githubAuthorize()` | `{ return_url? }` | `OAuthAuthorizeResponse` | GitHub OAuth
|
|
1256
|
-
| `unlinkGoogle()` | - | `SuccessResponse` |
|
|
1257
|
-
| `unlinkDiscord()` | - | `SuccessResponse` |
|
|
1258
|
-
| `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 |
|
|
1259
1245
|
|
|
1260
|
-
###
|
|
1246
|
+
### Container Management
|
|
1261
1247
|
|
|
1262
|
-
|
|
|
1248
|
+
| Method | Parameters | Return Value | Description |
|
|
1263
1249
|
|------|------|--------|------|
|
|
1264
|
-
| `listContainers()` | - | `ContainerListResponse` |
|
|
1265
|
-
| `registerContainer()` | `{ containerId, metadata }` | `SuccessResponse` |
|
|
1266
|
-
| `getContainer()` | `{ containerId }` | `Container` |
|
|
1267
|
-
| `unregisterContainer()` | `{ containerId }` | `SuccessResponse` |
|
|
1268
|
-
| `containerHeartbeat()` | `{ containerId, status }` | `SuccessResponse` |
|
|
1269
|
-
| `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 |
|
|
1270
1256
|
|
|
1271
|
-
###
|
|
1257
|
+
### Skill Marketplace
|
|
1272
1258
|
|
|
1273
|
-
|
|
|
1259
|
+
| Method | Parameters | Return Value | Description |
|
|
1274
1260
|
|------|------|--------|------|
|
|
1275
|
-
| `listMarketplaceSkills()` | `{ category?, page?, pageSize? }` | `MarketplaceSkillsListResponse` |
|
|
1276
|
-
| `getMarketplaceSkill()` | `{ skillId }` | `MarketplaceSkill` |
|
|
1277
|
-
| `installSkill()` | `{ skillId }` | `SuccessResponse` |
|
|
1278
|
-
| `listUserSkills()` | - | `UserInstalledSkillsListResponse` |
|
|
1279
|
-
| `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 |
|
|
1280
1266
|
|
|
1281
|
-
###
|
|
1267
|
+
### Invitation Code Management
|
|
1282
1268
|
|
|
1283
|
-
|
|
|
1269
|
+
| Method | Parameters | Return Value | Description |
|
|
1284
1270
|
|------|------|--------|------|
|
|
1285
|
-
| `applyInvite()` | `{ email, reason }` | `ApplyInviteResponse` |
|
|
1286
|
-
| `listInvites()` | `{ page?, page_size?, status? }` | `ListInvitesResponse` |
|
|
1287
|
-
| `getInviteStats()` | - | `InviteStatsResponse` |
|
|
1288
|
-
| `getInvite()` | `inviteId: string` | `InviteCodeDetailResponse` |
|
|
1289
|
-
| `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 |
|
|
1290
1276
|
|
|
1291
|
-
###
|
|
1277
|
+
### Others
|
|
1292
1278
|
|
|
1293
|
-
|
|
|
1279
|
+
| Method | Parameters | Return Value | Description |
|
|
1294
1280
|
|------|------|--------|------|
|
|
1295
|
-
| `getHealth()` | - | `HealthResponse` |
|
|
1296
|
-
| `getConversationStatus()` | `{ conversationId }` | `ConversationStatusResponse` |
|
|
1297
|
-
| `getSpeechToken()` | - | `SpeechTokenResponse` |
|
|
1281
|
+
| `getHealth()` | - | `HealthResponse` | Health check |
|
|
1282
|
+
| `getConversationStatus()` | `{ conversationId }` | `ConversationStatusResponse` | Get conversation status |
|
|
1283
|
+
| `getSpeechToken()` | - | `SpeechTokenResponse` | Get speech token |
|
|
1298
1284
|
|
|
1299
|
-
##
|
|
1285
|
+
## Response Format Compatibility
|
|
1300
1286
|
|
|
1301
|
-
###
|
|
1287
|
+
### Auto Response Unwrapping
|
|
1302
1288
|
|
|
1303
|
-
|
|
1289
|
+
Starting from v0.2.5, SDK automatically supports two API response formats, no manual handling needed:
|
|
1304
1290
|
|
|
1305
|
-
|
|
1291
|
+
**Format 1: Wrapped Format** (Recommended)
|
|
1306
1292
|
```json
|
|
1307
1293
|
{
|
|
1308
1294
|
"data": {
|
|
@@ -1313,7 +1299,7 @@ const response = await client.register({ email, password });
|
|
|
1313
1299
|
}
|
|
1314
1300
|
```
|
|
1315
1301
|
|
|
1316
|
-
|
|
1302
|
+
**Format 2: Flat Format** (Backward Compatible)
|
|
1317
1303
|
```json
|
|
1318
1304
|
{
|
|
1319
1305
|
"token": "eyJhbGc...",
|
|
@@ -1321,101 +1307,101 @@ const response = await client.register({ email, password });
|
|
|
1321
1307
|
}
|
|
1322
1308
|
```
|
|
1323
1309
|
|
|
1324
|
-
SDK
|
|
1310
|
+
SDK automatically detects response format and extracts correct data:
|
|
1325
1311
|
|
|
1326
1312
|
```typescript
|
|
1327
|
-
//
|
|
1313
|
+
// Regardless of which format backend returns, the following code works
|
|
1328
1314
|
const loginResult = await client.login({
|
|
1329
1315
|
email: 'user@example.com',
|
|
1330
1316
|
password: 'password123',
|
|
1331
1317
|
});
|
|
1332
1318
|
|
|
1333
|
-
console.log(loginResult.token); // ✅
|
|
1334
|
-
console.log(loginResult.user); // ✅
|
|
1319
|
+
console.log(loginResult.token); // ✅ Always gets token correctly
|
|
1320
|
+
console.log(loginResult.user); // ✅ Always gets user correctly
|
|
1335
1321
|
```
|
|
1336
1322
|
|
|
1337
|
-
###
|
|
1323
|
+
### Affected Methods
|
|
1338
1324
|
|
|
1339
|
-
|
|
1325
|
+
The following methods have implemented auto response unwrapping:
|
|
1340
1326
|
|
|
1341
|
-
- ✅ `login()` -
|
|
1342
|
-
- ✅ `register()` -
|
|
1343
|
-
- ✅ `getCurrentUser()` -
|
|
1327
|
+
- ✅ `login()` - Login API
|
|
1328
|
+
- ✅ `register()` - Registration API
|
|
1329
|
+
- ✅ `getCurrentUser()` - Get current user
|
|
1344
1330
|
|
|
1345
|
-
###
|
|
1331
|
+
### Implementation Principle
|
|
1346
1332
|
|
|
1347
|
-
SDK
|
|
1333
|
+
SDK internally handles response format through the following logic:
|
|
1348
1334
|
|
|
1349
1335
|
```typescript
|
|
1350
1336
|
const response = await httpClient.request(config);
|
|
1351
1337
|
const responseData = response.data;
|
|
1352
1338
|
|
|
1353
|
-
//
|
|
1339
|
+
// Detect and unwrap
|
|
1354
1340
|
if (responseData.data && typeof responseData.data === 'object') {
|
|
1355
|
-
//
|
|
1341
|
+
// Wrapped format: extract data field
|
|
1356
1342
|
return responseData.data;
|
|
1357
1343
|
}
|
|
1358
|
-
//
|
|
1344
|
+
// Flat format: return directly
|
|
1359
1345
|
return responseData;
|
|
1360
1346
|
```
|
|
1361
1347
|
|
|
1362
|
-
|
|
1363
|
-
- 🔄
|
|
1364
|
-
- 🔧
|
|
1365
|
-
- ✅
|
|
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
|
|
1366
1352
|
|
|
1367
|
-
##
|
|
1353
|
+
## Error Handling
|
|
1368
1354
|
|
|
1369
|
-
###
|
|
1355
|
+
### Error Response Format
|
|
1370
1356
|
|
|
1371
|
-
|
|
1357
|
+
All API errors follow a unified response format:
|
|
1372
1358
|
|
|
1373
1359
|
```typescript
|
|
1374
1360
|
interface ApiError {
|
|
1375
|
-
success: false; //
|
|
1376
|
-
error: string; //
|
|
1377
|
-
code?: string; //
|
|
1378
|
-
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
|
|
1379
1365
|
}
|
|
1380
1366
|
```
|
|
1381
1367
|
|
|
1382
|
-
###
|
|
1368
|
+
### Error Codes
|
|
1383
1369
|
|
|
1384
|
-
SDK
|
|
1370
|
+
SDK provides standard error code enums:
|
|
1385
1371
|
|
|
1386
1372
|
```typescript
|
|
1387
1373
|
import { ErrorCode } from '@seaverse/auth-sdk';
|
|
1388
1374
|
|
|
1389
|
-
//
|
|
1390
|
-
ErrorCode.ACCOUNT_EXISTS //
|
|
1391
|
-
ErrorCode.ACCOUNT_NOT_FOUND //
|
|
1392
|
-
ErrorCode.ACCOUNT_SUSPENDED //
|
|
1393
|
-
ErrorCode.INVALID_CREDENTIALS //
|
|
1394
|
-
ErrorCode.EMAIL_NOT_VERIFIED //
|
|
1395
|
-
|
|
1396
|
-
//
|
|
1397
|
-
ErrorCode.INVALID_EMAIL //
|
|
1398
|
-
ErrorCode.INVALID_PASSWORD //
|
|
1399
|
-
ErrorCode.PASSWORD_TOO_WEAK //
|
|
1400
|
-
|
|
1401
|
-
//
|
|
1402
|
-
ErrorCode.INVALID_INVITATION_CODE //
|
|
1403
|
-
ErrorCode.INVITATION_REQUIRED //
|
|
1404
|
-
ErrorCode.INVITE_CODE_REQUIRED //
|
|
1405
|
-
|
|
1406
|
-
// Token
|
|
1407
|
-
ErrorCode.INVALID_TOKEN //
|
|
1408
|
-
ErrorCode.TOKEN_EXPIRED // Token
|
|
1409
|
-
|
|
1410
|
-
//
|
|
1411
|
-
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
|
|
1412
1398
|
```
|
|
1413
1399
|
|
|
1414
|
-
###
|
|
1400
|
+
### Error Handling Best Practices
|
|
1415
1401
|
|
|
1416
|
-
#### 1.
|
|
1402
|
+
#### 1. Handle Duplicate Users During Registration
|
|
1417
1403
|
|
|
1418
|
-
|
|
1404
|
+
Since backend returns 200 OK when account exists (success response), frontend won't throw exception, but includes error info in response body.
|
|
1419
1405
|
|
|
1420
1406
|
```typescript
|
|
1421
1407
|
import { ErrorCode } from '@seaverse/auth-sdk';
|
|
@@ -1425,27 +1411,27 @@ const result = await client.register({
|
|
|
1425
1411
|
password: 'password123',
|
|
1426
1412
|
});
|
|
1427
1413
|
|
|
1428
|
-
//
|
|
1414
|
+
// Check success field in response
|
|
1429
1415
|
if (result.success) {
|
|
1430
|
-
console.log('
|
|
1431
|
-
//
|
|
1416
|
+
console.log('Registration successful:', result);
|
|
1417
|
+
// Proceed with follow-up actions, such as auto-login
|
|
1432
1418
|
} else if (result.code === ErrorCode.ACCOUNT_EXISTS) {
|
|
1433
|
-
//
|
|
1419
|
+
// Account exists, prompt user
|
|
1434
1420
|
const { email, app_id } = result.details;
|
|
1435
|
-
console.log(
|
|
1421
|
+
console.log(`Account ${email} already exists in application ${app_id}`);
|
|
1436
1422
|
|
|
1437
|
-
//
|
|
1423
|
+
// Show friendly prompt
|
|
1438
1424
|
alert('This email is already registered. Please login instead.');
|
|
1439
1425
|
|
|
1440
|
-
//
|
|
1426
|
+
// Or guide user to login
|
|
1441
1427
|
showLoginModal();
|
|
1442
1428
|
} else {
|
|
1443
|
-
//
|
|
1444
|
-
console.error('
|
|
1429
|
+
// Handle other errors
|
|
1430
|
+
console.error('Registration failed:', result.error);
|
|
1445
1431
|
}
|
|
1446
1432
|
```
|
|
1447
1433
|
|
|
1448
|
-
#### 2.
|
|
1434
|
+
#### 2. Handle Various Errors During Login
|
|
1449
1435
|
|
|
1450
1436
|
```typescript
|
|
1451
1437
|
try {
|
|
@@ -1453,28 +1439,28 @@ try {
|
|
|
1453
1439
|
email: 'user@example.com',
|
|
1454
1440
|
password: 'wrong-password',
|
|
1455
1441
|
});
|
|
1456
|
-
console.log('
|
|
1442
|
+
console.log('Login successful:', result);
|
|
1457
1443
|
} catch (error) {
|
|
1458
1444
|
const errorCode = error.response?.data?.code;
|
|
1459
1445
|
|
|
1460
1446
|
switch (errorCode) {
|
|
1461
1447
|
case ErrorCode.INVALID_CREDENTIALS:
|
|
1462
|
-
showError('
|
|
1448
|
+
showError('Incorrect username or password');
|
|
1463
1449
|
break;
|
|
1464
1450
|
case ErrorCode.EMAIL_NOT_VERIFIED:
|
|
1465
|
-
showError('
|
|
1451
|
+
showError('Please verify your email first');
|
|
1466
1452
|
showResendVerificationButton();
|
|
1467
1453
|
break;
|
|
1468
1454
|
case ErrorCode.ACCOUNT_SUSPENDED:
|
|
1469
|
-
showError('
|
|
1455
|
+
showError('Your account has been suspended, please contact administrator');
|
|
1470
1456
|
break;
|
|
1471
1457
|
default:
|
|
1472
|
-
showError('
|
|
1458
|
+
showError('Login failed, please try again later');
|
|
1473
1459
|
}
|
|
1474
1460
|
}
|
|
1475
1461
|
```
|
|
1476
1462
|
|
|
1477
|
-
#### 3.
|
|
1463
|
+
#### 3. Generic Error Handling Function
|
|
1478
1464
|
|
|
1479
1465
|
```typescript
|
|
1480
1466
|
import { ErrorCode } from '@seaverse/auth-sdk';
|
|
@@ -1483,44 +1469,44 @@ function handleApiError(error: any) {
|
|
|
1483
1469
|
const apiError = error.response?.data;
|
|
1484
1470
|
|
|
1485
1471
|
if (!apiError) {
|
|
1486
|
-
//
|
|
1472
|
+
// Network error or other unknown error
|
|
1487
1473
|
return {
|
|
1488
|
-
title: '
|
|
1489
|
-
message: '
|
|
1474
|
+
title: 'Network Error',
|
|
1475
|
+
message: 'Please check your network connection',
|
|
1490
1476
|
};
|
|
1491
1477
|
}
|
|
1492
1478
|
|
|
1493
|
-
//
|
|
1479
|
+
// Return user-friendly messages based on error code
|
|
1494
1480
|
const errorMessages: Record<string, { title: string; message: string }> = {
|
|
1495
1481
|
[ErrorCode.ACCOUNT_EXISTS]: {
|
|
1496
|
-
title: '
|
|
1497
|
-
message: '
|
|
1482
|
+
title: 'Account Exists',
|
|
1483
|
+
message: 'This email is already registered, please login directly',
|
|
1498
1484
|
},
|
|
1499
1485
|
[ErrorCode.INVALID_CREDENTIALS]: {
|
|
1500
|
-
title: '
|
|
1501
|
-
message: '
|
|
1486
|
+
title: 'Login Failed',
|
|
1487
|
+
message: 'Incorrect username or password',
|
|
1502
1488
|
},
|
|
1503
1489
|
[ErrorCode.EMAIL_NOT_VERIFIED]: {
|
|
1504
|
-
title: '
|
|
1505
|
-
message: '
|
|
1490
|
+
title: 'Email Not Verified',
|
|
1491
|
+
message: 'Please verify your email address first',
|
|
1506
1492
|
},
|
|
1507
1493
|
[ErrorCode.INVALID_INVITATION_CODE]: {
|
|
1508
|
-
title: '
|
|
1509
|
-
message: '
|
|
1494
|
+
title: 'Invalid Invitation Code',
|
|
1495
|
+
message: 'Please check if your invitation code is correct',
|
|
1510
1496
|
},
|
|
1511
1497
|
[ErrorCode.TOKEN_EXPIRED]: {
|
|
1512
|
-
title: '
|
|
1513
|
-
message: '
|
|
1498
|
+
title: 'Login Expired',
|
|
1499
|
+
message: 'Please login again',
|
|
1514
1500
|
},
|
|
1515
1501
|
};
|
|
1516
1502
|
|
|
1517
1503
|
return errorMessages[apiError.code] || {
|
|
1518
|
-
title: '
|
|
1519
|
-
message: apiError.error || '
|
|
1504
|
+
title: 'Operation Failed',
|
|
1505
|
+
message: apiError.error || 'Unknown error occurred',
|
|
1520
1506
|
};
|
|
1521
1507
|
}
|
|
1522
1508
|
|
|
1523
|
-
//
|
|
1509
|
+
// Usage example
|
|
1524
1510
|
try {
|
|
1525
1511
|
await client.register({ email, password });
|
|
1526
1512
|
} catch (error) {
|
|
@@ -1529,7 +1515,7 @@ try {
|
|
|
1529
1515
|
}
|
|
1530
1516
|
```
|
|
1531
1517
|
|
|
1532
|
-
#### 4. TypeScript
|
|
1518
|
+
#### 4. TypeScript Type-Safe Error Handling
|
|
1533
1519
|
|
|
1534
1520
|
```typescript
|
|
1535
1521
|
import { ErrorCode, models } from '@seaverse/auth-sdk';
|
|
@@ -1569,164 +1555,164 @@ if (registerResult.success) {
|
|
|
1569
1555
|
}
|
|
1570
1556
|
```
|
|
1571
1557
|
|
|
1572
|
-
### HTTP
|
|
1558
|
+
### HTTP Status Code Reference
|
|
1573
1559
|
|
|
1574
|
-
| HTTP
|
|
1560
|
+
| HTTP Status Code | Error Code Example | Description |
|
|
1575
1561
|
|-----------|----------|------|
|
|
1576
|
-
| 200 OK | `ACCOUNT_EXISTS` |
|
|
1577
|
-
| 400 Bad Request | `INVALID_EMAIL`, `PASSWORD_TOO_WEAK` |
|
|
1578
|
-
| 401 Unauthorized | `INVALID_CREDENTIALS`, `TOKEN_EXPIRED` |
|
|
1579
|
-
| 403 Forbidden | `EMAIL_NOT_VERIFIED`, `ACCOUNT_SUSPENDED` |
|
|
1580
|
-
| 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 |
|
|
1581
1567
|
|
|
1582
|
-
|
|
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.
|
|
1583
1569
|
|
|
1584
|
-
##
|
|
1570
|
+
## Type Definitions
|
|
1585
1571
|
|
|
1586
1572
|
```typescript
|
|
1587
|
-
//
|
|
1573
|
+
// User info
|
|
1588
1574
|
interface User {
|
|
1589
1575
|
id?: string;
|
|
1590
|
-
app_id?: string | null; //
|
|
1576
|
+
app_id?: string | null; // Application ID (multi-tenant support)
|
|
1591
1577
|
email?: string;
|
|
1592
1578
|
username?: string;
|
|
1593
|
-
created_at?: number; // Unix
|
|
1594
|
-
email_verified?: boolean; //
|
|
1595
|
-
google_id?: string | null; // Google
|
|
1596
|
-
discord_id?: string | null; // Discord
|
|
1597
|
-
github_id?: string | null; // GitHub
|
|
1598
|
-
|
|
1599
|
-
//
|
|
1600
|
-
createdAt?: number; // @deprecated
|
|
1601
|
-
emailVerified?: boolean; // @deprecated
|
|
1602
|
-
googleId?: string; // @deprecated
|
|
1603
|
-
discordId?: string; // @deprecated
|
|
1604
|
-
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
|
|
1605
1591
|
}
|
|
1606
1592
|
|
|
1607
|
-
//
|
|
1593
|
+
// Login response
|
|
1608
1594
|
interface LoginResponse {
|
|
1609
1595
|
token: string;
|
|
1610
1596
|
user: User;
|
|
1611
1597
|
}
|
|
1612
1598
|
|
|
1613
|
-
//
|
|
1599
|
+
// Registration response
|
|
1614
1600
|
interface RegisterResponse {
|
|
1615
1601
|
success: boolean;
|
|
1616
1602
|
message?: string;
|
|
1617
1603
|
userId?: string;
|
|
1618
|
-
//
|
|
1619
|
-
requiresEmailVerification?: boolean; //
|
|
1620
|
-
//
|
|
1621
|
-
requiresInvitationCode?: boolean; //
|
|
1622
|
-
tempUserId?: string; //
|
|
1623
|
-
//
|
|
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
|
|
1624
1610
|
error?: string;
|
|
1625
|
-
code?: string; //
|
|
1611
|
+
code?: string; // E.g. 'ACCOUNT_EXISTS'
|
|
1626
1612
|
details?: Record<string, any>;
|
|
1627
1613
|
}
|
|
1628
1614
|
|
|
1629
|
-
// AuthModal
|
|
1615
|
+
// AuthModal options
|
|
1630
1616
|
interface AuthModalOptions {
|
|
1631
1617
|
client: SeaVerseBackendAPIClient;
|
|
1632
1618
|
theme?: 'dark' | 'light';
|
|
1633
1619
|
onLoginSuccess?: (token: string, user: any) => void;
|
|
1634
1620
|
onSignupSuccess?: (token: string, user: any) => void;
|
|
1635
|
-
onInviteCodeRequired?: (userId: string, email: string) => void; //
|
|
1636
|
-
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
|
|
1637
1623
|
onError?: (error: Error) => void;
|
|
1638
|
-
returnUrl?: string; // OAuth
|
|
1624
|
+
returnUrl?: string; // URL to return after OAuth login, optional, defaults to window.location.href
|
|
1639
1625
|
enableOAuth?: {
|
|
1640
|
-
google?: boolean; //
|
|
1641
|
-
discord?: boolean; //
|
|
1642
|
-
github?: boolean; //
|
|
1626
|
+
google?: boolean; // Enable Google login
|
|
1627
|
+
discord?: boolean; // Enable Discord login
|
|
1628
|
+
github?: boolean; // Enable GitHub login
|
|
1643
1629
|
};
|
|
1644
1630
|
}
|
|
1645
1631
|
```
|
|
1646
1632
|
|
|
1647
|
-
##
|
|
1633
|
+
## Complete Examples
|
|
1648
1634
|
|
|
1649
|
-
|
|
1635
|
+
Check [examples/auth-sdk-demo](../../examples/auth-sdk-demo) directory for complete runnable examples.
|
|
1650
1636
|
|
|
1651
1637
|
```bash
|
|
1652
|
-
#
|
|
1638
|
+
# Run example
|
|
1653
1639
|
cd examples/auth-sdk-demo
|
|
1654
1640
|
pnpm install
|
|
1655
1641
|
pnpm dev
|
|
1656
1642
|
```
|
|
1657
1643
|
|
|
1658
|
-
##
|
|
1644
|
+
## Migration from Old Versions
|
|
1659
1645
|
|
|
1660
|
-
### v0.1.x → v0.2.0
|
|
1646
|
+
### v0.1.x → v0.2.0 Upgrade Guide
|
|
1661
1647
|
|
|
1662
|
-
#### 1.
|
|
1663
|
-
|
|
1648
|
+
#### 1. Backward Compatibility
|
|
1649
|
+
Good news! v0.2.0 is fully backward compatible, existing code **works without modifications**.
|
|
1664
1650
|
|
|
1665
|
-
#### 2.
|
|
1651
|
+
#### 2. Recommended Migration Steps
|
|
1666
1652
|
|
|
1667
|
-
|
|
1653
|
+
While not required, we recommend gradually migrating to new field naming conventions:
|
|
1668
1654
|
|
|
1669
1655
|
```typescript
|
|
1670
|
-
//
|
|
1656
|
+
// Old code (still works)
|
|
1671
1657
|
const user = await client.getCurrentUser();
|
|
1672
|
-
console.log(user.emailVerified); // ⚠️
|
|
1673
|
-
console.log(user.createdAt); // ⚠️
|
|
1658
|
+
console.log(user.emailVerified); // ⚠️ Deprecated but works
|
|
1659
|
+
console.log(user.createdAt); // ⚠️ Deprecated but works
|
|
1674
1660
|
|
|
1675
|
-
//
|
|
1661
|
+
// New code (recommended)
|
|
1676
1662
|
const user = await client.getCurrentUser();
|
|
1677
|
-
console.log(user.email_verified); // ✅
|
|
1678
|
-
console.log(user.created_at); // ✅
|
|
1679
|
-
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
|
|
1680
1666
|
```
|
|
1681
1667
|
|
|
1682
|
-
#### 3.
|
|
1668
|
+
#### 3. Registration API Update
|
|
1683
1669
|
|
|
1684
1670
|
```typescript
|
|
1685
|
-
//
|
|
1671
|
+
// Old code (still works)
|
|
1686
1672
|
await client.register({
|
|
1687
1673
|
email: 'user@example.com',
|
|
1688
1674
|
password: 'password123',
|
|
1689
|
-
invitationCode: 'INVITE123', // ⚠️
|
|
1675
|
+
invitationCode: 'INVITE123', // ⚠️ Deprecated but works
|
|
1690
1676
|
});
|
|
1691
1677
|
|
|
1692
|
-
//
|
|
1678
|
+
// New code (recommended)
|
|
1693
1679
|
await client.register({
|
|
1694
1680
|
email: 'user@example.com',
|
|
1695
1681
|
password: 'password123',
|
|
1696
|
-
username: 'myusername', // ✨
|
|
1697
|
-
invitation_code: 'INVITE123', // ✅
|
|
1682
|
+
username: 'myusername', // ✨ New feature
|
|
1683
|
+
invitation_code: 'INVITE123', // ✅ Recommended
|
|
1698
1684
|
});
|
|
1699
1685
|
```
|
|
1700
1686
|
|
|
1701
|
-
#### 4.
|
|
1687
|
+
#### 4. Password Reset API Update
|
|
1702
1688
|
|
|
1703
1689
|
```typescript
|
|
1704
|
-
//
|
|
1690
|
+
// Old code (needs update)
|
|
1705
1691
|
await client.resetPassword({
|
|
1706
1692
|
token: 'reset-token',
|
|
1707
|
-
newPassword: 'NewPass123', // ⚠️
|
|
1693
|
+
newPassword: 'NewPass123', // ⚠️ Deprecated
|
|
1708
1694
|
});
|
|
1709
1695
|
|
|
1710
|
-
//
|
|
1696
|
+
// New code (recommended)
|
|
1711
1697
|
await client.resetPassword({
|
|
1712
1698
|
token: 'reset-token',
|
|
1713
|
-
new_password: 'NewPass123', // ✅
|
|
1699
|
+
new_password: 'NewPass123', // ✅ Recommended
|
|
1714
1700
|
});
|
|
1715
1701
|
```
|
|
1716
1702
|
|
|
1717
|
-
#### 5. TypeScript
|
|
1703
|
+
#### 5. TypeScript Type Hints
|
|
1718
1704
|
|
|
1719
|
-
|
|
1705
|
+
If you use TypeScript, compiler will automatically hint deprecated fields:
|
|
1720
1706
|
|
|
1721
1707
|
```typescript
|
|
1722
1708
|
const user = await client.getCurrentUser();
|
|
1723
|
-
user.emailVerified; // TypeScript
|
|
1724
|
-
user.email_verified; // ✅
|
|
1709
|
+
user.emailVerified; // TypeScript will show strikethrough and deprecation warning
|
|
1710
|
+
user.email_verified; // ✅ No warning
|
|
1725
1711
|
```
|
|
1726
1712
|
|
|
1727
|
-
#### 6.
|
|
1713
|
+
#### 6. Multi-Tenant Feature (New)
|
|
1728
1714
|
|
|
1729
|
-
|
|
1715
|
+
If your application needs multi-tenant architecture support, you can use the new `app_id` field:
|
|
1730
1716
|
|
|
1731
1717
|
```typescript
|
|
1732
1718
|
const user = await client.getCurrentUser();
|
|
@@ -1734,20 +1720,20 @@ const user = await client.getCurrentUser();
|
|
|
1734
1720
|
console.log('your app id:', user.app_id);
|
|
1735
1721
|
```
|
|
1736
1722
|
|
|
1737
|
-
##
|
|
1723
|
+
## FAQ
|
|
1738
1724
|
|
|
1739
|
-
###
|
|
1725
|
+
### How to Handle Authentication Token?
|
|
1740
1726
|
|
|
1741
1727
|
```typescript
|
|
1742
|
-
//
|
|
1728
|
+
// Save token after successful login
|
|
1743
1729
|
const loginResult = await client.login({ email, password });
|
|
1744
1730
|
localStorage.setItem('token', loginResult.token);
|
|
1745
1731
|
|
|
1746
|
-
//
|
|
1732
|
+
// Create authenticated client
|
|
1747
1733
|
import { AuthFactory } from '@seaverse/auth-sdk';
|
|
1748
1734
|
|
|
1749
1735
|
const authenticatedClient = new SeaVerseBackendAPIClient({
|
|
1750
|
-
appId: 'your app id', //
|
|
1736
|
+
appId: 'your app id', // Required: Application ID
|
|
1751
1737
|
environment: 'production',
|
|
1752
1738
|
auth: AuthFactory.create({
|
|
1753
1739
|
type: 'jwt',
|
|
@@ -1759,49 +1745,49 @@ const authenticatedClient = new SeaVerseBackendAPIClient({
|
|
|
1759
1745
|
});
|
|
1760
1746
|
```
|
|
1761
1747
|
|
|
1762
|
-
### OAuth redirect_uri_mismatch
|
|
1748
|
+
### OAuth redirect_uri_mismatch Error?
|
|
1763
1749
|
|
|
1764
|
-
|
|
1750
|
+
Ensure the redirect URI in OAuth application configuration exactly matches the `redirectUri` in code (including protocol, domain, port, path).
|
|
1765
1751
|
|
|
1766
|
-
###
|
|
1752
|
+
### How to Customize Request Timeout?
|
|
1767
1753
|
|
|
1768
1754
|
```typescript
|
|
1769
1755
|
const client = new SeaVerseBackendAPIClient({
|
|
1770
|
-
appId: 'your app id', //
|
|
1756
|
+
appId: 'your app id', // Required: Application ID
|
|
1771
1757
|
environment: 'production',
|
|
1772
|
-
timeout: 30000, // 30
|
|
1758
|
+
timeout: 30000, // 30 seconds
|
|
1773
1759
|
});
|
|
1774
1760
|
```
|
|
1775
1761
|
|
|
1776
|
-
###
|
|
1762
|
+
### How to Connect to Local API in Local Development?
|
|
1777
1763
|
|
|
1778
1764
|
```typescript
|
|
1779
1765
|
const client = new SeaVerseBackendAPIClient({
|
|
1780
|
-
appId: 'your app id', //
|
|
1781
|
-
environment: 'local', //
|
|
1766
|
+
appId: 'your app id', // Required: Application ID
|
|
1767
|
+
environment: 'local', // Or use baseURL: 'http://localhost:3000'
|
|
1782
1768
|
});
|
|
1783
1769
|
```
|
|
1784
1770
|
|
|
1785
|
-
##
|
|
1771
|
+
## Development
|
|
1786
1772
|
|
|
1787
1773
|
```bash
|
|
1788
|
-
#
|
|
1774
|
+
# Install dependencies
|
|
1789
1775
|
pnpm install
|
|
1790
1776
|
|
|
1791
|
-
#
|
|
1777
|
+
# Build
|
|
1792
1778
|
pnpm build
|
|
1793
1779
|
|
|
1794
|
-
# Watch
|
|
1780
|
+
# Watch mode
|
|
1795
1781
|
pnpm dev
|
|
1796
1782
|
|
|
1797
|
-
#
|
|
1783
|
+
# Test
|
|
1798
1784
|
pnpm test
|
|
1799
1785
|
```
|
|
1800
1786
|
|
|
1801
|
-
##
|
|
1787
|
+
## Related Links
|
|
1802
1788
|
|
|
1803
1789
|
- 📦 [NPM Package](https://www.npmjs.com/package/@seaverse/auth-sdk)
|
|
1804
|
-
- 📖 [
|
|
1790
|
+
- 📖 [Example Project](../../examples/auth-sdk-demo)
|
|
1805
1791
|
- 🐛 [Issues](https://github.com/seaverseai/sv-sdk/issues)
|
|
1806
1792
|
- 💬 [Discussions](https://github.com/seaverseai/sv-sdk/discussions)
|
|
1807
1793
|
|
|
@@ -1809,48 +1795,48 @@ pnpm test
|
|
|
1809
1795
|
|
|
1810
1796
|
MIT © [SeaVerse Team](mailto:support@seaverse.com)
|
|
1811
1797
|
|
|
1812
|
-
##
|
|
1798
|
+
## Changelog
|
|
1813
1799
|
|
|
1814
|
-
### v0.3.6 (
|
|
1815
|
-
- 🧹
|
|
1816
|
-
-
|
|
1817
|
-
-
|
|
1818
|
-
-
|
|
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
|
|
1819
1805
|
|
|
1820
1806
|
### v0.2.5
|
|
1821
|
-
- 🔄
|
|
1822
|
-
-
|
|
1823
|
-
- `login()`, `register()`, `getCurrentUser()`
|
|
1824
|
-
-
|
|
1825
|
-
- 📝
|
|
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
|
|
1826
1812
|
|
|
1827
1813
|
### v0.2.0
|
|
1828
|
-
- 🔄 **API
|
|
1829
|
-
- 🏢
|
|
1830
|
-
- 🔑
|
|
1831
|
-
- ✨
|
|
1832
|
-
-
|
|
1833
|
-
-
|
|
1834
|
-
- 📝
|
|
1835
|
-
- `SeaVerseBackendAPIClientOptions`:
|
|
1836
|
-
- `User`:
|
|
1837
|
-
- `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`
|
|
1838
1824
|
- `ResetPasswordRequest`: `newPassword` → `new_password`
|
|
1839
1825
|
- ⚠️ **Breaking Changes**:
|
|
1840
|
-
-
|
|
1841
|
-
- API
|
|
1842
|
-
- ✅
|
|
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
|
|
1843
1829
|
|
|
1844
1830
|
### v0.1.5
|
|
1845
|
-
-
|
|
1846
|
-
-
|
|
1831
|
+
- Fix OAuth state management, switch from sessionStorage to localStorage
|
|
1832
|
+
- Enhance API response handling
|
|
1847
1833
|
|
|
1848
1834
|
### v0.1.4
|
|
1849
|
-
-
|
|
1850
|
-
-
|
|
1835
|
+
- Enhance API response handling
|
|
1836
|
+
- Optimize error handling
|
|
1851
1837
|
|
|
1852
1838
|
### v0.1.2
|
|
1853
|
-
-
|
|
1854
|
-
-
|
|
1839
|
+
- Update package name to @seaverse/auth-sdk
|
|
1840
|
+
- Add multi-environment support
|
|
1855
1841
|
|
|
1856
|
-
|
|
1842
|
+
View complete changelog: [CHANGELOG.md](./CHANGELOG.md)
|