@zerodev/wallet-react 0.0.1-alpha.6 → 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 +13 -0
- package/README.md +3 -13
- package/dist/_cjs/actions.js +23 -21
- package/dist/_cjs/connector.js +44 -3
- package/dist/_cjs/hooks/useGetUserEmail.js +19 -0
- package/dist/_cjs/index.js +6 -1
- package/dist/_cjs/oauth.js +60 -55
- package/dist/_esm/actions.js +34 -26
- package/dist/_esm/connector.js +56 -5
- package/dist/_esm/hooks/useGetUserEmail.js +19 -0
- package/dist/_esm/index.js +2 -1
- package/dist/_esm/oauth.js +71 -53
- package/dist/_types/actions.d.ts +22 -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/useGetUserEmail.d.ts +20 -0
- package/dist/_types/hooks/useGetUserEmail.d.ts.map +1 -0
- package/dist/_types/index.d.ts +3 -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 +58 -40
- package/src/connector.ts +68 -7
- package/src/hooks/useGetUserEmail.ts +54 -0
- package/src/index.ts +8 -2
- package/src/oauth.ts +97 -78
- package/src/store.ts +9 -4
- package/tsconfig.build.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @zerodev/wallet-react
|
|
2
2
|
|
|
3
|
+
## 0.0.1-alpha.7
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Add OTP authentication via Turnkey Auth Proxy
|
|
8
|
+
- Add OAuth backend PKCE flow
|
|
9
|
+
- Add getUserEmail method and React hook
|
|
10
|
+
- Fix passkey login endpoint
|
|
11
|
+
- Fix signature verification with JSON canonicalization
|
|
12
|
+
- Rename turnkeySession to session in OAuth response
|
|
13
|
+
- Updated dependencies
|
|
14
|
+
- @zerodev/wallet-core@0.0.1-alpha.7
|
|
15
|
+
|
|
3
16
|
## 0.0.1-alpha.6
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -38,10 +38,6 @@ const config = createConfig({
|
|
|
38
38
|
projectId: 'YOUR_PROJECT_ID',
|
|
39
39
|
aaUrl: 'YOUR_AA_PROVIDER_URL',
|
|
40
40
|
chains: [sepolia],
|
|
41
|
-
oauthConfig: {
|
|
42
|
-
googleClientId: 'YOUR_GOOGLE_CLIENT_ID',
|
|
43
|
-
redirectUri: 'http://localhost:3000',
|
|
44
|
-
},
|
|
45
41
|
})
|
|
46
42
|
],
|
|
47
43
|
transports: {
|
|
@@ -150,7 +146,8 @@ await loginPasskey.mutateAsync({ email: 'user@example.com' })
|
|
|
150
146
|
```typescript
|
|
151
147
|
const authenticateOAuth = useAuthenticateOAuth()
|
|
152
148
|
|
|
153
|
-
// Opens popup
|
|
149
|
+
// Opens popup, backend handles PKCE and token exchange
|
|
150
|
+
// No callback page or OAuth library needed - SDK handles everything
|
|
154
151
|
await authenticateOAuth.mutateAsync({
|
|
155
152
|
provider: OAUTH_PROVIDERS.GOOGLE
|
|
156
153
|
})
|
|
@@ -182,18 +179,12 @@ type ZeroDevWalletConnectorParams = {
|
|
|
182
179
|
projectId: string // Required: Your ZeroDev project ID
|
|
183
180
|
organizationId?: string // Optional: Turnkey organization ID
|
|
184
181
|
proxyBaseUrl?: string // Optional: KMS proxy URL
|
|
185
|
-
aaUrl
|
|
182
|
+
aaUrl?: string // Optional: Bundler/paymaster URL
|
|
186
183
|
chains: readonly Chain[] // Required: Supported chains
|
|
187
184
|
rpId?: string // Optional: WebAuthn RP ID
|
|
188
185
|
sessionStorage?: StorageAdapter // Optional: Custom session storage
|
|
189
186
|
autoRefreshSession?: boolean // Optional: Auto-refresh (default: true)
|
|
190
187
|
sessionWarningThreshold?: number // Optional: Refresh threshold in ms (default: 60000)
|
|
191
|
-
oauthConfig?: OAuthConfig // Optional: OAuth configuration
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
type OAuthConfig = {
|
|
195
|
-
googleClientId?: string
|
|
196
|
-
redirectUri: string
|
|
197
188
|
}
|
|
198
189
|
```
|
|
199
190
|
|
|
@@ -272,7 +263,6 @@ All hooks follow the TanStack Query mutation pattern:
|
|
|
272
263
|
### Types
|
|
273
264
|
|
|
274
265
|
- `OAuthProvider` - OAuth provider type
|
|
275
|
-
- `OAuthConfig` - OAuth configuration type
|
|
276
266
|
- `ZeroDevWalletConnectorParams` - Connector parameters
|
|
277
267
|
- `ZeroDevWalletState` - Store state type
|
|
278
268
|
- `ZeroDevProvider` - EIP-1193 provider type
|
package/dist/_cjs/actions.js
CHANGED
|
@@ -6,6 +6,7 @@ exports.authenticateOAuth = authenticateOAuth;
|
|
|
6
6
|
exports.sendOTP = sendOTP;
|
|
7
7
|
exports.verifyOTP = verifyOTP;
|
|
8
8
|
exports.refreshSession = refreshSession;
|
|
9
|
+
exports.getUserEmail = getUserEmail;
|
|
9
10
|
exports.exportWallet = exportWallet;
|
|
10
11
|
exports.exportPrivateKey = exportPrivateKey;
|
|
11
12
|
const actions_1 = require("@wagmi/core/actions");
|
|
@@ -64,40 +65,29 @@ async function authenticateOAuth(config, parameters) {
|
|
|
64
65
|
if (!wallet)
|
|
65
66
|
throw new Error('Wallet not initialized');
|
|
66
67
|
if (!oauthConfig) {
|
|
67
|
-
throw new Error('
|
|
68
|
-
}
|
|
69
|
-
let clientId = parameters.clientId;
|
|
70
|
-
if (!clientId) {
|
|
71
|
-
clientId = oauthConfig.googleClientId;
|
|
72
|
-
}
|
|
73
|
-
if (!clientId) {
|
|
74
|
-
throw new Error(`Client ID not configured for ${parameters.provider}`);
|
|
75
|
-
}
|
|
76
|
-
if (!oauthConfig.redirectUri) {
|
|
77
|
-
throw new Error('OAuth redirect URI is not configured.');
|
|
68
|
+
throw new Error('Wallet not initialized. Please wait for connector setup.');
|
|
78
69
|
}
|
|
79
70
|
const publicKey = await wallet.getPublicKey();
|
|
80
71
|
if (!publicKey) {
|
|
81
72
|
throw new Error('Failed to get wallet public key');
|
|
82
73
|
}
|
|
83
|
-
const
|
|
84
|
-
const oauthUrl = (0, oauth_js_1.buildOAuthUrl)({
|
|
74
|
+
const oauthUrl = (0, oauth_js_1.buildBackendOAuthUrl)({
|
|
85
75
|
provider: parameters.provider,
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
76
|
+
backendUrl: oauthConfig.backendUrl,
|
|
77
|
+
projectId: oauthConfig.projectId,
|
|
78
|
+
publicKey,
|
|
79
|
+
returnTo: `${window.location.origin}?oauth_success=true&oauth_provider=${parameters.provider}`,
|
|
89
80
|
});
|
|
90
81
|
const authWindow = (0, oauth_js_1.openOAuthPopup)(oauthUrl);
|
|
91
82
|
if (!authWindow) {
|
|
92
83
|
throw new Error(`Failed to open ${parameters.provider} login window.`);
|
|
93
84
|
}
|
|
94
85
|
return new Promise((resolve, reject) => {
|
|
95
|
-
(0, oauth_js_1.
|
|
86
|
+
const cleanup = (0, oauth_js_1.listenForOAuthMessage)(authWindow, window.location.origin, async () => {
|
|
96
87
|
try {
|
|
97
88
|
await wallet.auth({
|
|
98
89
|
type: 'oauth',
|
|
99
90
|
provider: parameters.provider,
|
|
100
|
-
credential: idToken,
|
|
101
91
|
});
|
|
102
92
|
const [session, eoaAccount] = await Promise.all([
|
|
103
93
|
wallet.getSession(),
|
|
@@ -111,7 +101,10 @@ async function authenticateOAuth(config, parameters) {
|
|
|
111
101
|
catch (err) {
|
|
112
102
|
reject(err);
|
|
113
103
|
}
|
|
114
|
-
},
|
|
104
|
+
}, (error) => {
|
|
105
|
+
cleanup();
|
|
106
|
+
reject(error);
|
|
107
|
+
});
|
|
115
108
|
});
|
|
116
109
|
}
|
|
117
110
|
async function sendOTP(config, parameters) {
|
|
@@ -131,7 +124,6 @@ async function sendOTP(config, parameters) {
|
|
|
131
124
|
});
|
|
132
125
|
return {
|
|
133
126
|
otpId: result.otpId,
|
|
134
|
-
subOrganizationId: result.subOrganizationId,
|
|
135
127
|
};
|
|
136
128
|
}
|
|
137
129
|
async function verifyOTP(config, parameters) {
|
|
@@ -145,7 +137,6 @@ async function verifyOTP(config, parameters) {
|
|
|
145
137
|
mode: 'verifyOtp',
|
|
146
138
|
otpId: parameters.otpId,
|
|
147
139
|
otpCode: parameters.code,
|
|
148
|
-
subOrganizationId: parameters.subOrganizationId,
|
|
149
140
|
});
|
|
150
141
|
const [session, eoaAccount] = await Promise.all([
|
|
151
142
|
wallet.getSession(),
|
|
@@ -167,6 +158,17 @@ async function refreshSession(config, parameters = {}) {
|
|
|
167
158
|
store.getState().setSession(newSession || null);
|
|
168
159
|
return newSession;
|
|
169
160
|
}
|
|
161
|
+
async function getUserEmail(config, parameters) {
|
|
162
|
+
const connector = parameters.connector ?? getZeroDevConnector(config);
|
|
163
|
+
const store = await connector.getStore();
|
|
164
|
+
const wallet = store.getState().wallet;
|
|
165
|
+
if (!wallet)
|
|
166
|
+
throw new Error('Wallet not initialized');
|
|
167
|
+
return await wallet.client.getUserEmail({
|
|
168
|
+
organizationId: parameters.organizationId,
|
|
169
|
+
projectId: parameters.projectId,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
170
172
|
async function exportWallet(config, parameters) {
|
|
171
173
|
const connector = parameters.connector ?? getZeroDevConnector(config);
|
|
172
174
|
const store = await connector.getStore();
|
package/dist/_cjs/connector.js
CHANGED
|
@@ -6,9 +6,48 @@ const sdk_1 = require("@zerodev/sdk");
|
|
|
6
6
|
const constants_1 = require("@zerodev/sdk/constants");
|
|
7
7
|
const wallet_core_1 = require("@zerodev/wallet-core");
|
|
8
8
|
const viem_1 = require("viem");
|
|
9
|
+
const oauth_js_1 = require("./oauth.js");
|
|
9
10
|
const provider_js_1 = require("./provider.js");
|
|
10
11
|
const store_js_1 = require("./store.js");
|
|
11
12
|
const aaUtils_js_1 = require("./utils/aaUtils.js");
|
|
13
|
+
const OAUTH_SUCCESS_PARAM = 'oauth_success';
|
|
14
|
+
const OAUTH_PROVIDER_PARAM = 'oauth_provider';
|
|
15
|
+
async function detectAndHandleOAuthCallback(wallet, store) {
|
|
16
|
+
if (typeof window === 'undefined')
|
|
17
|
+
return false;
|
|
18
|
+
const params = new URLSearchParams(window.location.search);
|
|
19
|
+
const isOAuthCallback = params.get(OAUTH_SUCCESS_PARAM) === 'true';
|
|
20
|
+
if (!isOAuthCallback)
|
|
21
|
+
return false;
|
|
22
|
+
if (window.opener) {
|
|
23
|
+
(0, oauth_js_1.handleOAuthCallback)(OAUTH_SUCCESS_PARAM);
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
console.log('OAuth callback detected, completing authentication...');
|
|
27
|
+
const provider = (params.get(OAUTH_PROVIDER_PARAM) ||
|
|
28
|
+
'google');
|
|
29
|
+
try {
|
|
30
|
+
await wallet.auth({ type: 'oauth', provider });
|
|
31
|
+
const [session, eoaAccount] = await Promise.all([
|
|
32
|
+
wallet.getSession(),
|
|
33
|
+
wallet.toAccount(),
|
|
34
|
+
]);
|
|
35
|
+
store.getState().setEoaAccount(eoaAccount);
|
|
36
|
+
store.getState().setSession(session || null);
|
|
37
|
+
params.delete(OAUTH_SUCCESS_PARAM);
|
|
38
|
+
params.delete(OAUTH_PROVIDER_PARAM);
|
|
39
|
+
const newUrl = params.toString()
|
|
40
|
+
? `${window.location.pathname}?${params.toString()}`
|
|
41
|
+
: window.location.pathname;
|
|
42
|
+
window.history.replaceState({}, '', newUrl);
|
|
43
|
+
console.log('OAuth authentication completed');
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
console.error('OAuth authentication failed:', error);
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
12
51
|
function zeroDevWallet(params) {
|
|
13
52
|
return (0, core_1.createConnector)((wagmiConfig) => {
|
|
14
53
|
let store;
|
|
@@ -29,9 +68,10 @@ function zeroDevWallet(params) {
|
|
|
29
68
|
});
|
|
30
69
|
store = (0, store_js_1.createZeroDevWalletStore)();
|
|
31
70
|
store.getState().setWallet(wallet);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
71
|
+
store.getState().setOAuthConfig({
|
|
72
|
+
backendUrl: params.proxyBaseUrl || `${wallet_core_1.KMS_SERVER_URL}/api/v1`,
|
|
73
|
+
projectId: params.projectId,
|
|
74
|
+
});
|
|
35
75
|
provider = (0, provider_js_1.createProvider)({
|
|
36
76
|
store,
|
|
37
77
|
config: params,
|
|
@@ -44,6 +84,7 @@ function zeroDevWallet(params) {
|
|
|
44
84
|
store.getState().setEoaAccount(eoaAccount);
|
|
45
85
|
store.getState().setSession(session);
|
|
46
86
|
}
|
|
87
|
+
await detectAndHandleOAuthCallback(wallet, store);
|
|
47
88
|
console.log('ZeroDevWallet connector initialized');
|
|
48
89
|
};
|
|
49
90
|
return {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
'use client';
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.useGetUserEmail = useGetUserEmail;
|
|
5
|
+
const react_query_1 = require("@tanstack/react-query");
|
|
6
|
+
const wagmi_1 = require("wagmi");
|
|
7
|
+
const actions_js_1 = require("../actions.js");
|
|
8
|
+
function useGetUserEmail(parameters) {
|
|
9
|
+
const { organizationId, projectId, query } = parameters;
|
|
10
|
+
const config = (0, wagmi_1.useConfig)(parameters);
|
|
11
|
+
return (0, react_query_1.useQuery)({
|
|
12
|
+
...query,
|
|
13
|
+
queryKey: ['getUserEmail', { organizationId, projectId }],
|
|
14
|
+
queryFn: async () => {
|
|
15
|
+
return (0, actions_js_1.getUserEmail)(config, { organizationId, projectId });
|
|
16
|
+
},
|
|
17
|
+
enabled: Boolean(organizationId && projectId),
|
|
18
|
+
});
|
|
19
|
+
}
|
package/dist/_cjs/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createZeroDevWalletStore = exports.OAUTH_PROVIDERS = exports.useVerifyOTP = exports.useSendOTP = exports.useRegisterPasskey = exports.useRefreshSession = exports.useLoginPasskey = exports.useExportWallet = exports.useExportPrivateKey = exports.useAuthenticateOAuth = exports.zeroDevWallet = void 0;
|
|
3
|
+
exports.createZeroDevWalletStore = exports.OAUTH_PROVIDERS = exports.listenForOAuthMessage = exports.handleOAuthCallback = exports.buildBackendOAuthUrl = exports.useVerifyOTP = exports.useSendOTP = exports.useRegisterPasskey = exports.useRefreshSession = exports.useLoginPasskey = exports.useGetUserEmail = exports.useExportWallet = exports.useExportPrivateKey = exports.useAuthenticateOAuth = exports.zeroDevWallet = void 0;
|
|
4
4
|
var connector_js_1 = require("./connector.js");
|
|
5
5
|
Object.defineProperty(exports, "zeroDevWallet", { enumerable: true, get: function () { return connector_js_1.zeroDevWallet; } });
|
|
6
6
|
var useAuthenticateOAuth_js_1 = require("./hooks/useAuthenticateOAuth.js");
|
|
@@ -9,6 +9,8 @@ var useExportPrivateKey_js_1 = require("./hooks/useExportPrivateKey.js");
|
|
|
9
9
|
Object.defineProperty(exports, "useExportPrivateKey", { enumerable: true, get: function () { return useExportPrivateKey_js_1.useExportPrivateKey; } });
|
|
10
10
|
var useExportWallet_js_1 = require("./hooks/useExportWallet.js");
|
|
11
11
|
Object.defineProperty(exports, "useExportWallet", { enumerable: true, get: function () { return useExportWallet_js_1.useExportWallet; } });
|
|
12
|
+
var useGetUserEmail_js_1 = require("./hooks/useGetUserEmail.js");
|
|
13
|
+
Object.defineProperty(exports, "useGetUserEmail", { enumerable: true, get: function () { return useGetUserEmail_js_1.useGetUserEmail; } });
|
|
12
14
|
var useLoginPasskey_js_1 = require("./hooks/useLoginPasskey.js");
|
|
13
15
|
Object.defineProperty(exports, "useLoginPasskey", { enumerable: true, get: function () { return useLoginPasskey_js_1.useLoginPasskey; } });
|
|
14
16
|
var useRefreshSession_js_1 = require("./hooks/useRefreshSession.js");
|
|
@@ -20,6 +22,9 @@ Object.defineProperty(exports, "useSendOTP", { enumerable: true, get: function (
|
|
|
20
22
|
var useVerifyOTP_js_1 = require("./hooks/useVerifyOTP.js");
|
|
21
23
|
Object.defineProperty(exports, "useVerifyOTP", { enumerable: true, get: function () { return useVerifyOTP_js_1.useVerifyOTP; } });
|
|
22
24
|
var oauth_js_1 = require("./oauth.js");
|
|
25
|
+
Object.defineProperty(exports, "buildBackendOAuthUrl", { enumerable: true, get: function () { return oauth_js_1.buildBackendOAuthUrl; } });
|
|
26
|
+
Object.defineProperty(exports, "handleOAuthCallback", { enumerable: true, get: function () { return oauth_js_1.handleOAuthCallback; } });
|
|
27
|
+
Object.defineProperty(exports, "listenForOAuthMessage", { enumerable: true, get: function () { return oauth_js_1.listenForOAuthMessage; } });
|
|
23
28
|
Object.defineProperty(exports, "OAUTH_PROVIDERS", { enumerable: true, get: function () { return oauth_js_1.OAUTH_PROVIDERS; } });
|
|
24
29
|
var store_js_1 = require("./store.js");
|
|
25
30
|
Object.defineProperty(exports, "createZeroDevWalletStore", { enumerable: true, get: function () { return store_js_1.createZeroDevWalletStore; } });
|
package/dist/_cjs/oauth.js
CHANGED
|
@@ -1,42 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.OAUTH_PROVIDERS = void 0;
|
|
4
|
-
exports.buildOAuthUrl = buildOAuthUrl;
|
|
5
4
|
exports.openOAuthPopup = openOAuthPopup;
|
|
6
|
-
exports.extractOAuthToken = extractOAuthToken;
|
|
7
|
-
exports.pollOAuthPopup = pollOAuthPopup;
|
|
8
5
|
exports.generateOAuthNonce = generateOAuthNonce;
|
|
6
|
+
exports.buildBackendOAuthUrl = buildBackendOAuthUrl;
|
|
7
|
+
exports.listenForOAuthMessage = listenForOAuthMessage;
|
|
8
|
+
exports.handleOAuthCallback = handleOAuthCallback;
|
|
9
9
|
const viem_1 = require("viem");
|
|
10
10
|
exports.OAUTH_PROVIDERS = {
|
|
11
11
|
GOOGLE: 'google',
|
|
12
12
|
};
|
|
13
|
-
const GOOGLE_AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth';
|
|
14
13
|
const POPUP_WIDTH = 500;
|
|
15
14
|
const POPUP_HEIGHT = 600;
|
|
16
|
-
function buildOAuthUrl(params) {
|
|
17
|
-
const { provider, clientId, redirectUri, nonce, state } = params;
|
|
18
|
-
if (provider !== exports.OAUTH_PROVIDERS.GOOGLE) {
|
|
19
|
-
throw new Error(`Unsupported OAuth provider: ${provider}`);
|
|
20
|
-
}
|
|
21
|
-
const authUrl = new URL(GOOGLE_AUTH_URL);
|
|
22
|
-
authUrl.searchParams.set('client_id', clientId);
|
|
23
|
-
authUrl.searchParams.set('redirect_uri', redirectUri);
|
|
24
|
-
authUrl.searchParams.set('response_type', 'id_token');
|
|
25
|
-
authUrl.searchParams.set('scope', 'openid email profile');
|
|
26
|
-
authUrl.searchParams.set('nonce', nonce);
|
|
27
|
-
authUrl.searchParams.set('prompt', 'select_account');
|
|
28
|
-
let stateParam = `provider=${provider}`;
|
|
29
|
-
if (state) {
|
|
30
|
-
const additionalState = Object.entries(state)
|
|
31
|
-
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
|
32
|
-
.join('&');
|
|
33
|
-
if (additionalState) {
|
|
34
|
-
stateParam += `&${additionalState}`;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
authUrl.searchParams.set('state', stateParam);
|
|
38
|
-
return authUrl.toString();
|
|
39
|
-
}
|
|
40
15
|
function openOAuthPopup(url) {
|
|
41
16
|
const width = POPUP_WIDTH;
|
|
42
17
|
const height = POPUP_HEIGHT;
|
|
@@ -48,37 +23,67 @@ function openOAuthPopup(url) {
|
|
|
48
23
|
}
|
|
49
24
|
return authWindow;
|
|
50
25
|
}
|
|
51
|
-
function
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
26
|
+
function generateOAuthNonce(publicKey) {
|
|
27
|
+
return (0, viem_1.sha256)(publicKey).replace(/^0x/, '');
|
|
28
|
+
}
|
|
29
|
+
function buildBackendOAuthUrl(params) {
|
|
30
|
+
const { provider, backendUrl, projectId, publicKey, returnTo } = params;
|
|
31
|
+
if (provider !== exports.OAUTH_PROVIDERS.GOOGLE) {
|
|
32
|
+
throw new Error(`Unsupported OAuth provider: ${provider}`);
|
|
57
33
|
}
|
|
58
|
-
|
|
34
|
+
const oauthUrl = new URL(`${backendUrl}/oauth/google/login`);
|
|
35
|
+
oauthUrl.searchParams.set('project_id', projectId);
|
|
36
|
+
oauthUrl.searchParams.set('pub_key', publicKey.replace(/^0x/, ''));
|
|
37
|
+
oauthUrl.searchParams.set('return_to', returnTo);
|
|
38
|
+
return oauthUrl.toString();
|
|
59
39
|
}
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const token = extractOAuthToken(url);
|
|
71
|
-
if (token) {
|
|
72
|
-
authWindow.close();
|
|
73
|
-
clearInterval(interval);
|
|
74
|
-
onSuccess(token);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
40
|
+
function listenForOAuthMessage(authWindow, expectedOrigin, onSuccess, onError) {
|
|
41
|
+
let cleaned = false;
|
|
42
|
+
const handleMessage = (event) => {
|
|
43
|
+
if (event.origin !== expectedOrigin)
|
|
44
|
+
return;
|
|
45
|
+
if (!event.data || typeof event.data !== 'object')
|
|
46
|
+
return;
|
|
47
|
+
if (event.data.type === 'oauth_success') {
|
|
48
|
+
cleanup();
|
|
49
|
+
onSuccess();
|
|
77
50
|
}
|
|
78
|
-
|
|
51
|
+
else if (event.data.type === 'oauth_error') {
|
|
52
|
+
cleanup();
|
|
53
|
+
onError(new Error(event.data.error || 'OAuth authentication failed'));
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const checkWindowClosed = setInterval(() => {
|
|
57
|
+
if (authWindow.closed) {
|
|
58
|
+
cleanup();
|
|
59
|
+
onError(new Error('Authentication window was closed'));
|
|
79
60
|
}
|
|
80
61
|
}, 500);
|
|
62
|
+
const cleanup = () => {
|
|
63
|
+
if (cleaned)
|
|
64
|
+
return;
|
|
65
|
+
cleaned = true;
|
|
66
|
+
window.removeEventListener('message', handleMessage);
|
|
67
|
+
clearInterval(checkWindowClosed);
|
|
68
|
+
};
|
|
69
|
+
window.addEventListener('message', handleMessage);
|
|
70
|
+
return cleanup;
|
|
81
71
|
}
|
|
82
|
-
function
|
|
83
|
-
|
|
72
|
+
function handleOAuthCallback(successParam = 'oauth_success') {
|
|
73
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
74
|
+
const isSuccess = urlParams.get(successParam) === 'true';
|
|
75
|
+
const error = urlParams.get('error');
|
|
76
|
+
if (window.opener) {
|
|
77
|
+
if (isSuccess) {
|
|
78
|
+
window.opener.postMessage({ type: 'oauth_success' }, window.location.origin);
|
|
79
|
+
window.close();
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
if (error) {
|
|
83
|
+
window.opener.postMessage({ type: 'oauth_error', error }, window.location.origin);
|
|
84
|
+
window.close();
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
84
89
|
}
|
package/dist/_esm/actions.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { connect as wagmiConnect } from '@wagmi/core/actions';
|
|
2
2
|
import { createIframeStamper, exportPrivateKey as exportPrivateKeySdk, exportWallet as exportWalletSdk, } from '@zerodev/wallet-core';
|
|
3
|
-
import {
|
|
3
|
+
import { buildBackendOAuthUrl, listenForOAuthMessage, openOAuthPopup, } from './oauth.js';
|
|
4
4
|
/**
|
|
5
5
|
* Get ZeroDev connector from config
|
|
6
6
|
*/
|
|
@@ -61,6 +61,7 @@ export async function loginPasskey(config, parameters) {
|
|
|
61
61
|
}
|
|
62
62
|
/**
|
|
63
63
|
* Authenticate with OAuth (opens popup)
|
|
64
|
+
* Uses backend OAuth flow where the backend handles PKCE and token exchange
|
|
64
65
|
*/
|
|
65
66
|
export async function authenticateOAuth(config, parameters) {
|
|
66
67
|
const connector = parameters.connector ?? getZeroDevConnector(config);
|
|
@@ -71,46 +72,36 @@ export async function authenticateOAuth(config, parameters) {
|
|
|
71
72
|
if (!wallet)
|
|
72
73
|
throw new Error('Wallet not initialized');
|
|
73
74
|
if (!oauthConfig) {
|
|
74
|
-
throw new Error('
|
|
75
|
+
throw new Error('Wallet not initialized. Please wait for connector setup.');
|
|
75
76
|
}
|
|
76
|
-
// Get
|
|
77
|
-
let clientId = parameters.clientId;
|
|
78
|
-
if (!clientId) {
|
|
79
|
-
clientId = oauthConfig.googleClientId;
|
|
80
|
-
}
|
|
81
|
-
if (!clientId) {
|
|
82
|
-
throw new Error(`Client ID not configured for ${parameters.provider}`);
|
|
83
|
-
}
|
|
84
|
-
if (!oauthConfig.redirectUri) {
|
|
85
|
-
throw new Error('OAuth redirect URI is not configured.');
|
|
86
|
-
}
|
|
87
|
-
// Generate nonce from wallet public key
|
|
77
|
+
// Get wallet public key for the OAuth flow
|
|
88
78
|
const publicKey = await wallet.getPublicKey();
|
|
89
79
|
if (!publicKey) {
|
|
90
80
|
throw new Error('Failed to get wallet public key');
|
|
91
81
|
}
|
|
92
|
-
|
|
93
|
-
//
|
|
94
|
-
const oauthUrl =
|
|
82
|
+
// Build OAuth URL that redirects to backend
|
|
83
|
+
// Use current origin as redirect - SDK auto-detects callback on any page
|
|
84
|
+
const oauthUrl = buildBackendOAuthUrl({
|
|
95
85
|
provider: parameters.provider,
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
86
|
+
backendUrl: oauthConfig.backendUrl,
|
|
87
|
+
projectId: oauthConfig.projectId,
|
|
88
|
+
publicKey,
|
|
89
|
+
returnTo: `${window.location.origin}?oauth_success=true&oauth_provider=${parameters.provider}`,
|
|
99
90
|
});
|
|
100
91
|
// Open popup
|
|
101
92
|
const authWindow = openOAuthPopup(oauthUrl);
|
|
102
93
|
if (!authWindow) {
|
|
103
94
|
throw new Error(`Failed to open ${parameters.provider} login window.`);
|
|
104
95
|
}
|
|
105
|
-
//
|
|
96
|
+
// Listen for OAuth completion via postMessage
|
|
106
97
|
return new Promise((resolve, reject) => {
|
|
107
|
-
|
|
98
|
+
const cleanup = listenForOAuthMessage(authWindow, window.location.origin, async () => {
|
|
108
99
|
try {
|
|
109
100
|
// Complete OAuth authentication with wallet-core
|
|
101
|
+
// The backend has stored the OAuth session in a cookie
|
|
110
102
|
await wallet.auth({
|
|
111
103
|
type: 'oauth',
|
|
112
104
|
provider: parameters.provider,
|
|
113
|
-
credential: idToken,
|
|
114
105
|
});
|
|
115
106
|
const [session, eoaAccount] = await Promise.all([
|
|
116
107
|
wallet.getSession(),
|
|
@@ -125,7 +116,10 @@ export async function authenticateOAuth(config, parameters) {
|
|
|
125
116
|
catch (err) {
|
|
126
117
|
reject(err);
|
|
127
118
|
}
|
|
128
|
-
},
|
|
119
|
+
}, (error) => {
|
|
120
|
+
cleanup();
|
|
121
|
+
reject(error);
|
|
122
|
+
});
|
|
129
123
|
});
|
|
130
124
|
}
|
|
131
125
|
/**
|
|
@@ -149,7 +143,6 @@ export async function sendOTP(config, parameters) {
|
|
|
149
143
|
});
|
|
150
144
|
return {
|
|
151
145
|
otpId: result.otpId,
|
|
152
|
-
subOrganizationId: result.subOrganizationId,
|
|
153
146
|
};
|
|
154
147
|
}
|
|
155
148
|
/**
|
|
@@ -167,7 +160,6 @@ export async function verifyOTP(config, parameters) {
|
|
|
167
160
|
mode: 'verifyOtp',
|
|
168
161
|
otpId: parameters.otpId,
|
|
169
162
|
otpCode: parameters.code,
|
|
170
|
-
subOrganizationId: parameters.subOrganizationId,
|
|
171
163
|
});
|
|
172
164
|
const [session, eoaAccount] = await Promise.all([
|
|
173
165
|
wallet.getSession(),
|
|
@@ -194,6 +186,22 @@ export async function refreshSession(config, parameters = {}) {
|
|
|
194
186
|
store.getState().setSession(newSession || null);
|
|
195
187
|
return newSession;
|
|
196
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* Get user email
|
|
191
|
+
*/
|
|
192
|
+
export async function getUserEmail(config, parameters) {
|
|
193
|
+
const connector = parameters.connector ?? getZeroDevConnector(config);
|
|
194
|
+
// @ts-expect-error - getStore is a custom method
|
|
195
|
+
const store = await connector.getStore();
|
|
196
|
+
const wallet = store.getState().wallet;
|
|
197
|
+
if (!wallet)
|
|
198
|
+
throw new Error('Wallet not initialized');
|
|
199
|
+
// Call the core SDK method
|
|
200
|
+
return await wallet.client.getUserEmail({
|
|
201
|
+
organizationId: parameters.organizationId,
|
|
202
|
+
projectId: parameters.projectId,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
197
205
|
/**
|
|
198
206
|
* Export wallet
|
|
199
207
|
*/
|
package/dist/_esm/connector.js
CHANGED
|
@@ -1,11 +1,59 @@
|
|
|
1
1
|
import { createConnector } from '@wagmi/core';
|
|
2
2
|
import { createKernelAccount, createKernelAccountClient, createZeroDevPaymasterClient, } from '@zerodev/sdk';
|
|
3
3
|
import { getEntryPoint, KERNEL_V3_3 } from '@zerodev/sdk/constants';
|
|
4
|
-
import { createZeroDevWallet } from '@zerodev/wallet-core';
|
|
4
|
+
import { createZeroDevWallet, KMS_SERVER_URL } from '@zerodev/wallet-core';
|
|
5
5
|
import { createPublicClient, http } from 'viem';
|
|
6
|
+
import { handleOAuthCallback } from './oauth.js';
|
|
6
7
|
import { createProvider } from './provider.js';
|
|
7
8
|
import { createZeroDevWalletStore } from './store.js';
|
|
8
9
|
import { getAAUrl } from './utils/aaUtils.js';
|
|
10
|
+
// OAuth URL parameter used to detect callback
|
|
11
|
+
const OAUTH_SUCCESS_PARAM = 'oauth_success';
|
|
12
|
+
const OAUTH_PROVIDER_PARAM = 'oauth_provider';
|
|
13
|
+
/**
|
|
14
|
+
* Detect OAuth callback from URL params and handle it.
|
|
15
|
+
* - If in popup: sends postMessage to opener and closes
|
|
16
|
+
* - If not in popup: completes auth directly
|
|
17
|
+
*/
|
|
18
|
+
async function detectAndHandleOAuthCallback(wallet, store) {
|
|
19
|
+
if (typeof window === 'undefined')
|
|
20
|
+
return false;
|
|
21
|
+
const params = new URLSearchParams(window.location.search);
|
|
22
|
+
const isOAuthCallback = params.get(OAUTH_SUCCESS_PARAM) === 'true';
|
|
23
|
+
if (!isOAuthCallback)
|
|
24
|
+
return false;
|
|
25
|
+
// If in popup, use the existing handler to notify opener
|
|
26
|
+
if (window.opener) {
|
|
27
|
+
handleOAuthCallback(OAUTH_SUCCESS_PARAM);
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
// Not in popup - complete auth directly (redirect flow)
|
|
31
|
+
console.log('OAuth callback detected, completing authentication...');
|
|
32
|
+
const provider = (params.get(OAUTH_PROVIDER_PARAM) ||
|
|
33
|
+
'google');
|
|
34
|
+
try {
|
|
35
|
+
await wallet.auth({ type: 'oauth', provider });
|
|
36
|
+
const [session, eoaAccount] = await Promise.all([
|
|
37
|
+
wallet.getSession(),
|
|
38
|
+
wallet.toAccount(),
|
|
39
|
+
]);
|
|
40
|
+
store.getState().setEoaAccount(eoaAccount);
|
|
41
|
+
store.getState().setSession(session || null);
|
|
42
|
+
// Clean up URL params
|
|
43
|
+
params.delete(OAUTH_SUCCESS_PARAM);
|
|
44
|
+
params.delete(OAUTH_PROVIDER_PARAM);
|
|
45
|
+
const newUrl = params.toString()
|
|
46
|
+
? `${window.location.pathname}?${params.toString()}`
|
|
47
|
+
: window.location.pathname;
|
|
48
|
+
window.history.replaceState({}, '', newUrl);
|
|
49
|
+
console.log('OAuth authentication completed');
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
console.error('OAuth authentication failed:', error);
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
9
57
|
export function zeroDevWallet(params) {
|
|
10
58
|
return createConnector((wagmiConfig) => {
|
|
11
59
|
let store;
|
|
@@ -30,10 +78,11 @@ export function zeroDevWallet(params) {
|
|
|
30
78
|
// Create store
|
|
31
79
|
store = createZeroDevWalletStore();
|
|
32
80
|
store.getState().setWallet(wallet);
|
|
33
|
-
// Store OAuth config
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
81
|
+
// Store OAuth config - uses proxyBaseUrl and projectId from params
|
|
82
|
+
store.getState().setOAuthConfig({
|
|
83
|
+
backendUrl: params.proxyBaseUrl || `${KMS_SERVER_URL}/api/v1`,
|
|
84
|
+
projectId: params.projectId,
|
|
85
|
+
});
|
|
37
86
|
// Create EIP-1193 provider
|
|
38
87
|
provider = createProvider({
|
|
39
88
|
store,
|
|
@@ -48,6 +97,8 @@ export function zeroDevWallet(params) {
|
|
|
48
97
|
store.getState().setEoaAccount(eoaAccount);
|
|
49
98
|
store.getState().setSession(session);
|
|
50
99
|
}
|
|
100
|
+
// Auto-detect OAuth callback (when popup redirects back with ?oauth_success=true)
|
|
101
|
+
await detectAndHandleOAuthCallback(wallet, store);
|
|
51
102
|
console.log('ZeroDevWallet connector initialized');
|
|
52
103
|
};
|
|
53
104
|
return {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useQuery, } from '@tanstack/react-query';
|
|
3
|
+
import { useConfig } from 'wagmi';
|
|
4
|
+
import { getUserEmail } from '../actions.js';
|
|
5
|
+
/**
|
|
6
|
+
* Hook to fetch user email address
|
|
7
|
+
*/
|
|
8
|
+
export function useGetUserEmail(parameters) {
|
|
9
|
+
const { organizationId, projectId, query } = parameters;
|
|
10
|
+
const config = useConfig(parameters);
|
|
11
|
+
return useQuery({
|
|
12
|
+
...query,
|
|
13
|
+
queryKey: ['getUserEmail', { organizationId, projectId }],
|
|
14
|
+
queryFn: async () => {
|
|
15
|
+
return getUserEmail(config, { organizationId, projectId });
|
|
16
|
+
},
|
|
17
|
+
enabled: Boolean(organizationId && projectId),
|
|
18
|
+
});
|
|
19
|
+
}
|