@zerodev/wallet-react 0.0.1-alpha.5 → 0.0.1-alpha.7
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/CHANGELOG.md +21 -0
- package/README.md +17 -14
- package/dist/_cjs/actions.js +53 -24
- package/dist/_cjs/connector.js +44 -3
- package/dist/_cjs/hooks/useExportPrivateKey.js +18 -0
- package/dist/_cjs/hooks/useGetUserEmail.js +19 -0
- package/dist/_cjs/index.js +8 -1
- package/dist/_cjs/oauth.js +60 -55
- package/dist/_esm/actions.js +65 -27
- package/dist/_esm/connector.js +56 -5
- package/dist/_esm/hooks/useExportPrivateKey.js +18 -0
- package/dist/_esm/hooks/useGetUserEmail.js +19 -0
- package/dist/_esm/index.js +3 -1
- package/dist/_esm/oauth.js +71 -53
- package/dist/_types/actions.d.ts +41 -6
- package/dist/_types/actions.d.ts.map +1 -1
- package/dist/_types/connector.d.ts +0 -2
- package/dist/_types/connector.d.ts.map +1 -1
- package/dist/_types/hooks/useExportPrivateKey.d.ts +18 -0
- package/dist/_types/hooks/useExportPrivateKey.d.ts.map +1 -0
- package/dist/_types/hooks/useGetUserEmail.d.ts +20 -0
- package/dist/_types/hooks/useGetUserEmail.d.ts.map +1 -0
- package/dist/_types/index.d.ts +4 -2
- package/dist/_types/index.d.ts.map +1 -1
- package/dist/_types/oauth.d.ts +25 -12
- package/dist/_types/oauth.d.ts.map +1 -1
- package/dist/_types/store.d.ts +7 -3
- package/dist/_types/store.d.ts.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/actions.ts +122 -44
- package/src/connector.ts +68 -7
- package/src/hooks/useExportPrivateKey.ts +57 -0
- package/src/hooks/useGetUserEmail.ts +54 -0
- package/src/index.ts +9 -2
- package/src/oauth.ts +97 -78
- package/src/store.ts +9 -4
- package/tsconfig.build.tsbuildinfo +1 -1
package/src/oauth.ts
CHANGED
|
@@ -7,56 +7,17 @@ export const OAUTH_PROVIDERS = {
|
|
|
7
7
|
export type OAuthProvider =
|
|
8
8
|
(typeof OAUTH_PROVIDERS)[keyof typeof OAUTH_PROVIDERS]
|
|
9
9
|
|
|
10
|
-
export type
|
|
11
|
-
googleClientId?: string
|
|
12
|
-
redirectUri: string
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export type OAuthFlowParams = {
|
|
10
|
+
export type BackendOAuthFlowParams = {
|
|
16
11
|
provider: OAuthProvider
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
backendUrl: string
|
|
13
|
+
projectId: string
|
|
14
|
+
publicKey: string
|
|
15
|
+
returnTo: string
|
|
21
16
|
}
|
|
22
17
|
|
|
23
|
-
const GOOGLE_AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth'
|
|
24
|
-
|
|
25
18
|
const POPUP_WIDTH = 500
|
|
26
19
|
const POPUP_HEIGHT = 600
|
|
27
20
|
|
|
28
|
-
export function buildOAuthUrl(params: OAuthFlowParams): string {
|
|
29
|
-
const { provider, clientId, redirectUri, nonce, state } = params
|
|
30
|
-
|
|
31
|
-
if (provider !== OAUTH_PROVIDERS.GOOGLE) {
|
|
32
|
-
throw new Error(`Unsupported OAuth provider: ${provider}`)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const authUrl = new URL(GOOGLE_AUTH_URL)
|
|
36
|
-
authUrl.searchParams.set('client_id', clientId)
|
|
37
|
-
authUrl.searchParams.set('redirect_uri', redirectUri)
|
|
38
|
-
authUrl.searchParams.set('response_type', 'id_token')
|
|
39
|
-
authUrl.searchParams.set('scope', 'openid email profile')
|
|
40
|
-
authUrl.searchParams.set('nonce', nonce)
|
|
41
|
-
authUrl.searchParams.set('prompt', 'select_account')
|
|
42
|
-
|
|
43
|
-
let stateParam = `provider=${provider}`
|
|
44
|
-
if (state) {
|
|
45
|
-
const additionalState = Object.entries(state)
|
|
46
|
-
.map(
|
|
47
|
-
([key, value]) =>
|
|
48
|
-
`${encodeURIComponent(key)}=${encodeURIComponent(value)}`,
|
|
49
|
-
)
|
|
50
|
-
.join('&')
|
|
51
|
-
if (additionalState) {
|
|
52
|
-
stateParam += `&${additionalState}`
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
authUrl.searchParams.set('state', stateParam)
|
|
56
|
-
|
|
57
|
-
return authUrl.toString()
|
|
58
|
-
}
|
|
59
|
-
|
|
60
21
|
export function openOAuthPopup(url: string): Window | null {
|
|
61
22
|
const width = POPUP_WIDTH
|
|
62
23
|
const height = POPUP_HEIGHT
|
|
@@ -76,49 +37,107 @@ export function openOAuthPopup(url: string): Window | null {
|
|
|
76
37
|
return authWindow
|
|
77
38
|
}
|
|
78
39
|
|
|
79
|
-
export function
|
|
80
|
-
|
|
81
|
-
|
|
40
|
+
export function generateOAuthNonce(publicKey: string): string {
|
|
41
|
+
return sha256(publicKey as Hex).replace(/^0x/, '')
|
|
42
|
+
}
|
|
82
43
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Build OAuth URL that redirects to backend's OAuth endpoint
|
|
46
|
+
* The backend handles PKCE, client credentials, and token exchange
|
|
47
|
+
*/
|
|
48
|
+
export function buildBackendOAuthUrl(params: BackendOAuthFlowParams): string {
|
|
49
|
+
const { provider, backendUrl, projectId, publicKey, returnTo } = params
|
|
50
|
+
|
|
51
|
+
if (provider !== OAUTH_PROVIDERS.GOOGLE) {
|
|
52
|
+
throw new Error(`Unsupported OAuth provider: ${provider}`)
|
|
86
53
|
}
|
|
87
54
|
|
|
88
|
-
|
|
55
|
+
const oauthUrl = new URL(`${backendUrl}/oauth/google/login`)
|
|
56
|
+
oauthUrl.searchParams.set('project_id', projectId)
|
|
57
|
+
oauthUrl.searchParams.set('pub_key', publicKey.replace(/^0x/, ''))
|
|
58
|
+
oauthUrl.searchParams.set('return_to', returnTo)
|
|
59
|
+
|
|
60
|
+
return oauthUrl.toString()
|
|
89
61
|
}
|
|
90
62
|
|
|
91
|
-
export
|
|
63
|
+
export type OAuthMessageData = {
|
|
64
|
+
type: 'oauth_success' | 'oauth_error'
|
|
65
|
+
error?: string
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Listen for OAuth completion via postMessage from popup
|
|
70
|
+
* The popup sends a message when it detects a successful redirect
|
|
71
|
+
*/
|
|
72
|
+
export function listenForOAuthMessage(
|
|
92
73
|
authWindow: Window,
|
|
93
|
-
|
|
94
|
-
onSuccess: (
|
|
74
|
+
expectedOrigin: string,
|
|
75
|
+
onSuccess: () => void,
|
|
95
76
|
onError: (error: Error) => void,
|
|
96
|
-
): void {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
// Ignore cross-origin errors
|
|
77
|
+
): () => void {
|
|
78
|
+
let cleaned = false
|
|
79
|
+
|
|
80
|
+
const handleMessage = (event: MessageEvent<OAuthMessageData>) => {
|
|
81
|
+
// Only trust messages from expected origin
|
|
82
|
+
if (event.origin !== expectedOrigin) return
|
|
83
|
+
if (!event.data || typeof event.data !== 'object') return
|
|
84
|
+
|
|
85
|
+
if (event.data.type === 'oauth_success') {
|
|
86
|
+
cleanup()
|
|
87
|
+
onSuccess()
|
|
88
|
+
} else if (event.data.type === 'oauth_error') {
|
|
89
|
+
cleanup()
|
|
90
|
+
onError(new Error(event.data.error || 'OAuth authentication failed'))
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const checkWindowClosed = setInterval(() => {
|
|
95
|
+
if (authWindow.closed) {
|
|
96
|
+
cleanup()
|
|
97
|
+
onError(new Error('Authentication window was closed'))
|
|
118
98
|
}
|
|
119
99
|
}, 500)
|
|
100
|
+
|
|
101
|
+
const cleanup = () => {
|
|
102
|
+
if (cleaned) return
|
|
103
|
+
cleaned = true
|
|
104
|
+
window.removeEventListener('message', handleMessage)
|
|
105
|
+
clearInterval(checkWindowClosed)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
window.addEventListener('message', handleMessage)
|
|
109
|
+
|
|
110
|
+
return cleanup
|
|
120
111
|
}
|
|
121
112
|
|
|
122
|
-
|
|
123
|
-
|
|
113
|
+
/**
|
|
114
|
+
* Handle OAuth callback on the return page
|
|
115
|
+
* Call this on the page that receives the OAuth redirect
|
|
116
|
+
* It sends a postMessage to the opener and closes the window
|
|
117
|
+
*/
|
|
118
|
+
export function handleOAuthCallback(successParam = 'oauth_success'): boolean {
|
|
119
|
+
const urlParams = new URLSearchParams(window.location.search)
|
|
120
|
+
const isSuccess = urlParams.get(successParam) === 'true'
|
|
121
|
+
const error = urlParams.get('error')
|
|
122
|
+
|
|
123
|
+
if (window.opener) {
|
|
124
|
+
if (isSuccess) {
|
|
125
|
+
window.opener.postMessage(
|
|
126
|
+
{ type: 'oauth_success' } satisfies OAuthMessageData,
|
|
127
|
+
window.location.origin,
|
|
128
|
+
)
|
|
129
|
+
window.close()
|
|
130
|
+
return true
|
|
131
|
+
}
|
|
132
|
+
if (error) {
|
|
133
|
+
window.opener.postMessage(
|
|
134
|
+
{ type: 'oauth_error', error } satisfies OAuthMessageData,
|
|
135
|
+
window.location.origin,
|
|
136
|
+
)
|
|
137
|
+
window.close()
|
|
138
|
+
return false
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return false
|
|
124
143
|
}
|
package/src/store.ts
CHANGED
|
@@ -10,7 +10,12 @@ import type { LocalAccount } from 'viem'
|
|
|
10
10
|
import type { SmartAccount } from 'viem/account-abstraction'
|
|
11
11
|
import { create } from 'zustand'
|
|
12
12
|
import { persist, subscribeWithSelector } from 'zustand/middleware'
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
// Internal OAuth config stored in the state (derived from connector params)
|
|
15
|
+
type InternalOAuthConfig = {
|
|
16
|
+
backendUrl: string
|
|
17
|
+
projectId: string
|
|
18
|
+
}
|
|
14
19
|
|
|
15
20
|
export type ZeroDevWalletState = {
|
|
16
21
|
// Core
|
|
@@ -26,8 +31,8 @@ export type ZeroDevWalletState = {
|
|
|
26
31
|
// Session expiry
|
|
27
32
|
isExpiring: boolean
|
|
28
33
|
|
|
29
|
-
// OAuth config (
|
|
30
|
-
oauthConfig:
|
|
34
|
+
// OAuth config (derived from connector params)
|
|
35
|
+
oauthConfig: InternalOAuthConfig | null
|
|
31
36
|
|
|
32
37
|
// Actions
|
|
33
38
|
setWallet: (wallet: ZeroDevWalletSDK) => void
|
|
@@ -40,7 +45,7 @@ export type ZeroDevWalletState = {
|
|
|
40
45
|
setSession: (session: ZeroDevWalletSession | null) => void
|
|
41
46
|
setActiveChainId: (chainId: number | null) => void
|
|
42
47
|
setIsExpiring: (isExpiring: boolean) => void
|
|
43
|
-
setOAuthConfig: (config:
|
|
48
|
+
setOAuthConfig: (config: InternalOAuthConfig | null) => void
|
|
44
49
|
clear: () => void
|
|
45
50
|
}
|
|
46
51
|
|