@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.
Files changed (38) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +17 -14
  3. package/dist/_cjs/actions.js +53 -24
  4. package/dist/_cjs/connector.js +44 -3
  5. package/dist/_cjs/hooks/useExportPrivateKey.js +18 -0
  6. package/dist/_cjs/hooks/useGetUserEmail.js +19 -0
  7. package/dist/_cjs/index.js +8 -1
  8. package/dist/_cjs/oauth.js +60 -55
  9. package/dist/_esm/actions.js +65 -27
  10. package/dist/_esm/connector.js +56 -5
  11. package/dist/_esm/hooks/useExportPrivateKey.js +18 -0
  12. package/dist/_esm/hooks/useGetUserEmail.js +19 -0
  13. package/dist/_esm/index.js +3 -1
  14. package/dist/_esm/oauth.js +71 -53
  15. package/dist/_types/actions.d.ts +41 -6
  16. package/dist/_types/actions.d.ts.map +1 -1
  17. package/dist/_types/connector.d.ts +0 -2
  18. package/dist/_types/connector.d.ts.map +1 -1
  19. package/dist/_types/hooks/useExportPrivateKey.d.ts +18 -0
  20. package/dist/_types/hooks/useExportPrivateKey.d.ts.map +1 -0
  21. package/dist/_types/hooks/useGetUserEmail.d.ts +20 -0
  22. package/dist/_types/hooks/useGetUserEmail.d.ts.map +1 -0
  23. package/dist/_types/index.d.ts +4 -2
  24. package/dist/_types/index.d.ts.map +1 -1
  25. package/dist/_types/oauth.d.ts +25 -12
  26. package/dist/_types/oauth.d.ts.map +1 -1
  27. package/dist/_types/store.d.ts +7 -3
  28. package/dist/_types/store.d.ts.map +1 -1
  29. package/dist/tsconfig.build.tsbuildinfo +1 -1
  30. package/package.json +2 -2
  31. package/src/actions.ts +122 -44
  32. package/src/connector.ts +68 -7
  33. package/src/hooks/useExportPrivateKey.ts +57 -0
  34. package/src/hooks/useGetUserEmail.ts +54 -0
  35. package/src/index.ts +9 -2
  36. package/src/oauth.ts +97 -78
  37. package/src/store.ts +9 -4
  38. package/tsconfig.build.tsbuildinfo +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
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
+
16
+ ## 0.0.1-alpha.6
17
+
18
+ ### Patch Changes
19
+
20
+ - feat: Added private key export feature
21
+ - Updated dependencies
22
+ - @zerodev/wallet-core@0.0.1-alpha.6
23
+
3
24
  ## 0.0.1-alpha.5
4
25
 
5
26
  ### 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 automatically, handles token exchange
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: string // Required: Bundler/paymaster URL
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
 
@@ -224,16 +215,28 @@ const refreshSession = useRefreshSession()
224
215
  await refreshSession.mutateAsync({})
225
216
  ```
226
217
 
227
- ### Export Wallet
218
+ ### Export Wallet (Seed Phrase)
228
219
 
229
220
  ```typescript
230
221
  const exportWallet = useExportWallet()
231
222
 
223
+ // Container element must exist: <div id="export-container" />
232
224
  await exportWallet.mutateAsync({
233
225
  iframeContainerId: 'export-container'
234
226
  })
