@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 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
+ }