@uptickjs/webauth-sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +35 -0
- package/src/index.ts +191 -0
- package/src/modules/Auth.ts +385 -0
- package/src/modules/OTP.ts +83 -0
- package/src/modules/Sign.ts +126 -0
- package/src/modules/Wallet.ts +89 -0
- package/src/types/index.ts +125 -0
- package/src/utils/config.ts +87 -0
- package/src/utils/http.ts +106 -0
- package/src/utils/storage.ts +111 -0
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@uptickjs/webauth-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "WebAuth JavaScript SDK for React Native - Social login, Wallet and Sign modules",
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"types": "src/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"src"
|
|
9
|
+
],
|
|
10
|
+
"keywords": [
|
|
11
|
+
"webauth",
|
|
12
|
+
"react-native",
|
|
13
|
+
"authentication",
|
|
14
|
+
"google-login",
|
|
15
|
+
"apple-login",
|
|
16
|
+
"otp",
|
|
17
|
+
"wallet",
|
|
18
|
+
"signing"
|
|
19
|
+
],
|
|
20
|
+
"author": "Uptick Dev Team",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"react-native": ">=0.60.0"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/react-native": "^0.73.0",
|
|
30
|
+
"typescript": "^5.0.0"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"axios": "^1.6.0"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebAuth SDK - Main Entry Point
|
|
3
|
+
* A comprehensive authentication SDK for React Native
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export * from './types';
|
|
7
|
+
export { WebAuthConfigManager, createConfig } from './utils/config';
|
|
8
|
+
export { createTokenStorage, RNTokenStorage, MemoryTokenStorage } from './utils/storage';
|
|
9
|
+
export { createHttpClient, HttpClient } from './utils/http';
|
|
10
|
+
export { AuthModule, createAuthModule } from './modules/Auth';
|
|
11
|
+
export { OTPModule, createOTPModule } from './modules/OTP';
|
|
12
|
+
export { WalletModule, createWalletModule } from './modules/Wallet';
|
|
13
|
+
export { SignModule, createSignModule } from './modules/Sign';
|
|
14
|
+
|
|
15
|
+
import { WebAuthConfig, TokenStorage, EventListener, AuthEvent } from './types';
|
|
16
|
+
import { WebAuthConfigManager, createConfig } from './utils/config';
|
|
17
|
+
import { createTokenStorage, RNTokenStorage, MemoryTokenStorage } from './utils/storage';
|
|
18
|
+
import { createHttpClient, HttpClient } from './utils/http';
|
|
19
|
+
import { AuthModule, createAuthModule } from './modules/Auth';
|
|
20
|
+
import { OTPModule, createOTPModule } from './modules/OTP';
|
|
21
|
+
import { WalletModule, createWalletModule } from './modules/Wallet';
|
|
22
|
+
import { SignModule, createSignModule } from './modules/Sign';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Main WebAuth Client
|
|
26
|
+
* Provides unified access to all authentication features
|
|
27
|
+
*/
|
|
28
|
+
export class WebAuthClient {
|
|
29
|
+
private configManager: WebAuthConfigManager;
|
|
30
|
+
private tokenStorage: TokenStorage;
|
|
31
|
+
private httpClient: HttpClient;
|
|
32
|
+
private auth: AuthModule;
|
|
33
|
+
private otp: OTPModule;
|
|
34
|
+
private wallet: WalletModule;
|
|
35
|
+
private sign: SignModule;
|
|
36
|
+
private eventListeners: Map<string, EventListener> = new Map();
|
|
37
|
+
private isInitialized: boolean = false;
|
|
38
|
+
|
|
39
|
+
constructor(config: WebAuthConfig, tokenStorage?: TokenStorage) {
|
|
40
|
+
this.configManager = createConfig(config);
|
|
41
|
+
this.tokenStorage = tokenStorage || createTokenStorage('memory');
|
|
42
|
+
|
|
43
|
+
// Create HTTP client with token accessor
|
|
44
|
+
this.httpClient = createHttpClient(
|
|
45
|
+
config.apiBaseUrl,
|
|
46
|
+
() => this.tokenStorage.getAccessToken()
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// Initialize modules
|
|
50
|
+
this.auth = createAuthModule(this.httpClient, this.configManager);
|
|
51
|
+
this.otp = createOTPModule(this.httpClient);
|
|
52
|
+
this.wallet = createWalletModule(this.httpClient);
|
|
53
|
+
this.sign = createSignModule(this.httpClient);
|
|
54
|
+
|
|
55
|
+
// Setup auth callback
|
|
56
|
+
this.auth.setAuthCallback(async (response) => {
|
|
57
|
+
await this.handleAuthSuccess(response);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Initialize the WebAuth client
|
|
63
|
+
* Call this at app startup
|
|
64
|
+
*/
|
|
65
|
+
async initialize(): Promise<void> {
|
|
66
|
+
if (this.isInitialized) return;
|
|
67
|
+
|
|
68
|
+
// Validate tokens and refresh if needed
|
|
69
|
+
const accessToken = await this.tokenStorage.getAccessToken();
|
|
70
|
+
const refreshToken = await this.tokenStorage.getRefreshToken();
|
|
71
|
+
|
|
72
|
+
if (!accessToken && refreshToken) {
|
|
73
|
+
try {
|
|
74
|
+
const response = await this.auth.refreshToken(refreshToken);
|
|
75
|
+
await this.handleAuthSuccess(response);
|
|
76
|
+
} catch {
|
|
77
|
+
await this.tokenStorage.clearTokens();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.isInitialized = true;
|
|
82
|
+
this.emit({ type: 'login' });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get Auth module
|
|
87
|
+
*/
|
|
88
|
+
getAuth(): AuthModule {
|
|
89
|
+
return this.auth;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get OTP module
|
|
94
|
+
*/
|
|
95
|
+
getOTP(): OTPModule {
|
|
96
|
+
return this.otp;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get Wallet module
|
|
101
|
+
*/
|
|
102
|
+
getWallet(): WalletModule {
|
|
103
|
+
return this.wallet;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get Sign module
|
|
108
|
+
*/
|
|
109
|
+
getSign(): SignModule {
|
|
110
|
+
return this.sign;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Check if user is authenticated
|
|
115
|
+
*/
|
|
116
|
+
async isAuthenticated(): Promise<boolean> {
|
|
117
|
+
const token = await this.tokenStorage.getAccessToken();
|
|
118
|
+
return !!token;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Handle authentication redirect
|
|
123
|
+
* Call this when your app receives a deep link callback
|
|
124
|
+
*/
|
|
125
|
+
async handleRedirect(url: string): Promise<void> {
|
|
126
|
+
const response = await this.auth.handleAuthRedirect(url);
|
|
127
|
+
if (response) {
|
|
128
|
+
await this.handleAuthSuccess(response);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Add event listener
|
|
134
|
+
*/
|
|
135
|
+
addEventListener(id: string, listener: EventListener): void {
|
|
136
|
+
this.eventListeners.set(id, listener);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Remove event listener
|
|
141
|
+
*/
|
|
142
|
+
removeEventListener(id: string): void {
|
|
143
|
+
this.eventListeners.delete(id);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Logout current user
|
|
148
|
+
*/
|
|
149
|
+
async logout(): Promise<void> {
|
|
150
|
+
try {
|
|
151
|
+
await this.auth.logout();
|
|
152
|
+
} catch {
|
|
153
|
+
// Ignore logout errors
|
|
154
|
+
}
|
|
155
|
+
await this.tokenStorage.clearTokens();
|
|
156
|
+
this.emit({ type: 'logout' });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private async handleAuthSuccess(response: any): Promise<void> {
|
|
160
|
+
console.log('wxl 160---- responseSDK');
|
|
161
|
+
if (response.access_token) {
|
|
162
|
+
await this.tokenStorage.setAccessToken(response.access_token);
|
|
163
|
+
}
|
|
164
|
+
if (response.refresh_token) {
|
|
165
|
+
await this.tokenStorage.setRefreshToken(response.refresh_token);
|
|
166
|
+
}
|
|
167
|
+
this.emit({ type: 'login', data: response });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private emit(event: AuthEvent): void {
|
|
171
|
+
this.eventListeners.forEach((listener) => {
|
|
172
|
+
try {
|
|
173
|
+
listener(event);
|
|
174
|
+
} catch (error) {
|
|
175
|
+
console.error('WebAuth: Event listener error', error);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Create a new WebAuth client instance
|
|
183
|
+
*/
|
|
184
|
+
export function createWebAuth(config: WebAuthConfig, tokenStorage?: TokenStorage): WebAuthClient {
|
|
185
|
+
return new WebAuthClient(config, tokenStorage);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Default export
|
|
190
|
+
*/
|
|
191
|
+
export default WebAuthClient;
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebAuth SDK - Authentication Module
|
|
3
|
+
* Supports Google, Apple, and Email login
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Linking } from 'react-native';
|
|
7
|
+
import {
|
|
8
|
+
AuthResponse,
|
|
9
|
+
UserInfo,
|
|
10
|
+
SendCodeRequest,
|
|
11
|
+
SendCodeResponse,
|
|
12
|
+
EmailLoginRequest,
|
|
13
|
+
AppleLoginRequest,
|
|
14
|
+
WebAuthConfig,
|
|
15
|
+
} from '../types';
|
|
16
|
+
import { HttpClient } from '../utils/http';
|
|
17
|
+
import { WebAuthConfigManager } from '../utils/config';
|
|
18
|
+
import { createTokenStorage } from '../utils/storage';
|
|
19
|
+
|
|
20
|
+
export class AuthModule {
|
|
21
|
+
private httpClient: HttpClient;
|
|
22
|
+
private configManager: WebAuthConfigManager;
|
|
23
|
+
private onAuthCallback?: (response: AuthResponse) => void;
|
|
24
|
+
|
|
25
|
+
constructor(httpClient: HttpClient, configManager: WebAuthConfigManager) {
|
|
26
|
+
this.httpClient = httpClient;
|
|
27
|
+
this.configManager = configManager;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Set callback for auth response (used in redirect flow)
|
|
32
|
+
*/
|
|
33
|
+
setAuthCallback(callback: (response: AuthResponse) => void): void {
|
|
34
|
+
this.onAuthCallback = callback;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Handle auth redirect from deep link
|
|
39
|
+
* Call this in your Linking listener when URL contains auth callback
|
|
40
|
+
*/
|
|
41
|
+
async handleAuthRedirect(url: string): Promise<AuthResponse | null> {
|
|
42
|
+
try {
|
|
43
|
+
const urlObj = new URL(url);
|
|
44
|
+
const params = urlObj.searchParams;
|
|
45
|
+
|
|
46
|
+
const accessToken = params.get('access_token');
|
|
47
|
+
const email = params.get('email');
|
|
48
|
+
const owner = params.get('owner');
|
|
49
|
+
const error = params.get('error');
|
|
50
|
+
|
|
51
|
+
if (error) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
params.get('error_description') || 'Authentication failed',
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (accessToken) {
|
|
58
|
+
const response: AuthResponse = {
|
|
59
|
+
access_token: accessToken,
|
|
60
|
+
refresh_token: params.get('refresh_token') || undefined,
|
|
61
|
+
email: email,
|
|
62
|
+
owner: owner,
|
|
63
|
+
expires_in: params.get('expires_in')
|
|
64
|
+
? parseInt(params.get('expires_in')!, 10)
|
|
65
|
+
: undefined,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
if (this.onAuthCallback) {
|
|
69
|
+
this.onAuthCallback(response);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return response;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return null;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error('WebAuth: Handle redirect error', error);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Initiate Google login via browser redirect.
|
|
84
|
+
* Registers a one-time Linking listener, opens the auth page, then resolves when the app
|
|
85
|
+
* receives a deep link matching `config.redirectUri` with tokens (or rejects on OAuth error).
|
|
86
|
+
*
|
|
87
|
+
* Note: If the OS terminates the app while the browser is open, the returned Promise is
|
|
88
|
+
* lost on cold start — call `handleAuthRedirect` / app-level `handleRedirect` from
|
|
89
|
+
* `Linking.getInitialURL()` on launch as a fallback.
|
|
90
|
+
*/
|
|
91
|
+
async loginWithGoogleRedirect(): Promise<AuthResponse | null> {
|
|
92
|
+
const config = this.configManager.getConfig();
|
|
93
|
+
const googleAuthUrl =
|
|
94
|
+
`${this.configManager.getGoogleAuthUrl()}?` +
|
|
95
|
+
`apiKey=${config.apiKey}&` +
|
|
96
|
+
`appLink=${encodeURIComponent(config.redirectUri)}`;
|
|
97
|
+
|
|
98
|
+
const timeoutMs = 15 * 60 * 1000;
|
|
99
|
+
|
|
100
|
+
return new Promise<AuthResponse | null>((resolve, reject) => {
|
|
101
|
+
let settled = false;
|
|
102
|
+
let subscription: { remove: () => void } | undefined;
|
|
103
|
+
|
|
104
|
+
const cleanup = () => {
|
|
105
|
+
clearTimeout(timeoutId);
|
|
106
|
+
subscription?.remove();
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const timeoutId = setTimeout(() => {
|
|
110
|
+
if (settled) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
settled = true;
|
|
114
|
+
cleanup();
|
|
115
|
+
resolve(null);
|
|
116
|
+
}, timeoutMs);
|
|
117
|
+
|
|
118
|
+
const onIncomingUrl = async (incoming: string | null) => {
|
|
119
|
+
if (!incoming || settled) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (!this.isAuthDeepLink(incoming, config.redirectUri)) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
const urlObj = new URL(incoming);
|
|
127
|
+
const oauthError = urlObj.searchParams.get('error');
|
|
128
|
+
if (oauthError) {
|
|
129
|
+
if (!settled) {
|
|
130
|
+
settled = true;
|
|
131
|
+
cleanup();
|
|
132
|
+
reject(
|
|
133
|
+
new Error(
|
|
134
|
+
urlObj.searchParams.get('error_description') ||
|
|
135
|
+
oauthError ||
|
|
136
|
+
'Authentication failed',
|
|
137
|
+
),
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
} catch {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const response = await this.handleAuthRedirect(incoming);
|
|
147
|
+
if (response && !settled) {
|
|
148
|
+
settled = true;
|
|
149
|
+
cleanup();
|
|
150
|
+
resolve(response);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
subscription = Linking.addEventListener('url', ({ url }) => {
|
|
155
|
+
void onIncomingUrl(url);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
void (async () => {
|
|
159
|
+
try {
|
|
160
|
+
await this.openBrowser(googleAuthUrl);
|
|
161
|
+
} catch (e) {
|
|
162
|
+
if (!settled) {
|
|
163
|
+
settled = true;
|
|
164
|
+
cleanup();
|
|
165
|
+
reject(e instanceof Error ? e : new Error(String(e)));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
})();
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** True when `incoming` targets the same scheme+host as configured `redirectUri`. */
|
|
173
|
+
private isAuthDeepLink(incoming: string, redirectUri: string): boolean {
|
|
174
|
+
try {
|
|
175
|
+
const u = new URL(incoming);
|
|
176
|
+
const r = new URL(redirectUri);
|
|
177
|
+
return (
|
|
178
|
+
u.protocol === r.protocol &&
|
|
179
|
+
u.hostname.toLowerCase() === r.hostname.toLowerCase()
|
|
180
|
+
);
|
|
181
|
+
} catch {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Initiate Apple login via browser redirect.
|
|
188
|
+
* Same contract as `loginWithGoogleRedirect`: listens for deep link on `config.redirectUri`,
|
|
189
|
+
* resolves with `AuthResponse` when tokens arrive (or `null` on timeout).
|
|
190
|
+
*/
|
|
191
|
+
async loginWithAppleRedirect(): Promise<AuthResponse | null> {
|
|
192
|
+
const config = this.configManager.getConfig();
|
|
193
|
+
const state = this.generateState();
|
|
194
|
+
|
|
195
|
+
const appleAuthUrl =
|
|
196
|
+
`${this.configManager.getAppleAuthUrl()}?` +
|
|
197
|
+
`apiKey=${config.apiKey}&` +
|
|
198
|
+
`response_type=code%20id_token&` +
|
|
199
|
+
`scope=name%20email&` +
|
|
200
|
+
`state=${state}&` +
|
|
201
|
+
`response_mode=form_post&` +
|
|
202
|
+
`appLink=${encodeURIComponent(config.redirectUri)}`;
|
|
203
|
+
|
|
204
|
+
const timeoutMs = 15 * 60 * 1000;
|
|
205
|
+
|
|
206
|
+
return new Promise<AuthResponse | null>((resolve, reject) => {
|
|
207
|
+
let settled = false;
|
|
208
|
+
let subscription: { remove: () => void } | undefined;
|
|
209
|
+
|
|
210
|
+
const cleanup = () => {
|
|
211
|
+
clearTimeout(timeoutId);
|
|
212
|
+
subscription?.remove();
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const timeoutId = setTimeout(() => {
|
|
216
|
+
if (settled) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
settled = true;
|
|
220
|
+
cleanup();
|
|
221
|
+
resolve(null);
|
|
222
|
+
}, timeoutMs);
|
|
223
|
+
|
|
224
|
+
const onIncomingUrl = async (incoming: string | null) => {
|
|
225
|
+
if (!incoming || settled) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (!this.isAuthDeepLink(incoming, config.redirectUri)) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
try {
|
|
232
|
+
const urlObj = new URL(incoming);
|
|
233
|
+
const oauthError = urlObj.searchParams.get('error');
|
|
234
|
+
if (oauthError) {
|
|
235
|
+
if (!settled) {
|
|
236
|
+
settled = true;
|
|
237
|
+
cleanup();
|
|
238
|
+
reject(
|
|
239
|
+
new Error(
|
|
240
|
+
urlObj.searchParams.get('error_description') ||
|
|
241
|
+
oauthError ||
|
|
242
|
+
'Authentication failed',
|
|
243
|
+
),
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const returnedState = urlObj.searchParams.get('state');
|
|
249
|
+
if (returnedState != null && returnedState !== state) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
} catch {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const response = await this.handleAuthRedirect(incoming);
|
|
257
|
+
if (response && !settled) {
|
|
258
|
+
settled = true;
|
|
259
|
+
cleanup();
|
|
260
|
+
resolve(response);
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
subscription = Linking.addEventListener('url', ({ url }) => {
|
|
265
|
+
void onIncomingUrl(url);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
void (async () => {
|
|
269
|
+
try {
|
|
270
|
+
await this.openBrowser(appleAuthUrl);
|
|
271
|
+
} catch (e) {
|
|
272
|
+
if (!settled) {
|
|
273
|
+
settled = true;
|
|
274
|
+
cleanup();
|
|
275
|
+
reject(e instanceof Error ? e : new Error(String(e)));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
})();
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Login with Apple Identity Token
|
|
284
|
+
*/
|
|
285
|
+
async loginWithApple(
|
|
286
|
+
identityToken: string,
|
|
287
|
+
authorizationCode: string,
|
|
288
|
+
): Promise<AuthResponse> {
|
|
289
|
+
const request: AppleLoginRequest = {
|
|
290
|
+
identityToken,
|
|
291
|
+
authorizationCode,
|
|
292
|
+
};
|
|
293
|
+
const response = await this.httpClient.post<AuthResponse>(
|
|
294
|
+
'/auth/apple/token',
|
|
295
|
+
request,
|
|
296
|
+
);
|
|
297
|
+
return response;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Send verification code to email
|
|
302
|
+
*/
|
|
303
|
+
async sendEmailCode(email: string): Promise<SendCodeResponse> {
|
|
304
|
+
const request: SendCodeRequest = { email };
|
|
305
|
+
const response = await this.httpClient.post<SendCodeResponse>(
|
|
306
|
+
'/auth/email/send-code',
|
|
307
|
+
request,
|
|
308
|
+
);
|
|
309
|
+
return response;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Verify email code and login
|
|
314
|
+
*/
|
|
315
|
+
async loginWithEmail(email: string, code: string): Promise<AuthResponse> {
|
|
316
|
+
const request: EmailLoginRequest = { email, code };
|
|
317
|
+
const response = await this.httpClient.post<AuthResponse>(
|
|
318
|
+
'/auth/email/login',
|
|
319
|
+
request,
|
|
320
|
+
);
|
|
321
|
+
let data = response.data;
|
|
322
|
+
//将返回的accessToken和refreshToken存储到本地
|
|
323
|
+
const ts = createTokenStorage('react-native');
|
|
324
|
+
if (data?.tokens?.accessToken) {
|
|
325
|
+
await ts.setAccessToken(data.tokens.accessToken);
|
|
326
|
+
}
|
|
327
|
+
if (data?.tokens?.refreshToken) {
|
|
328
|
+
await ts.setRefreshToken(data.tokens.refreshToken);
|
|
329
|
+
}
|
|
330
|
+
return data;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Get current user info
|
|
335
|
+
*/
|
|
336
|
+
async getUserInfo(): Promise<UserInfo> {
|
|
337
|
+
const response = await this.httpClient.get<UserInfo>('/user/me');
|
|
338
|
+
return response;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Logout current user
|
|
343
|
+
*/
|
|
344
|
+
async logout(): Promise<void> {
|
|
345
|
+
await this.httpClient.post('/auth/logout');
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Refresh access token
|
|
350
|
+
*/
|
|
351
|
+
async refreshToken(refreshToken: string): Promise<AuthResponse> {
|
|
352
|
+
const response = await this.httpClient.post<AuthResponse>('/auth/refresh', {
|
|
353
|
+
refresh_token: refreshToken,
|
|
354
|
+
});
|
|
355
|
+
return response;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
private async openBrowser(url: string): Promise<void> {
|
|
359
|
+
try {
|
|
360
|
+
const canOpen = await Linking.canOpenURL(url);
|
|
361
|
+
if (canOpen) {
|
|
362
|
+
await Linking.openURL(url);
|
|
363
|
+
} else {
|
|
364
|
+
throw new Error('Cannot open browser');
|
|
365
|
+
}
|
|
366
|
+
} catch (error) {
|
|
367
|
+
console.error('WebAuth: Failed to open browser', error);
|
|
368
|
+
throw error;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
private generateState(): string {
|
|
373
|
+
return (
|
|
374
|
+
Math.random().toString(36).substring(2, 15) +
|
|
375
|
+
Math.random().toString(36).substring(2, 15)
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export function createAuthModule(
|
|
381
|
+
httpClient: HttpClient,
|
|
382
|
+
configManager: WebAuthConfigManager,
|
|
383
|
+
): AuthModule {
|
|
384
|
+
return new AuthModule(httpClient, configManager);
|
|
385
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebAuth SDK - OTP (One-Time Password) Module
|
|
3
|
+
* Handles 2FA setup, verification, and management
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
OTPSetupResponse,
|
|
8
|
+
OTPVerifyRequest,
|
|
9
|
+
OTPStatusResponse,
|
|
10
|
+
} from '../types';
|
|
11
|
+
import { HttpClient } from '../utils/http';
|
|
12
|
+
|
|
13
|
+
export class OTPModule {
|
|
14
|
+
private httpClient: HttpClient;
|
|
15
|
+
|
|
16
|
+
constructor(httpClient: HttpClient) {
|
|
17
|
+
this.httpClient = httpClient;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Setup OTP - Generate secret and QR code for authenticator app
|
|
22
|
+
* @returns OTPSetupResponse with secret and QR code URL
|
|
23
|
+
*/
|
|
24
|
+
async setup(): Promise<OTPSetupResponse> {
|
|
25
|
+
const response = await this.httpClient.post<OTPSetupResponse>(
|
|
26
|
+
'/security/otp/setup'
|
|
27
|
+
);
|
|
28
|
+
return response;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Verify OTP code to enable 2FA
|
|
33
|
+
* @param code - The 6-digit code from your authenticator app
|
|
34
|
+
* @returns OTP status with enabled flag
|
|
35
|
+
*/
|
|
36
|
+
async verify(code: string): Promise<OTPStatusResponse> {
|
|
37
|
+
const request: OTPVerifyRequest = { code };
|
|
38
|
+
const response = await this.httpClient.post<OTPStatusResponse>(
|
|
39
|
+
'/security/otp/verify',
|
|
40
|
+
request
|
|
41
|
+
);
|
|
42
|
+
return response;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Disable OTP (requires current OTP code)
|
|
47
|
+
* @param code - Current OTP code for verification before disabling
|
|
48
|
+
*/
|
|
49
|
+
async disable(code: string): Promise<{ message: string }> {
|
|
50
|
+
const response = await this.httpClient.post<{ message: string }>(
|
|
51
|
+
'/security/otp/disable',
|
|
52
|
+
{ code }
|
|
53
|
+
);
|
|
54
|
+
return response;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Check if OTP is enabled for current user
|
|
59
|
+
*/
|
|
60
|
+
async getStatus(): Promise<OTPStatusResponse> {
|
|
61
|
+
const response = await this.httpClient.get<OTPStatusResponse>(
|
|
62
|
+
'/security/otp/status'
|
|
63
|
+
);
|
|
64
|
+
return response;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Validate an OTP code without enabling/disabling
|
|
69
|
+
* Useful for testing or verifying codes during login
|
|
70
|
+
*/
|
|
71
|
+
async validate(code: string): Promise<boolean> {
|
|
72
|
+
try {
|
|
73
|
+
await this.httpClient.post('/security/otp/validate', { code });
|
|
74
|
+
return true;
|
|
75
|
+
} catch (error) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function createOTPModule(httpClient: HttpClient): OTPModule {
|
|
82
|
+
return new OTPModule(httpClient);
|
|
83
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebAuth SDK - Sign Module
|
|
3
|
+
* Handles message signing for MPC wallets
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
SignRequest,
|
|
8
|
+
SignResponse,
|
|
9
|
+
} from '../types';
|
|
10
|
+
import { HttpClient } from '../utils/http';
|
|
11
|
+
|
|
12
|
+
export class SignModule {
|
|
13
|
+
private httpClient: HttpClient;
|
|
14
|
+
|
|
15
|
+
constructor(httpClient: HttpClient) {
|
|
16
|
+
this.httpClient = httpClient;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Request signature for a message
|
|
21
|
+
* This initiates the MPC signing flow
|
|
22
|
+
*
|
|
23
|
+
* @param request - Sign request with message and wallet address
|
|
24
|
+
* @returns Promise resolving to signed signature
|
|
25
|
+
*/
|
|
26
|
+
async sign(request: SignRequest): Promise<SignResponse> {
|
|
27
|
+
const response = await this.httpClient.post<SignResponse>(
|
|
28
|
+
'/sign',
|
|
29
|
+
request
|
|
30
|
+
);
|
|
31
|
+
return response;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Sign a simple text message
|
|
36
|
+
*/
|
|
37
|
+
async signMessage(
|
|
38
|
+
message: string,
|
|
39
|
+
walletAddress: string,
|
|
40
|
+
chainId?: number
|
|
41
|
+
): Promise<SignResponse> {
|
|
42
|
+
return this.sign({
|
|
43
|
+
message,
|
|
44
|
+
walletAddress,
|
|
45
|
+
chainId,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Sign typed data (EIP-712)
|
|
51
|
+
*/
|
|
52
|
+
async signTypedData(
|
|
53
|
+
data: any,
|
|
54
|
+
walletAddress: string,
|
|
55
|
+
chainId?: number
|
|
56
|
+
): Promise<SignResponse> {
|
|
57
|
+
const response = await this.httpClient.post<SignResponse>(
|
|
58
|
+
'/sign/typed-data',
|
|
59
|
+
{
|
|
60
|
+
data,
|
|
61
|
+
walletAddress,
|
|
62
|
+
chainId,
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
return response;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Sign a transaction
|
|
70
|
+
*/
|
|
71
|
+
async signTransaction(
|
|
72
|
+
transaction: any,
|
|
73
|
+
walletAddress: string
|
|
74
|
+
): Promise<SignResponse> {
|
|
75
|
+
const response = await this.httpClient.post<SignResponse>(
|
|
76
|
+
'/sign/transaction',
|
|
77
|
+
{
|
|
78
|
+
transaction,
|
|
79
|
+
walletAddress,
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
return response;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Verify a signature
|
|
87
|
+
*/
|
|
88
|
+
async verify(
|
|
89
|
+
message: string,
|
|
90
|
+
signature: string,
|
|
91
|
+
walletAddress: string
|
|
92
|
+
): Promise<boolean> {
|
|
93
|
+
try {
|
|
94
|
+
const response = await this.httpClient.post<{ valid: boolean }>(
|
|
95
|
+
'/sign/verify',
|
|
96
|
+
{
|
|
97
|
+
message,
|
|
98
|
+
signature,
|
|
99
|
+
walletAddress,
|
|
100
|
+
}
|
|
101
|
+
);
|
|
102
|
+
return response.valid;
|
|
103
|
+
} catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get pending signature requests
|
|
110
|
+
*/
|
|
111
|
+
async getPendingRequests(): Promise<SignRequest[]> {
|
|
112
|
+
const response = await this.httpClient.get<SignRequest[]>('/sign/pending');
|
|
113
|
+
return response;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Cancel a pending signature request
|
|
118
|
+
*/
|
|
119
|
+
async cancelRequest(requestId: string): Promise<void> {
|
|
120
|
+
await this.httpClient.delete(`/sign/request/${requestId}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function createSignModule(httpClient: HttpClient): SignModule {
|
|
125
|
+
return new SignModule(httpClient);
|
|
126
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebAuth SDK - Wallet Module (MPC-based)
|
|
3
|
+
* Handles MPC wallet creation and management
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
WalletInfo,
|
|
8
|
+
CreateWalletResponse,
|
|
9
|
+
} from '../types';
|
|
10
|
+
import { HttpClient } from '../utils/http';
|
|
11
|
+
|
|
12
|
+
export class WalletModule {
|
|
13
|
+
private httpClient: HttpClient;
|
|
14
|
+
|
|
15
|
+
constructor(httpClient: HttpClient) {
|
|
16
|
+
this.httpClient = httpClient;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create a new MPC wallet
|
|
21
|
+
* @param name - Optional wallet name/alias
|
|
22
|
+
* @returns Created wallet information including address
|
|
23
|
+
*/
|
|
24
|
+
async create(name?: string): Promise<CreateWalletResponse> {
|
|
25
|
+
const response = await this.httpClient.post<CreateWalletResponse>(
|
|
26
|
+
'/wallet/create',
|
|
27
|
+
name ? { name } : undefined
|
|
28
|
+
);
|
|
29
|
+
return response;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get wallet information for current user
|
|
34
|
+
*/
|
|
35
|
+
async getWallet(): Promise<WalletInfo> {
|
|
36
|
+
const response = await this.httpClient.get<WalletInfo>('/wallet');
|
|
37
|
+
return response;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Check if user has a wallet
|
|
42
|
+
*/
|
|
43
|
+
async hasWallet(): Promise<boolean> {
|
|
44
|
+
try {
|
|
45
|
+
await this.getWallet();
|
|
46
|
+
return true;
|
|
47
|
+
} catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Export wallet info (address only, no private key)
|
|
54
|
+
*/
|
|
55
|
+
async exportPublicInfo(): Promise<{ address: string; publicKey?: string }> {
|
|
56
|
+
const wallet = await this.getWallet();
|
|
57
|
+
return {
|
|
58
|
+
address: wallet.address,
|
|
59
|
+
publicKey: wallet.publicKey,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get wallet balance (if applicable)
|
|
65
|
+
*/
|
|
66
|
+
async getBalance(): Promise<{ [token: string]: string }> {
|
|
67
|
+
const response = await this.httpClient.get<{ [token: string]: string }>(
|
|
68
|
+
'/wallet/balance'
|
|
69
|
+
);
|
|
70
|
+
return response;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get wallet transactions
|
|
75
|
+
*/
|
|
76
|
+
async getTransactions(
|
|
77
|
+
page: number = 1,
|
|
78
|
+
limit: number = 20
|
|
79
|
+
): Promise<{ transactions: any[]; total: number }> {
|
|
80
|
+
const response = await this.httpClient.get<{ transactions: any[]; total: number }>(
|
|
81
|
+
`/wallet/transactions?page=${page}&limit=${limit}`
|
|
82
|
+
);
|
|
83
|
+
return response;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function createWalletModule(httpClient: HttpClient): WalletModule {
|
|
88
|
+
return new WalletModule(httpClient);
|
|
89
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebAuth SDK Type Definitions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Core Configuration
|
|
6
|
+
export interface WebAuthConfig {
|
|
7
|
+
apiBaseUrl: string;
|
|
8
|
+
apiKey: string;
|
|
9
|
+
redirectUri: string;
|
|
10
|
+
appScheme?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Auth Types
|
|
14
|
+
export interface AuthResponse {
|
|
15
|
+
access_token: string;
|
|
16
|
+
refresh_token?: string;
|
|
17
|
+
expires_in?: number;
|
|
18
|
+
token_type?: string;
|
|
19
|
+
email?: string;
|
|
20
|
+
owner?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface UserInfo {
|
|
24
|
+
id: string;
|
|
25
|
+
email?: string;
|
|
26
|
+
name?: string;
|
|
27
|
+
avatar?: string;
|
|
28
|
+
phone?: string;
|
|
29
|
+
created_at?: string;
|
|
30
|
+
updated_at?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface SendCodeRequest {
|
|
34
|
+
email: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface SendCodeResponse {
|
|
38
|
+
message: string;
|
|
39
|
+
expires_in?: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface EmailLoginRequest {
|
|
43
|
+
email: string;
|
|
44
|
+
code: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface AppleLoginRequest {
|
|
48
|
+
identityToken: string;
|
|
49
|
+
authorizationCode: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// OTP Types
|
|
53
|
+
export interface OTPSetupResponse {
|
|
54
|
+
secret: string;
|
|
55
|
+
qrCodeUrl: string;
|
|
56
|
+
manualEntryKey?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface OTPVerifyRequest {
|
|
60
|
+
code: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface OTPStatusResponse {
|
|
64
|
+
enabled: boolean;
|
|
65
|
+
created_at?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Wallet Types
|
|
69
|
+
export interface WalletInfo {
|
|
70
|
+
address: string;
|
|
71
|
+
publicKey?: string;
|
|
72
|
+
created_at?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface CreateWalletResponse extends WalletInfo {
|
|
76
|
+
encryptedKey?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface SignRequest {
|
|
80
|
+
message: string;
|
|
81
|
+
walletAddress: string;
|
|
82
|
+
chainId?: number;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface SignResponse {
|
|
86
|
+
signature: string;
|
|
87
|
+
message: string;
|
|
88
|
+
walletAddress: string;
|
|
89
|
+
timestamp: number;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Error Types
|
|
93
|
+
export interface WebAuthError {
|
|
94
|
+
code: string;
|
|
95
|
+
message: string;
|
|
96
|
+
details?: any;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Event Types
|
|
100
|
+
export type AuthEventType = 'login' | 'logout' | 'token_refresh' | 'error';
|
|
101
|
+
|
|
102
|
+
export interface AuthEvent {
|
|
103
|
+
type: AuthEventType;
|
|
104
|
+
data?: any;
|
|
105
|
+
error?: WebAuthError;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export type EventListener = (event: AuthEvent) => void;
|
|
109
|
+
|
|
110
|
+
// Storage Interface
|
|
111
|
+
export interface TokenStorage {
|
|
112
|
+
getAccessToken(): Promise<string | null>;
|
|
113
|
+
setAccessToken(token: string): Promise<void>;
|
|
114
|
+
getRefreshToken(): Promise<string | null>;
|
|
115
|
+
setRefreshToken(token: string): Promise<void>;
|
|
116
|
+
clearTokens(): Promise<void>;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Platform-specific types
|
|
120
|
+
export interface LinkingOptions {
|
|
121
|
+
url: string;
|
|
122
|
+
options?: {
|
|
123
|
+
redirect?: boolean;
|
|
124
|
+
};
|
|
125
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebAuth SDK Configuration Module
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { WebAuthConfig } from '../types';
|
|
6
|
+
|
|
7
|
+
const DEFAULT_CONFIG: Partial<WebAuthConfig> = {
|
|
8
|
+
appScheme: 'webauth',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export class WebAuthConfigManager {
|
|
12
|
+
private config: WebAuthConfig;
|
|
13
|
+
|
|
14
|
+
constructor(config: WebAuthConfig) {
|
|
15
|
+
this.config = { ...DEFAULT_CONFIG, ...config } as WebAuthConfig;
|
|
16
|
+
this.validateConfig();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
private validateConfig(): void {
|
|
20
|
+
if (!this.config.apiBaseUrl) {
|
|
21
|
+
throw new Error('WebAuth: apiBaseUrl is required');
|
|
22
|
+
}
|
|
23
|
+
if (!this.config.apiKey) {
|
|
24
|
+
throw new Error('WebAuth: googleClientId is required');
|
|
25
|
+
}
|
|
26
|
+
if (!this.config.redirectUri) {
|
|
27
|
+
throw new Error('WebAuth: redirectUri is required');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
getConfig(): WebAuthConfig {
|
|
32
|
+
return { ...this.config };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
updateConfig(updates: Partial<WebAuthConfig>): void {
|
|
36
|
+
this.config = { ...this.config, ...updates };
|
|
37
|
+
this.validateConfig();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
getGoogleAuthUrl(): string {
|
|
41
|
+
return `${this.config.apiBaseUrl}/auth/google/login`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
getAppleAuthUrl(): string {
|
|
45
|
+
return `${this.config.apiBaseUrl}/auth/apple/login`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
getEmailSendCodeUrl(): string {
|
|
49
|
+
return `${this.config.apiBaseUrl}/auth/email/send-code`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
getEmailLoginUrl(): string {
|
|
53
|
+
return `${this.config.apiBaseUrl}/auth/email/login`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
getUserMeUrl(): string {
|
|
57
|
+
return `${this.config.apiBaseUrl}/user/me`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
getOTPSetupUrl(): string {
|
|
61
|
+
return `${this.config.apiBaseUrl}/security/otp/setup`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
getOTPVerifyUrl(): string {
|
|
65
|
+
return `${this.config.apiBaseUrl}/security/otp/verify`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
getOTPDisableUrl(): string {
|
|
69
|
+
return `${this.config.apiBaseUrl}/security/otp/disable`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
getWalletCreateUrl(): string {
|
|
73
|
+
return `${this.config.apiBaseUrl}/wallet/create`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
getWalletUrl(): string {
|
|
77
|
+
return `${this.config.apiBaseUrl}/wallet`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
getSignUrl(): string {
|
|
81
|
+
return `${this.config.apiBaseUrl}/sign`;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function createConfig(config: WebAuthConfig): WebAuthConfigManager {
|
|
86
|
+
return new WebAuthConfigManager(config);
|
|
87
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebAuth SDK HTTP Client Module
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
|
|
6
|
+
import { WebAuthError } from '../types';
|
|
7
|
+
|
|
8
|
+
export class HttpClient {
|
|
9
|
+
private client: AxiosInstance;
|
|
10
|
+
private accessTokenGetter: () => Promise<string | null>;
|
|
11
|
+
|
|
12
|
+
constructor(
|
|
13
|
+
baseURL: string,
|
|
14
|
+
accessTokenGetter: () => Promise<string | null>
|
|
15
|
+
) {
|
|
16
|
+
this.accessTokenGetter = accessTokenGetter;
|
|
17
|
+
this.client = axios.create({
|
|
18
|
+
baseURL,
|
|
19
|
+
timeout: 30000,
|
|
20
|
+
headers: {
|
|
21
|
+
'Content-Type': 'application/json',
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
this.setupInterceptors();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private setupInterceptors(): void {
|
|
29
|
+
// Request interceptor to add auth token
|
|
30
|
+
this.client.interceptors.request.use(
|
|
31
|
+
async (config) => {
|
|
32
|
+
const token = await this.accessTokenGetter();
|
|
33
|
+
if (token) {
|
|
34
|
+
config.headers.Authorization = `Bearer ${token}`;
|
|
35
|
+
}
|
|
36
|
+
return config;
|
|
37
|
+
},
|
|
38
|
+
(error) => {
|
|
39
|
+
return Promise.reject(this.formatError(error));
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// Response interceptor for error handling
|
|
44
|
+
this.client.interceptors.response.use(
|
|
45
|
+
(response) => response,
|
|
46
|
+
(error) => {
|
|
47
|
+
return Promise.reject(this.formatError(error));
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private formatError(error: any): WebAuthError {
|
|
53
|
+
if (error.response) {
|
|
54
|
+
const { status, data } = error.response;
|
|
55
|
+
return {
|
|
56
|
+
code: `HTTP_${status}`,
|
|
57
|
+
message: data?.message || data?.error || 'An error occurred',
|
|
58
|
+
details: data,
|
|
59
|
+
};
|
|
60
|
+
} else if (error.request) {
|
|
61
|
+
return {
|
|
62
|
+
code: 'NETWORK_ERROR',
|
|
63
|
+
message: 'Network error - please check your connection',
|
|
64
|
+
details: error.request,
|
|
65
|
+
};
|
|
66
|
+
} else {
|
|
67
|
+
return {
|
|
68
|
+
code: 'UNKNOWN_ERROR',
|
|
69
|
+
message: error.message || 'An unknown error occurred',
|
|
70
|
+
details: error,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async get<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
|
|
76
|
+
const response: AxiosResponse<T> = await this.client.get(url, config);
|
|
77
|
+
return response.data;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async post<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
|
|
81
|
+
const response: AxiosResponse<T> = await this.client.post(url, data, config);
|
|
82
|
+
return response.data;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async put<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
|
|
86
|
+
const response: AxiosResponse<T> = await this.client.put(url, data, config);
|
|
87
|
+
return response.data;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async delete<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
|
|
91
|
+
const response: AxiosResponse<T> = await this.client.delete(url, config);
|
|
92
|
+
return response.data;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async patch<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
|
|
96
|
+
const response: AxiosResponse<T> = await this.client.patch(url, data, config);
|
|
97
|
+
return response.data;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function createHttpClient(
|
|
102
|
+
baseURL: string,
|
|
103
|
+
accessTokenGetter: () => Promise<string | null>
|
|
104
|
+
): HttpClient {
|
|
105
|
+
return new HttpClient(baseURL, accessTokenGetter);
|
|
106
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebAuth SDK Storage Module
|
|
3
|
+
* Token storage implementation for React Native
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { TokenStorage } from '../types';
|
|
7
|
+
|
|
8
|
+
const STORAGE_KEYS = {
|
|
9
|
+
ACCESS_TOKEN: 'webauth_access_token',
|
|
10
|
+
REFRESH_TOKEN: 'webauth_refresh_token',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export class RNTokenStorage implements TokenStorage {
|
|
14
|
+
private storage: any;
|
|
15
|
+
|
|
16
|
+
constructor(storage?: any) {
|
|
17
|
+
// Use AsyncStorage or custom storage
|
|
18
|
+
this.storage = storage || {
|
|
19
|
+
getItem: async (key: string) => {
|
|
20
|
+
try {
|
|
21
|
+
// React Native AsyncStorage
|
|
22
|
+
const AsyncStorage = require('@react-native-async-storage/async-storage').default;
|
|
23
|
+
return await AsyncStorage.getItem(key);
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
setItem: async (key: string, value: string) => {
|
|
29
|
+
try {
|
|
30
|
+
const AsyncStorage = require('@react-native-async-storage/async-storage').default;
|
|
31
|
+
await AsyncStorage.setItem(key, value);
|
|
32
|
+
} catch {
|
|
33
|
+
console.warn('WebAuth: Failed to save token');
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
removeItem: async (key: string) => {
|
|
37
|
+
try {
|
|
38
|
+
const AsyncStorage = require('@react-native-async-storage/async-storage').default;
|
|
39
|
+
await AsyncStorage.removeItem(key);
|
|
40
|
+
} catch {
|
|
41
|
+
console.warn('WebAuth: Failed to remove token');
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async getAccessToken(): Promise<string | null> {
|
|
48
|
+
try {
|
|
49
|
+
const value = await this.storage.getItem(STORAGE_KEYS.ACCESS_TOKEN);
|
|
50
|
+
return value;
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async setAccessToken(token: string): Promise<void> {
|
|
57
|
+
await this.storage.setItem(STORAGE_KEYS.ACCESS_TOKEN, token);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async getRefreshToken(): Promise<string | null> {
|
|
61
|
+
try {
|
|
62
|
+
const value = await this.storage.getItem(STORAGE_KEYS.REFRESH_TOKEN);
|
|
63
|
+
return value;
|
|
64
|
+
} catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async setRefreshToken(token: string): Promise<void> {
|
|
70
|
+
await this.storage.setItem(STORAGE_KEYS.REFRESH_TOKEN, token);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async clearTokens(): Promise<void> {
|
|
74
|
+
await this.storage.removeItem(STORAGE_KEYS.ACCESS_TOKEN);
|
|
75
|
+
await this.storage.removeItem(STORAGE_KEYS.REFRESH_TOKEN);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// In-memory storage for web or testing
|
|
80
|
+
export class MemoryTokenStorage implements TokenStorage {
|
|
81
|
+
private accessToken: string | null = null;
|
|
82
|
+
private refreshToken: string | null = null;
|
|
83
|
+
|
|
84
|
+
async getAccessToken(): Promise<string | null> {
|
|
85
|
+
return this.accessToken;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async setAccessToken(token: string): Promise<void> {
|
|
89
|
+
this.accessToken = token;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async getRefreshToken(): Promise<string | null> {
|
|
93
|
+
return this.refreshToken;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async setRefreshToken(token: string): Promise<void> {
|
|
97
|
+
this.refreshToken = token;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async clearTokens(): Promise<void> {
|
|
101
|
+
this.accessToken = null;
|
|
102
|
+
this.refreshToken = null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function createTokenStorage(type: 'react-native' | 'memory' = 'memory', storage?: any): TokenStorage {
|
|
107
|
+
if (type === 'react-native') {
|
|
108
|
+
return new RNTokenStorage(storage);
|
|
109
|
+
}
|
|
110
|
+
return new MemoryTokenStorage();
|
|
111
|
+
}
|