235
227
  ```
236
228
 
229
+ ### Export Private Key
230
+
231
+ ```typescript
232
+ const exportPrivateKey = useExportPrivateKey()
233
+
234
+ // Container element must exist: <div id="export-container" />
235
+ await exportPrivateKey.mutateAsync({
236
+ iframeContainerId: 'export-container'
237
+ })
238
+ ```
239
+
237
240
  ## API Reference
238
241
 
239
242
  ### Hooks
@@ -247,6 +250,7 @@ All hooks follow the TanStack Query mutation pattern:
247
250
  - `useVerifyOTP()` - Verify OTP code
248
251
  - `useRefreshSession()` - Manually refresh session
249
252
  - `useExportWallet()` - Export wallet seed phrase
253
+ - `useExportPrivateKey()` - Export wallet private key
250
254
 
251
255
  ### Connector
252
256
 
@@ -259,7 +263,6 @@ All hooks follow the TanStack Query mutation pattern:
259
263
  ### Types
260
264
 
261
265
  - `OAuthProvider` - OAuth provider type
262
- - `OAuthConfig` - OAuth configuration type
263
266
  - `ZeroDevWalletConnectorParams` - Connector parameters
264
267
  - `ZeroDevWalletState` - Store state type
265
268
  - `ZeroDevProvider` - EIP-1193 provider type
@@ -6,8 +6,11 @@ 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;
11
+ exports.exportPrivateKey = exportPrivateKey;
10
12
  const actions_1 = require("@wagmi/core/actions");
13
+ const wallet_core_1 = require("@zerodev/wallet-core");
11
14
  const oauth_js_1 = require("./oauth.js");
12
15
  function getZeroDevConnector(config) {
13
16
  const connector = config.connectors.find((c) => c.id === 'zerodev-wallet');
@@ -62,40 +65,29 @@ async function authenticateOAuth(config, parameters) {
62
65
  if (!wallet)
63
66
  throw new Error('Wallet not initialized');
64
67
  if (!oauthConfig) {
65
- throw new Error('OAuth is not configured. Please provide oauthConfig to zeroDevWallet connector.');
66
- }
67
- let clientId = parameters.clientId;
68
- if (!clientId) {
69
- clientId = oauthConfig.googleClientId;
70
- }
71
- if (!clientId) {
72
- throw new Error(`Client ID not configured for ${parameters.provider}`);
73
- }
74
- if (!oauthConfig.redirectUri) {
75
- throw new Error('OAuth redirect URI is not configured.');
68
+ throw new Error('Wallet not initialized. Please wait for connector setup.');
76
69
  }
77
70
  const publicKey = await wallet.getPublicKey();
78
71
  if (!publicKey) {
79
72
  throw new Error('Failed to get wallet public key');
80
73
  }
81
- const nonce = (0, oauth_js_1.generateOAuthNonce)(publicKey);
82
- const oauthUrl = (0, oauth_js_1.buildOAuthUrl)({
74
+ const oauthUrl = (0, oauth_js_1.buildBackendOAuthUrl)({
83
75
  provider: parameters.provider,
84
- clientId,
85
- redirectUri: oauthConfig.redirectUri,
86
- nonce,
76
+ backendUrl: oauthConfig.backendUrl,
77
+ projectId: oauthConfig.projectId,
78
+ publicKey,
79
+ returnTo: `${window.location.origin}?oauth_success=true&oauth_provider=${parameters.provider}`,
87
80
  });
88
81
  const authWindow = (0, oauth_js_1.openOAuthPopup)(oauthUrl);
89
82
  if (!authWindow) {
90
83
  throw new Error(`Failed to open ${parameters.provider} login window.`);
91
84
  }
92
85
  return new Promise((resolve, reject) => {
93
- (0, oauth_js_1.pollOAuthPopup)(authWindow, window.location.origin, async (idToken) => {
86
+ const cleanup = (0, oauth_js_1.listenForOAuthMessage)(authWindow, window.location.origin, async () => {
94
87
  try {
95
88
  await wallet.auth({
96
89
  type: 'oauth',
97
90
  provider: parameters.provider,
98
- credential: idToken,
99
91
  });
100
92
  const [session, eoaAccount] = await Promise.all([
101
93
  wallet.getSession(),
@@ -109,7 +101,10 @@ async function authenticateOAuth(config, parameters) {
109
101
  catch (err) {
110
102
  reject(err);
111
103
  }
112
- }, reject);
104
+ }, (error) => {
105
+ cleanup();
106
+ reject(error);
107
+ });
113
108
  });
114
109
  }
115
110
  async function sendOTP(config, parameters) {
@@ -129,7 +124,6 @@ async function sendOTP(config, parameters) {
129
124
  });
130
125
  return {
131
126
  otpId: result.otpId,
132
- subOrganizationId: result.subOrganizationId,
133
127
  };
134
128
  }
135
129
  async function verifyOTP(config, parameters) {
@@ -143,7 +137,6 @@ async function verifyOTP(config, parameters) {
143
137
  mode: 'verifyOtp',
144
138
  otpId: parameters.otpId,
145
139
  otpCode: parameters.code,
146
- subOrganizationId: parameters.subOrganizationId,
147
140
  });
148
141
  const [session, eoaAccount] = await Promise.all([
149
142
  wallet.getSession(),
@@ -165,24 +158,34 @@ async function refreshSession(config, parameters = {}) {
165
158
  store.getState().setSession(newSession || null);
166
159
  return newSession;
167
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
+ }
168
172
  async function exportWallet(config, parameters) {
169
173
  const connector = parameters.connector ?? getZeroDevConnector(config);
170
174
  const store = await connector.getStore();
171
175
  const wallet = store.getState().wallet;
172
176
  if (!wallet)
173
177
  throw new Error('Wallet not initialized');
174
- const { exportWallet: exportWalletSdk, createIframeStamper } = await Promise.resolve().then(() => require('@zerodev/wallet-core'));
175
178
  const iframeContainer = document.getElementById(parameters.iframeContainerId);
176
179
  if (!iframeContainer) {
177
180
  throw new Error('Iframe container not found');
178
181
  }
179
- const iframeStamper = await createIframeStamper({
182
+ const iframeStamper = await (0, wallet_core_1.createIframeStamper)({
180
183
  iframeUrl: 'https://export.turnkey.com',
181
184
  iframeContainer,
182
185
  iframeElementId: 'export-wallet-iframe',
183
186
  });
184
187
  const publicKey = await iframeStamper.init();
185
- const { exportBundle, organizationId } = await exportWalletSdk({
188
+ const { exportBundle, organizationId } = await (0, wallet_core_1.exportWallet)({
186
189
  wallet,
187
190
  targetPublicKey: publicKey,
188
191
  });
@@ -191,3 +194,29 @@ async function exportWallet(config, parameters) {
191
194
  throw new Error('Failed to inject export bundle');
192
195
  }
193
196
  }
197
+ async function exportPrivateKey(config, parameters) {
198
+ const connector = parameters.connector ?? getZeroDevConnector(config);
199
+ const store = await connector.getStore();
200
+ const wallet = store.getState().wallet;
201
+ if (!wallet)
202
+ throw new Error('Wallet not initialized');
203
+ const iframeContainer = document.getElementById(parameters.iframeContainerId);
204
+ if (!iframeContainer) {
205
+ throw new Error('Iframe container not found');
206
+ }
207
+ const iframeStamper = await (0, wallet_core_1.createIframeStamper)({
208
+ iframeUrl: 'https://export.turnkey.com',
209
+ iframeContainer,
210
+ iframeElementId: 'export-private-key-iframe',
211
+ });
212
+ const publicKey = await iframeStamper.init();
213
+ const { exportBundle, organizationId } = await (0, wallet_core_1.exportPrivateKey)({
214
+ wallet,
215
+ targetPublicKey: publicKey,
216
+ ...(parameters.address && { address: parameters.address }),
217
+ });
218
+ const success = await iframeStamper.injectKeyExportBundle(exportBundle, organizationId, parameters.keyFormat ?? 'Hexadecimal');
219
+ if (success !== true) {
220
+ throw new Error('Failed to inject export bundle');
221
+ }
222
+ }
@@ -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
- if (params.oauthConfig) {
33
- store.getState().setOAuthConfig(params.oauthConfig);
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,18 @@
1
+ "use strict";
2
+ 'use client';
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.useExportPrivateKey = useExportPrivateKey;
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 useExportPrivateKey(parameters = {}) {
9
+ const { mutation } = parameters;
10
+ const config = (0, wagmi_1.useConfig)(parameters);
11
+ return (0, react_query_1.useMutation)({
12
+ ...mutation,
13
+ async mutationFn(variables) {
14
+ return (0, actions_js_1.exportPrivateKey)(config, variables);
15
+ },
16
+ mutationKey: ['exportPrivateKey'],
17
+ });
18
+ }
@@ -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
+ }
@@ -1,12 +1,16 @@
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.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");
7
7
  Object.defineProperty(exports, "useAuthenticateOAuth", { enumerable: true, get: function () { return useAuthenticateOAuth_js_1.useAuthenticateOAuth; } });
8
+ var useExportPrivateKey_js_1 = require("./hooks/useExportPrivateKey.js");
9
+ Object.defineProperty(exports, "useExportPrivateKey", { enumerable: true, get: function () { return useExportPrivateKey_js_1.useExportPrivateKey; } });
8
10
  var useExportWallet_js_1 = require("./hooks/useExportWallet.js");
9
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; } });
10
14
  var useLoginPasskey_js_1 = require("./hooks/useLoginPasskey.js");
11
15
  Object.defineProperty(exports, "useLoginPasskey", { enumerable: true, get: function () { return useLoginPasskey_js_1.useLoginPasskey; } });
12
16
  var useRefreshSession_js_1 = require("./hooks/useRefreshSession.js");
@@ -18,6 +22,9 @@ Object.defineProperty(exports, "useSendOTP", { enumerable: true, get: function (
18
22
  var useVerifyOTP_js_1 = require("./hooks/useVerifyOTP.js");
19
23
  Object.defineProperty(exports, "useVerifyOTP", { enumerable: true, get: function () { return useVerifyOTP_js_1.useVerifyOTP; } });
20
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; } });
21
28
  Object.defineProperty(exports, "OAUTH_PROVIDERS", { enumerable: true, get: function () { return oauth_js_1.OAUTH_PROVIDERS; } });
22
29
  var store_js_1 = require("./store.js");
23
30
  Object.defineProperty(exports, "createZeroDevWalletStore", { enumerable: true, get: function () { return store_js_1.createZeroDevWalletStore; } });
@@ -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 extractOAuthToken(url) {
52
- const hashParams = new URLSearchParams(url.split('#')[1]);
53
- let idToken = hashParams.get('id_token');
54
- if (!idToken) {
55
- const queryParams = new URLSearchParams(url.split('?')[1]);
56
- idToken = queryParams.get('id_token');
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
- return idToken;
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 pollOAuthPopup(authWindow, originUrl, onSuccess, onError) {
61
- const interval = setInterval(() => {
62
- try {
63
- if (authWindow.closed) {
64
- clearInterval(interval);
65
- onError(new Error('Authentication window was closed'));
66
- return;
67
- }
68
- const url = authWindow.location.href || '';
69
- if (url.startsWith(originUrl)) {
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
- catch {
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 generateOAuthNonce(publicKey) {
83
- return (0, viem_1.sha256)(publicKey).replace(/^0x/, '');
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
  }