@openkey/sdk 0.1.0 → 0.2.1

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/README.md CHANGED
@@ -9,7 +9,8 @@ OpenKey provides secure cryptographic key management using passkeys (WebAuthn).
9
9
  **Key features:**
10
10
  - **Passkey-secured keys** - Keys are protected by WebAuthn, not passwords
11
11
  - **No seed phrases** - Never worry about losing or exposing recovery phrases
12
- - **Popup-based signing** - Clean UX with authorization popups
12
+ - **Iframe modal signing** - Clean inline UX with auto-fallback to popup
13
+ - **External wallet support** - Link and sign with MetaMask, Rainbow, etc.
13
14
  - **EIP-1193 compatible** - Works with ethers.js, viem, and other web3 libraries
14
15
 
15
16
  ## Installation
@@ -25,164 +26,151 @@ bun add @openkey/sdk
25
26
  ## Quick Start
26
27
 
27
28
  ```typescript
28
- import { OpenKey } from '@openkey/sdk';
29
+ import { OpenKey, OpenKeyEIP1193Provider } from '@openkey/sdk';
29
30
 
30
- // Initialize the SDK
31
+ // Initialize the SDK (iframe modal by default)
31
32
  const openkey = new OpenKey({
32
- host: 'https://openkey.so', // Production
33
- // host: 'http://localhost:5173', // Development
33
+ host: 'https://openkey.so',
34
34
  });
35
35
 
36
- // Connect to OpenKey (opens popup for user to select/create a key)
37
- const { address, keyId } = await openkey.connect();
38
- console.log('Connected:', address);
36
+ // Connect (opens iframe modal for passkey auth + key selection)
37
+ const authResult = await openkey.connect();
38
+ console.log('Connected:', authResult.address, authResult.keyType);
39
39
 
40
- // Sign a message (opens popup for user approval)
41
- const signature = await openkey.signMessage({
40
+ // Sign a message (opens iframe modal for user approval)
41
+ const { signature } = await openkey.signMessage({
42
42
  message: 'Hello, OpenKey!',
43
- keyId,
43
+ keyId: authResult.keyId,
44
44
  });
45
- console.log('Signature:', signature);
45
+ ```
46
+
47
+ ## UI Modes
48
+
49
+ The SDK supports three UI modes for auth and signing flows:
50
+
51
+ | Mode | Description |
52
+ |------|-------------|
53
+ | `'iframe'` (default) | Inline modal overlay. No popup blockers. Responsive (card on desktop, bottom sheet on mobile). |
54
+ | `'popup'` | Separate browser window. Classic approach. May be blocked by browsers. |
55
+ | `'redirect'` | Full page navigation to OpenKey. User returns after completion. |
56
+
57
+ ```typescript
58
+ // Set default mode in constructor
59
+ const openkey = new OpenKey({ mode: 'iframe' });
60
+
61
+ // Override per operation
62
+ await openkey.connect({ mode: 'popup' });
63
+ ```
64
+
65
+ ### Auto-fallback
66
+
67
+ When using `'iframe'` mode, the SDK automatically falls back to `'popup'` if the iframe is blocked by the app's CSP. A brief toast notification ("Opening in new window...") is shown. To avoid the fallback, add to your CSP:
68
+
69
+ ```
70
+ frame-src https://openkey.so;
46
71
  ```
47
72
 
48
73
  ## API Reference
49
74
 
50
75
  ### `new OpenKey(options)`
51
76
 
52
- Create a new OpenKey instance.
53
-
54
77
  ```typescript
55
78
  const openkey = new OpenKey({
56
- host: 'https://openkey.so', // OpenKey server URL
79
+ host: 'https://openkey.so', // OpenKey server URL
80
+ appName: 'My App', // Display name in auth UI
81
+ mode: 'iframe', // 'iframe' | 'popup' | 'redirect'
82
+ externalProvider: provider, // Optional: app-provided wallet for external key signing
57
83
  });
58
84
  ```
59
85
 
60
- **Options:**
61
- - `host` (string): The OpenKey server URL. Use `https://openkey.so` for production.
86
+ ### `openkey.connect(opts?)`
62
87
 
63
- ### `openkey.connect()`
64
-
65
- Opens the OpenKey popup for the user to authenticate and select a key.
88
+ Authenticate and select a key.
66
89
 
67
90
  ```typescript
68
- const { address, keyId } = await openkey.connect();
91
+ const { address, keyId, keyType } = await openkey.connect();
92
+ // keyType: 'MANAGED' (TEE-secured) or 'EXTERNAL' (linked wallet)
69
93
  ```
70
94
 
71
- **Returns:**
72
- - `address` (string): The Ethereum address of the selected key
73
- - `keyId` (string): The unique identifier for the key (used in signing operations)
74
-
75
- ### `openkey.signMessage(params)`
95
+ ### `openkey.signMessage(request, opts?)`
76
96
 
77
- Sign a message with the user's key. Opens a popup for user approval.
97
+ Sign a message. External keys route directly to the user's wallet.
78
98
 
79
99
  ```typescript
80
- const signature = await openkey.signMessage({
100
+ const { signature, address } = await openkey.signMessage({
81
101
  message: 'Hello, World!',
82
102
  keyId: 'key-id-from-connect',
83
103
  });
84
104
  ```
85
105
 
86
- **Parameters:**
87
- - `message` (string): The message to sign
88
- - `keyId` (string): The key ID from `connect()`
106
+ ### `openkey.signTypedData(request, opts?)`
89
107
 
90
- **Returns:**
91
- - `signature` (string): The hex-encoded signature
92
-
93
- ### `openkey.signTypedData(params)`
94
-
95
- Sign EIP-712 typed data with the user's key.
108
+ Sign EIP-712 typed data.
96
109
 
97
110
  ```typescript
98
- const signature = await openkey.signTypedData({
99
- domain: {
100
- name: 'My App',
101
- version: '1',
102
- chainId: 1,
103
- },
104
- types: {
105
- Message: [{ name: 'content', type: 'string' }],
106
- },
111
+ const { signature } = await openkey.signTypedData({
112
+ domain: { name: 'My App', version: '1', chainId: 1 },
113
+ types: { Message: [{ name: 'content', type: 'string' }] },
107
114
  primaryType: 'Message',
108
- message: {
109
- content: 'Hello!',
110
- },
115
+ message: { content: 'Hello!' },
111
116
  keyId: 'key-id-from-connect',
112
117
  });
113
118
  ```
114
119
 
115
- ## Integration with ethers.js
120
+ ### `openkey.linkWallet(opts?)`
116
121
 
117
- OpenKey can be used as an EIP-1193 provider with ethers.js:
122
+ Link an external wallet to the user's OpenKey account.
118
123
 
119
124
  ```typescript
120
- import { OpenKey } from '@openkey/sdk';
125
+ const { address, keyId } = await openkey.linkWallet();
126
+ ```
127
+
128
+ ### `OpenKeyEIP1193Provider`
129
+
130
+ A drop-in EIP-1193 provider that routes signing through OpenKey. Handles both managed and external keys transparently.
131
+
132
+ ```typescript
133
+ import { OpenKey, OpenKeyEIP1193Provider } from '@openkey/sdk';
134
+
135
+ const openkey = new OpenKey({ host: 'https://openkey.so' });
136
+ const authResult = await openkey.connect();
137
+
138
+ // Create provider that works with ethers.js, viem, etc.
139
+ const provider = new OpenKeyEIP1193Provider(openkey, authResult);
140
+ ```
141
+
142
+ ## Integration with ethers.js
143
+
144
+ ```typescript
145
+ import { OpenKey, OpenKeyEIP1193Provider } from '@openkey/sdk';
121
146
  import { BrowserProvider } from 'ethers';
122
147
 
123
- // Create OpenKey instance
124
148
  const openkey = new OpenKey({ host: 'https://openkey.so' });
149
+ const authResult = await openkey.connect();
150
+ const provider = new OpenKeyEIP1193Provider(openkey, authResult);
125
151
 
126
- // Connect and get the address
127
- const { address, keyId } = await openkey.connect();
128
-
129
- // Create an EIP-1193 compatible provider
130
- const eip1193Provider = {
131
- async request({ method, params }: { method: string; params?: any[] }) {
132
- switch (method) {
133
- case 'eth_accounts':
134
- case 'eth_requestAccounts':
135
- return [address];
136
- case 'eth_chainId':
137
- return '0x1'; // Mainnet
138
- case 'personal_sign': {
139
- const message = hexToString(params![0]);
140
- const result = await openkey.signMessage({ message, keyId });
141
- return result.signature;
142
- }
143
- default:
144
- throw new Error(`Unsupported method: ${method}`);
145
- }
146
- },
147
- };
148
-
149
- // Use with ethers
150
- const provider = new BrowserProvider(eip1193Provider);
151
- const signer = await provider.getSigner();
152
+ const ethersProvider = new BrowserProvider(provider);
153
+ const signer = await ethersProvider.getSigner();
152
154
  ```
153
155
 
154
156
  ## Integration with TinyCloud
155
157
 
156
- OpenKey works seamlessly with [TinyCloud](https://tinycloud.xyz) for decentralized storage:
157
-
158
158
  ```typescript
159
- import { OpenKey } from '@openkey/sdk';
159
+ import { OpenKey, OpenKeyEIP1193Provider } from '@openkey/sdk';
160
160
  import { TinyCloudWeb } from '@tinycloud/web-sdk';
161
- import { BrowserProvider } from 'ethers';
161
+ import { providers } from 'ethers';
162
162
 
163
- // Setup OpenKey
164
163
  const openkey = new OpenKey({ host: 'https://openkey.so' });
165
- const { address, keyId } = await openkey.connect();
164
+ const authResult = await openkey.connect();
165
+ const eip1193Provider = new OpenKeyEIP1193Provider(openkey, authResult);
166
+ const web3Provider = new providers.Web3Provider(eip1193Provider);
166
167
 
167
- // Create EIP-1193 provider (see above)
168
- const eip1193Provider = createOpenKeyProvider(openkey, address, keyId);
169
- const ethersProvider = new BrowserProvider(eip1193Provider);
170
-
171
- // Initialize TinyCloud with OpenKey as the signer
172
168
  const tcw = new TinyCloudWeb({
173
- providers: {
174
- web3: {
175
- driver: ethersProvider,
176
- },
177
- },
169
+ providers: { web3: { driver: web3Provider } },
178
170
  });
179
171
 
180
- // Sign in to TinyCloud (uses OpenKey for SIWE signature)
181
172
  await tcw.signIn();
182
-
183
- // Now you can use TinyCloud storage
184
173
  await tcw.kv.put('hello', 'world');
185
- const result = await tcw.kv.get('hello');
186
174
  ```
187
175
 
188
176
  ## Development
@@ -195,12 +183,6 @@ const openkey = new OpenKey({
195
183
  });
196
184
  ```
197
185
 
198
- You can also enable dev mode in your app to use local OpenKey:
199
-
200
- ```javascript
201
- localStorage.setItem('__DEV_MODE__', 'true');
202
- ```
203
-
204
186
  ## Browser Support
205
187
 
206
188
  OpenKey requires WebAuthn support:
@@ -209,17 +191,23 @@ OpenKey requires WebAuthn support:
209
191
  - Safari 13+
210
192
  - Edge 79+
211
193
 
194
+ Iframe mode requires cross-origin WebAuthn support:
195
+ - Chrome 84+
196
+ - Firefox 84+
197
+ - Safari 14.5+
198
+
212
199
  ## Security
213
200
 
214
201
  - Keys never leave the OpenKey TEE (Trusted Execution Environment)
215
202
  - All signing operations require explicit user approval via passkey
216
203
  - No seed phrases or private keys to manage or lose
204
+ - Iframe communicates via `postMessage` with origin validation
205
+ - Auto-fallback to popup ensures signing always works
217
206
 
218
207
  ## Links
219
208
 
220
209
  - [OpenKey Website](https://openkey.so)
221
- - [Documentation](https://docs.openkey.so)
222
- - [GitHub](https://github.com/AstroX11/openkey)
210
+ - [GitHub](https://github.com/TinyCloudLabs/openkey)
223
211
  - [TinyCloud Integration](https://tinycloud.xyz)
224
212
 
225
213
  ## License
package/dist/index.d.mts CHANGED
@@ -4,13 +4,14 @@ interface EIP1193Provider {
4
4
  params?: any[];
5
5
  }): Promise<any>;
6
6
  }
7
+ type OpenKeyMode = 'iframe' | 'popup' | 'redirect';
7
8
  interface OpenKeyConfig {
8
9
  /** OpenKey host URL (default: https://openkey.so) */
9
10
  host?: string;
10
11
  /** App identifier for display */
11
12
  appName?: string;
12
- /** Use popup instead of iframe (default: true) */
13
- usePopup?: boolean;
13
+ /** UI mode: 'iframe' (default), 'popup', or 'redirect' */
14
+ mode?: OpenKeyMode;
14
15
  /** App-provided wallet provider for external key signing */
15
16
  externalProvider?: EIP1193Provider;
16
17
  }
@@ -74,10 +75,9 @@ interface OAuthTokenResponse {
74
75
  declare class OpenKey {
75
76
  private host;
76
77
  private appName;
77
- private usePopup;
78
+ private mode;
78
79
  private config;
79
80
  private popup;
80
- private iframe;
81
81
  private lastAuth;
82
82
  private discoveredProviders;
83
83
  constructor(config?: OpenKeyConfig);
@@ -85,12 +85,16 @@ declare class OpenKey {
85
85
  * Connect to OpenKey and get user's wallet address
86
86
  * Opens auth flow for user to select/create a key
87
87
  */
88
- connect(): Promise<AuthResult>;
88
+ connect(opts?: {
89
+ mode?: OpenKeyMode;
90
+ }): Promise<AuthResult>;
89
91
  /**
90
92
  * Link an external wallet to the user's OpenKey account
91
93
  * Opens link-wallet widget flow
92
94
  */
93
- linkWallet(): Promise<{
95
+ linkWallet(opts?: {
96
+ mode?: OpenKeyMode;
97
+ }): Promise<{
94
98
  address: string;
95
99
  keyId: string;
96
100
  }>;
@@ -98,12 +102,16 @@ declare class OpenKey {
98
102
  * Sign a message with the user's OpenKey wallet
99
103
  * For external keys, routes directly to the user's wallet provider
100
104
  */
101
- signMessage(request: SignMessageRequest): Promise<SignResult>;
105
+ signMessage(request: SignMessageRequest, opts?: {
106
+ mode?: OpenKeyMode;
107
+ }): Promise<SignResult>;
102
108
  /**
103
109
  * Sign typed data (EIP-712) with the user's OpenKey wallet
104
110
  * For external keys, routes directly to the user's wallet provider
105
111
  */
106
- signTypedData(request: SignTypedDataRequest): Promise<SignResult>;
112
+ signTypedData(request: SignTypedDataRequest, opts?: {
113
+ mode?: OpenKeyMode;
114
+ }): Promise<SignResult>;
107
115
  /**
108
116
  * Find a wallet provider that controls the given address.
109
117
  * Checks: 1) app-provided externalProvider, 2) EIP-6963 discovered wallets, 3) window.ethereum
@@ -111,8 +119,8 @@ declare class OpenKey {
111
119
  findWalletProvider(targetAddress: string): Promise<EIP1193Provider>;
112
120
  private signWithExternalWallet;
113
121
  private signTypedDataWithExternalWallet;
114
- private signWithPopup;
115
- private signTypedDataWithPopup;
122
+ private signWithOpenKey;
123
+ private signTypedDataWithOpenKey;
116
124
  private toHex;
117
125
  /**
118
126
  * Check if user is already authenticated
@@ -159,9 +167,11 @@ declare class OpenKey {
159
167
  };
160
168
  private openOAuthFlow;
161
169
  private openOAuthPopup;
170
+ private resolveMode;
162
171
  private openFlow;
172
+ private openIframeModal;
173
+ private handleWalletLinkDelegation;
163
174
  private openPopup;
164
- private openIframe;
165
175
  /**
166
176
  * Disconnect and close any open flows
167
177
  */
@@ -187,4 +197,4 @@ declare class OpenKeyEIP1193Provider implements EIP1193Provider {
187
197
 
188
198
  declare function getOpenKey(config?: OpenKeyConfig): OpenKey;
189
199
 
190
- export { type AuthResult, type EIP1193Provider, type OAuthConfig, type OAuthResult, type OAuthTokenResponse, OpenKey, type OpenKeyConfig, OpenKeyEIP1193Provider, type OpenKeyError, type SignMessageRequest, type SignResult, type SignTypedDataRequest, OpenKey as default, getOpenKey };
200
+ export { type AuthResult, type EIP1193Provider, type OAuthConfig, type OAuthResult, type OAuthTokenResponse, OpenKey, type OpenKeyConfig, OpenKeyEIP1193Provider, type OpenKeyError, type OpenKeyMode, type SignMessageRequest, type SignResult, type SignTypedDataRequest, OpenKey as default, getOpenKey };
package/dist/index.d.ts CHANGED
@@ -4,13 +4,14 @@ interface EIP1193Provider {
4
4
  params?: any[];
5
5
  }): Promise<any>;
6
6
  }
7
+ type OpenKeyMode = 'iframe' | 'popup' | 'redirect';
7
8
  interface OpenKeyConfig {
8
9
  /** OpenKey host URL (default: https://openkey.so) */
9
10
  host?: string;
10
11
  /** App identifier for display */
11
12
  appName?: string;
12
- /** Use popup instead of iframe (default: true) */
13
- usePopup?: boolean;
13
+ /** UI mode: 'iframe' (default), 'popup', or 'redirect' */
14
+ mode?: OpenKeyMode;
14
15
  /** App-provided wallet provider for external key signing */
15
16
  externalProvider?: EIP1193Provider;
16
17
  }
@@ -74,10 +75,9 @@ interface OAuthTokenResponse {
74
75
  declare class OpenKey {
75
76
  private host;
76
77
  private appName;
77
- private usePopup;
78
+ private mode;
78
79
  private config;
79
80
  private popup;
80
- private iframe;
81
81
  private lastAuth;
82
82
  private discoveredProviders;
83
83
  constructor(config?: OpenKeyConfig);
@@ -85,12 +85,16 @@ declare class OpenKey {
85
85
  * Connect to OpenKey and get user's wallet address
86
86
  * Opens auth flow for user to select/create a key
87
87
  */
88
- connect(): Promise<AuthResult>;
88
+ connect(opts?: {
89
+ mode?: OpenKeyMode;
90
+ }): Promise<AuthResult>;
89
91
  /**
90
92
  * Link an external wallet to the user's OpenKey account
91
93
  * Opens link-wallet widget flow
92
94
  */
93
- linkWallet(): Promise<{
95
+ linkWallet(opts?: {
96
+ mode?: OpenKeyMode;
97
+ }): Promise<{
94
98
  address: string;
95
99
  keyId: string;
96
100
  }>;
@@ -98,12 +102,16 @@ declare class OpenKey {
98
102
  * Sign a message with the user's OpenKey wallet
99
103
  * For external keys, routes directly to the user's wallet provider
100
104
  */
101
- signMessage(request: SignMessageRequest): Promise<SignResult>;
105
+ signMessage(request: SignMessageRequest, opts?: {
106
+ mode?: OpenKeyMode;
107
+ }): Promise<SignResult>;
102
108
  /**
103
109
  * Sign typed data (EIP-712) with the user's OpenKey wallet
104
110
  * For external keys, routes directly to the user's wallet provider
105
111
  */
106
- signTypedData(request: SignTypedDataRequest): Promise<SignResult>;
112
+ signTypedData(request: SignTypedDataRequest, opts?: {
113
+ mode?: OpenKeyMode;
114
+ }): Promise<SignResult>;
107
115
  /**
108
116
  * Find a wallet provider that controls the given address.
109
117
  * Checks: 1) app-provided externalProvider, 2) EIP-6963 discovered wallets, 3) window.ethereum
@@ -111,8 +119,8 @@ declare class OpenKey {
111
119
  findWalletProvider(targetAddress: string): Promise<EIP1193Provider>;
112
120
  private signWithExternalWallet;
113
121
  private signTypedDataWithExternalWallet;
114
- private signWithPopup;
115
- private signTypedDataWithPopup;
122
+ private signWithOpenKey;
123
+ private signTypedDataWithOpenKey;
116
124
  private toHex;
117
125
  /**
118
126
  * Check if user is already authenticated
@@ -159,9 +167,11 @@ declare class OpenKey {
159
167
  };
160
168
  private openOAuthFlow;
161
169
  private openOAuthPopup;
170
+ private resolveMode;
162
171
  private openFlow;
172
+ private openIframeModal;
173
+ private handleWalletLinkDelegation;
163
174
  private openPopup;
164
- private openIframe;
165
175
  /**
166
176
  * Disconnect and close any open flows
167
177
  */
@@ -187,4 +197,4 @@ declare class OpenKeyEIP1193Provider implements EIP1193Provider {
187
197
 
188
198
  declare function getOpenKey(config?: OpenKeyConfig): OpenKey;
189
199
 
190
- export { type AuthResult, type EIP1193Provider, type OAuthConfig, type OAuthResult, type OAuthTokenResponse, OpenKey, type OpenKeyConfig, OpenKeyEIP1193Provider, type OpenKeyError, type SignMessageRequest, type SignResult, type SignTypedDataRequest, OpenKey as default, getOpenKey };
200
+ export { type AuthResult, type EIP1193Provider, type OAuthConfig, type OAuthResult, type OAuthTokenResponse, OpenKey, type OpenKeyConfig, OpenKeyEIP1193Provider, type OpenKeyError, type OpenKeyMode, type SignMessageRequest, type SignResult, type SignTypedDataRequest, OpenKey as default, getOpenKey };
package/dist/index.js CHANGED
@@ -1,21 +1,41 @@
1
- "use strict";var y=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var v=Object.getOwnPropertyNames;var I=Object.prototype.hasOwnProperty;var A=(o,e)=>{for(var t in e)y(o,t,{get:e[t],enumerable:!0})},O=(o,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of v(e))!I.call(o,r)&&r!==t&&y(o,r,{get:()=>e[r],enumerable:!(s=k(e,r))||s.enumerable});return o};var U=o=>O(y({},"__esModule",{value:!0}),o);var L={};A(L,{OpenKey:()=>g,OpenKeyEIP1193Provider:()=>w,default:()=>x,getOpenKey:()=>C});module.exports=U(L);var _="https://openkey.so";var h="openkey_oauth";function P(o){return btoa(String.fromCharCode(...o)).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}async function R(){let o=new Uint8Array(32);return crypto.getRandomValues(o),P(o)}async function S(o){let t=new TextEncoder().encode(o),s=await crypto.subtle.digest("SHA-256",t);return P(new Uint8Array(s))}function D(){let o=new Uint8Array(16);return crypto.getRandomValues(o),P(o)}var g=class{host;appName;usePopup;config;popup=null;iframe=null;lastAuth=null;discoveredProviders=[];constructor(e={}){this.config=e,this.host=e.host||_,this.appName=e.appName||window.location.hostname,this.usePopup=e.usePopup??!0,typeof window<"u"&&(this.discoveredProviders=[],window.addEventListener("eip6963:announceProvider",t=>{this.discoveredProviders.push(t.detail)}),window.dispatchEvent(new Event("eip6963:requestProvider")))}async connect(){let e=await this.openFlow("connect",{type:"openkey:auth:request",appName:this.appName});return this.lastAuth=e,e}async linkWallet(){return this.openFlow("link-wallet",{type:"openkey:link-wallet:request"})}async signMessage(e){return this.lastAuth?.keyType==="EXTERNAL"?this.signWithExternalWallet(e):this.signWithPopup(e)}async signTypedData(e){return this.lastAuth?.keyType==="EXTERNAL"?this.signTypedDataWithExternalWallet(e):this.signTypedDataWithPopup(e)}async findWalletProvider(e){if(this.config.externalProvider)return this.config.externalProvider;for(let{provider:t}of this.discoveredProviders)try{if((await t.request({method:"eth_requestAccounts"})).some(r=>r.toLowerCase()===e.toLowerCase()))return t}catch{}if(typeof window<"u"&&window.ethereum){let t=window.ethereum;try{if((await t.request({method:"eth_requestAccounts"})).some(r=>r.toLowerCase()===e.toLowerCase()))return t}catch{}return t}throw new Error("No wallet provider found for external key. Connect your wallet.")}async signWithExternalWallet(e){let t=await this.findWalletProvider(this.lastAuth.address),s=this.toHex(e.message);return{signature:await t.request({method:"personal_sign",params:[s,this.lastAuth.address]}),address:this.lastAuth.address}}async signTypedDataWithExternalWallet(e){return{signature:await(await this.findWalletProvider(this.lastAuth.address)).request({method:"eth_signTypedData_v4",params:[this.lastAuth.address,JSON.stringify(e)]}),address:this.lastAuth.address}}signWithPopup(e){return this.openFlow("sign",{type:"openkey:sign:request",message:e.message,keyId:e.keyId})}signTypedDataWithPopup(e){return this.openFlow("sign-typed-data",{type:"openkey:signTypedData:request",data:e})}toHex(e){return"0x"+Array.from(new TextEncoder().encode(e)).map(t=>t.toString(16).padStart(2,"0")).join("")}async isConnected(){try{let e=await fetch(`${this.host}/api/auth/session`,{credentials:"include"});return e.ok?!!(await e.json()).user:!1}catch{return!1}}oauth={connect:async e=>{let t=await R(),s=await S(t),r=e.state||D();sessionStorage.setItem(h,JSON.stringify({verifier:t,state:r}));let n=new URL(`${this.host}/api/auth/oauth2/authorize`);return n.searchParams.set("client_id",e.clientId),n.searchParams.set("redirect_uri",e.redirectUri),n.searchParams.set("response_type","code"),n.searchParams.set("scope","openid"),n.searchParams.set("state",r),n.searchParams.set("code_challenge",s),n.searchParams.set("code_challenge_method","S256"),this.openOAuthFlow(n.toString(),r,e.redirectUri)},exchangeCode:async(e,t)=>{let s=sessionStorage.getItem(h);if(!s)throw new Error("No PKCE verifier found. Start a new OAuth flow.");let{verifier:r}=JSON.parse(s),n=await fetch(`${this.host}/api/auth/oauth2/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",code:e,redirect_uri:t.redirectUri,client_id:t.clientId,code_verifier:r})});if(sessionStorage.removeItem(h),!n.ok){let c=await n.json();throw new Error(c.error_description||"Token exchange failed")}return n.json()},verifyState:e=>{let t=sessionStorage.getItem(h);if(!t)return!1;let{state:s}=JSON.parse(t);return s===e},parseCallback:e=>{let t=new URL(e||window.location.href),s=t.searchParams.get("error");if(s)return{error:s,errorDescription:t.searchParams.get("error_description")||void 0};let r=t.searchParams.get("code"),n=t.searchParams.get("state");return!r||!n?{error:"missing_params",errorDescription:"Missing code or state parameter"}:{code:r,state:n}}};openOAuthFlow(e,t,s){return new Promise((r,n)=>{this.usePopup?this.openOAuthPopup(e,t,s,r,n):window.location.href=e})}openOAuthPopup(e,t,s,r,n){let c=window.screenX+(window.outerWidth-400)/2,p=window.screenY+(window.outerHeight-600)/2;if(this.popup=window.open(e,"openkey-oauth",`width=400,height=600,left=${c},top=${p},popup=true`),!this.popup){n({code:"POPUP_BLOCKED",message:"Popup was blocked. Please allow popups or use redirect mode."});return}let l=new URL(s).origin,u=setInterval(()=>{try{if(this.popup?.closed){clearInterval(u),clearTimeout(d),n({code:"USER_CANCELLED",message:"User closed the popup"});return}let i=this.popup?.location?.href;if(i&&i.startsWith(l)){clearInterval(u),clearTimeout(d);let a=new URL(i),f=a.searchParams.get("code"),T=a.searchParams.get("state"),E=a.searchParams.get("error");if(this.popup?.close(),E){n({code:"UNAUTHORIZED",message:a.searchParams.get("error_description")||E});return}if(T!==t){n({code:"STATE_MISMATCH",message:"State mismatch - possible CSRF attack"});return}f?r({code:f,state:T}):n({code:"UNAUTHORIZED",message:"Authorization failed - no code returned"})}}catch{}},100),d=setTimeout(()=>{clearInterval(u),this.popup?.close(),n({code:"TIMEOUT",message:"OAuth flow timed out"})},3e5)}async openFlow(e,t){return new Promise((s,r)=>{let n=`${this.host}/widget/${e}?origin=${encodeURIComponent(window.location.origin)}`;this.usePopup?this.openPopup(n,t,s,r):this.openIframe(n,t,s,r)})}openPopup(e,t,s,r){let n=window.screenX+(window.outerWidth-400)/2,c=window.screenY+(window.outerHeight-600)/2;if(this.popup=window.open(e,"openkey",`width=400,height=600,left=${n},top=${c},popup=true`),!this.popup){r({code:"POPUP_BLOCKED",message:"Popup was blocked. Please allow popups for this site."});return}let p=()=>{window.removeEventListener("message",d),clearInterval(l),clearTimeout(u)},l=setInterval(()=>{this.popup?.closed&&(p(),r({code:"USER_CANCELLED",message:"User closed the popup"}))},500),u=setTimeout(()=>{p(),this.popup?.close(),r({code:"TIMEOUT",message:"Request timed out"})},3e5),d=i=>{if(i.origin!==this.host)return;let a=i.data;if(a.type==="openkey:ready"){this.popup?.postMessage(t,this.host);return}if(a.type==="openkey:close"){p(),this.popup?.close(),r({code:"USER_CANCELLED",message:"User cancelled the request"});return}(a.type==="openkey:auth:response"||a.type==="openkey:sign:response"||a.type==="openkey:signTypedData:response"||a.type==="openkey:link-wallet:response")&&(p(),this.popup?.close(),a.success?a.type==="openkey:auth:response"?s({address:a.address,keyId:a.keyId,keyType:a.keyType||"MANAGED"}):a.type==="openkey:link-wallet:response"?s({address:a.address,keyId:a.keyId}):s({signature:a.signature,address:a.address}):r(a.error))};window.addEventListener("message",d)}openIframe(e,t,s,r){let n=document.createElement("div");n.style.cssText=`
2
- position: fixed;
3
- inset: 0;
4
- background: rgba(0, 0, 0, 0.5);
5
- display: flex;
6
- align-items: center;
7
- justify-content: center;
8
- z-index: 999999;
9
- `;let c=document.createElement("div");c.style.cssText=`
10
- width: 400px;
11
- height: 600px;
12
- background: #1a1a1a;
13
- border-radius: 12px;
14
- overflow: hidden;
15
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
16
- `,this.iframe=document.createElement("iframe"),this.iframe.src=e,this.iframe.style.cssText=`
17
- width: 100%;
18
- height: 100%;
19
- border: none;
20
- `,c.appendChild(this.iframe),n.appendChild(c),document.body.appendChild(n);let p=()=>{window.removeEventListener("message",u),clearTimeout(l),n.remove(),this.iframe=null};n.addEventListener("click",d=>{d.target===n&&(p(),r({code:"USER_CANCELLED",message:"User cancelled the request"}))});let l=setTimeout(()=>{p(),r({code:"TIMEOUT",message:"Request timed out"})},3e5),u=d=>{if(d.origin!==this.host)return;let i=d.data;if(i.type==="openkey:ready"){this.iframe?.contentWindow?.postMessage(t,this.host);return}if(i.type==="openkey:close"){p(),r({code:"USER_CANCELLED",message:"User cancelled the request"});return}(i.type==="openkey:auth:response"||i.type==="openkey:sign:response"||i.type==="openkey:signTypedData:response"||i.type==="openkey:link-wallet:response")&&(p(),i.success?i.type==="openkey:auth:response"?s({address:i.address,keyId:i.keyId,keyType:i.keyType||"MANAGED"}):i.type==="openkey:link-wallet:response"?s({address:i.address,keyId:i.keyId}):s({signature:i.signature,address:i.address}):r(i.error))};window.addEventListener("message",u)}disconnect(){this.popup?.close(),this.popup=null,this.iframe?.parentElement?.parentElement?.remove(),this.iframe=null}},w=class{openkey;address;keyId;keyType;constructor(e,t){this.openkey=e,this.address=t.address,this.keyId=t.keyId,this.keyType=t.keyType}async request({method:e,params:t}){switch(e){case"eth_accounts":case"eth_requestAccounts":return[this.address];case"eth_chainId":return"0x1";case"personal_sign":{if(this.keyType==="EXTERNAL")return(await this.openkey.findWalletProvider(this.address)).request({method:e,params:t});let s=this.hexToString(t[0]);return(await this.openkey.signMessage({message:s,keyId:this.keyId})).signature}case"eth_signTypedData_v4":{if(this.keyType==="EXTERNAL")return(await this.openkey.findWalletProvider(this.address)).request({method:e,params:t});let s=JSON.parse(t[1]);return(await this.openkey.signTypedData(s)).signature}default:throw new Error(`Unsupported method: ${e}`)}}hexToString(e){let t=new Uint8Array((e.startsWith("0x")?e.slice(2):e).match(/.{1,2}/g).map(s=>parseInt(s,16)));return new TextDecoder().decode(t)}},x=g,m=null;function C(o){return(!m||o)&&(m=new g(o)),m}0&&(module.exports={OpenKey,OpenKeyEIP1193Provider,getOpenKey});
1
+ "use strict";var k=Object.defineProperty;var O=Object.getOwnPropertyDescriptor;var I=Object.getOwnPropertyNames;var A=Object.prototype.hasOwnProperty;var C=(i,e)=>{for(var t in e)k(i,t,{get:e[t],enumerable:!0})},M=(i,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of I(e))!A.call(i,r)&&r!==t&&k(i,r,{get:()=>e[r],enumerable:!(s=O(e,r))||s.enumerable});return i};var S=i=>M(k({},"__esModule",{value:!0}),i);var K={};C(K,{OpenKey:()=>f,OpenKeyEIP1193Provider:()=>P,default:()=>N,getOpenKey:()=>L});module.exports=S(K);var U="https://openkey.so";var w="openkey_oauth",v=class{root;shadow;iframe;onClose;onMessage;messageHandler;host;constructor(e){this.host=e.host,this.onClose=e.onClose,this.onMessage=e.onMessage;let t=window.matchMedia("(max-width: 639px)").matches;this.root=document.createElement("div"),this.shadow=this.root.attachShadow({mode:"closed"});let s=document.createElement("style");s.textContent=`
2
+ :host{all:initial}
3
+ .ok-backdrop{position:fixed;inset:0;background:rgba(0,0,0,0.4);backdrop-filter:blur(4px);z-index:999999;display:flex;align-items:${t?"flex-end":"center"};justify-content:center;animation:ok-fade-in 150ms ease-out}
4
+ .ok-card{position:relative;background:#fafafa;width:${t?"100%":"400px"};border-radius:${t?"16px 16px 0 0":"16px"};box-shadow:0 25px 50px -12px rgba(0,0,0,0.25);overflow:hidden;overflow-y:auto;animation:${t?"ok-slide-up 250ms ease-out":"ok-scale-in 200ms ease-out"};max-height:90vh}
5
+ .ok-close{position:absolute;top:8px;right:8px;width:24px;height:24px;border:none;background:transparent;color:#6b7280;font-size:18px;cursor:pointer;z-index:1;display:flex;align-items:center;justify-content:center;border-radius:4px}
6
+ .ok-close:hover{color:#111827}
7
+ .ok-handle{width:40px;height:4px;background:#d4d4d4;border-radius:2px;margin:8px auto 0}
8
+ iframe{border:none;width:100%;height:400px;display:block;transition:height 200ms ease}
9
+ .ok-backdrop.ok-exit{animation:ok-fade-out 150ms ease-in}
10
+ .ok-backdrop.ok-exit .ok-card{animation:${t?"ok-slide-down 200ms ease-in":"ok-scale-out 150ms ease-in"}}
11
+ @keyframes ok-fade-in{from{opacity:0}to{opacity:1}}
12
+ @keyframes ok-fade-out{from{opacity:1}to{opacity:0}}
13
+ @keyframes ok-scale-in{from{opacity:0;transform:scale(0.95)}to{opacity:1;transform:scale(1)}}
14
+ @keyframes ok-scale-out{from{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(0.95)}}
15
+ @keyframes ok-slide-up{from{transform:translateY(100%)}to{transform:translateY(0)}}
16
+ @keyframes ok-slide-down{from{transform:translateY(0)}to{transform:translateY(100%)}}
17
+ `;let r=document.createElement("div");r.className="ok-backdrop",r.addEventListener("click",n=>{n.target===r&&this.close()});let o=document.createElement("div");if(o.className="ok-card",t){let n=document.createElement("div");n.className="ok-handle",o.appendChild(n)}let d=document.createElement("button");d.className="ok-close",d.textContent="\xD7",d.addEventListener("click",()=>this.close()),o.appendChild(d),this.iframe=document.createElement("iframe"),this.iframe.src=e.url,this.iframe.setAttribute("allow","publickey-credentials-get *; publickey-credentials-create *"),this.iframe.setAttribute("sandbox","allow-scripts allow-same-origin allow-forms allow-popups"),o.appendChild(this.iframe),r.appendChild(o),this.shadow.appendChild(s),this.shadow.appendChild(r),this.messageHandler=n=>{if(n.origin!==this.host)return;let l=n.data;if(l.type==="openkey:resize"){let u=Math.min(l.height,Math.floor(window.innerHeight*.85));this.iframe.style.height=`${u}px`;return}this.onMessage(l)},window.addEventListener("message",this.messageHandler),document.body.appendChild(this.root)}postMessage(e){this.iframe.contentWindow?.postMessage(e,this.host)}destroy(){window.removeEventListener("message",this.messageHandler),this.root.remove()}close(){let e=this.shadow.querySelector(".ok-backdrop");e?(e.classList.add("ok-exit"),setTimeout(()=>{this.destroy(),this.onClose()},200)):(this.destroy(),this.onClose())}},T=class{root;shadow;constructor(e){this.root=document.createElement("div"),this.shadow=this.root.attachShadow({mode:"closed"});let t=document.createElement("style");t.textContent=`
18
+ :host{all:initial}
19
+ .wp-backdrop{position:fixed;inset:0;background:rgba(0,0,0,0.4);backdrop-filter:blur(4px);z-index:1000000;display:flex;align-items:center;justify-content:center;animation:wp-fade 150ms ease-out}
20
+ .wp-card{background:#fff;width:340px;border-radius:16px;box-shadow:0 25px 50px -12px rgba(0,0,0,0.25);padding:20px;animation:wp-scale 200ms ease-out}
21
+ .wp-title{font:600 16px/1.3 system-ui,sans-serif;color:#111;margin:0 0 12px}
22
+ .wp-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}
23
+ .wp-item{display:flex;align-items:center;gap:10px;padding:10px 12px;border:1px solid #e5e7eb;border-radius:10px;background:transparent;cursor:pointer;font:14px/1.3 system-ui,sans-serif;color:#111;width:100%;text-align:left;transition:border-color 150ms}
24
+ .wp-item:hover{border-color:#6366f1}
25
+ .wp-icon{width:28px;height:28px;border-radius:6px}
26
+ .wp-empty{font:14px/1.4 system-ui,sans-serif;color:#6b7280;text-align:center;padding:16px 0}
27
+ .wp-cancel{display:block;width:100%;margin-top:12px;padding:8px;border:none;background:transparent;color:#6b7280;font:14px/1.3 system-ui,sans-serif;cursor:pointer;border-radius:8px}
28
+ .wp-cancel:hover{background:#f3f4f6;color:#111}
29
+ @keyframes wp-fade{from{opacity:0}to{opacity:1}}
30
+ @keyframes wp-scale{from{opacity:0;transform:scale(0.95)}to{opacity:1;transform:scale(1)}}
31
+ @media(prefers-color-scheme:dark){
32
+ .wp-card{background:#1a1a1a;box-shadow:0 25px 50px -12px rgba(0,0,0,0.5)}
33
+ .wp-title{color:#f3f4f6}
34
+ .wp-item{border-color:#374151;color:#f3f4f6}
35
+ .wp-item:hover{border-color:#818cf8}
36
+ .wp-empty{color:#9ca3af}
37
+ .wp-cancel{color:#9ca3af}
38
+ .wp-cancel:hover{background:#262626;color:#f3f4f6}
39
+ }
40
+ `;let s=document.createElement("div");s.className="wp-backdrop",s.addEventListener("click",n=>{n.target===s&&(this.destroy(),e.onCancel())});let r=document.createElement("div");r.className="wp-card";let o=document.createElement("h2");if(o.className="wp-title",o.textContent="Select a wallet",r.appendChild(o),e.providers.length===0){let n=document.createElement("div");n.className="wp-empty",n.textContent="No wallets detected",r.appendChild(n)}else{let n=document.createElement("ul");n.className="wp-list";for(let{info:l,provider:u}of e.providers){let c=document.createElement("button");if(c.className="wp-item",l.icon){let a=document.createElement("img");a.className="wp-icon",a.src=l.icon,a.alt=l.name,c.appendChild(a)}let h=document.createElement("span");h.textContent=l.name,c.appendChild(h),c.addEventListener("click",()=>{this.destroy(),e.onSelect(u)}),n.appendChild(c)}r.appendChild(n)}let d=document.createElement("button");d.className="wp-cancel",d.textContent="Cancel",d.addEventListener("click",()=>{this.destroy(),e.onCancel()}),r.appendChild(d),s.appendChild(r),this.shadow.appendChild(t),this.shadow.appendChild(s),document.body.appendChild(this.root)}destroy(){this.root.remove()}};function g(i="Opening in new window\u2026",e="info"){let t=e==="error"?"#dc2626":"#1f2937",s=document.createElement("div"),r=s.attachShadow({mode:"closed"});r.innerHTML=`<style>:host{all:initial}.ok-toast{position:fixed;top:16px;right:16px;z-index:1000000;background:${t};color:#f9fafb;padding:10px 16px;border-radius:8px;font:14px/1.4 system-ui,sans-serif;box-shadow:0 4px 12px rgba(0,0,0,0.15);animation:ok-t-in 200ms ease-out;max-width:360px}@keyframes ok-t-in{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}</style><div class="ok-toast">${i}</div>`,document.body.appendChild(s),setTimeout(()=>s.remove(),4e3)}function b(i){return btoa(String.fromCharCode(...i)).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}async function _(){let i=new Uint8Array(32);return crypto.getRandomValues(i),b(i)}async function R(i){let t=new TextEncoder().encode(i),s=await crypto.subtle.digest("SHA-256",t);return b(new Uint8Array(s))}function D(){let i=new Uint8Array(16);return crypto.getRandomValues(i),b(i)}var x=class extends Error{constructor(e){super(e),this.name="ExternalWalletError"}},f=class{host;appName;mode;config;popup=null;lastAuth=null;discoveredProviders=[];constructor(e={}){this.config=e,this.host=e.host||U,this.appName=e.appName||window.location.hostname,this.mode=e.mode??"iframe",typeof window<"u"&&(this.discoveredProviders=[],window.addEventListener("eip6963:announceProvider",t=>{this.discoveredProviders.push(t.detail)}),window.dispatchEvent(new Event("eip6963:requestProvider")))}async connect(e){let t=await this.openFlow("connect",{type:"openkey:auth:request",appName:this.appName},e?.mode);return this.lastAuth=t,t}async linkWallet(e){return this.openFlow("link-wallet",{type:"openkey:link-wallet:request"},e?.mode)}async signMessage(e,t){return this.lastAuth?.keyType==="EXTERNAL"?this.signWithExternalWallet(e):this.signWithOpenKey(e,t?.mode)}async signTypedData(e,t){return this.lastAuth?.keyType==="EXTERNAL"?this.signTypedDataWithExternalWallet(e):this.signTypedDataWithOpenKey(e,t?.mode)}async findWalletProvider(e){if(this.config.externalProvider)return this.config.externalProvider;for(let{provider:t}of this.discoveredProviders)try{if((await t.request({method:"eth_accounts"})).some(r=>r.toLowerCase()===e.toLowerCase()))return t}catch{}if(typeof window<"u"&&window.ethereum){let t=window.ethereum;try{if((await t.request({method:"eth_accounts"})).some(r=>r.toLowerCase()===e.toLowerCase()))return t}catch{}}throw new x("External wallet not found. Connect the wallet that owns this key and try again.")}async signWithExternalWallet(e){let t;try{t=await this.findWalletProvider(this.lastAuth.address)}catch(s){throw g(s.message,"error"),s}try{let s=this.toHex(e.message);return{signature:await t.request({method:"personal_sign",params:[s,this.lastAuth.address]}),address:this.lastAuth.address}}catch(s){let r=s?.message?.includes("not been authorized")?"Wallet rejected the request. Approve the connection in your wallet and try again.":s?.message||"Signing failed";throw g(r,"error"),new Error(r)}}async signTypedDataWithExternalWallet(e){let t;try{t=await this.findWalletProvider(this.lastAuth.address)}catch(s){throw g(s.message,"error"),s}try{return{signature:await t.request({method:"eth_signTypedData_v4",params:[this.lastAuth.address,JSON.stringify(e)]}),address:this.lastAuth.address}}catch(s){let r=s?.message?.includes("not been authorized")?"Wallet rejected the request. Approve the connection in your wallet and try again.":s?.message||"Signing failed";throw g(r,"error"),new Error(r)}}signWithOpenKey(e,t){return this.openFlow("sign",{type:"openkey:sign:request",message:e.message,keyId:e.keyId},t)}signTypedDataWithOpenKey(e,t){return this.openFlow("sign-typed-data",{type:"openkey:signTypedData:request",data:e},t)}toHex(e){return"0x"+Array.from(new TextEncoder().encode(e)).map(t=>t.toString(16).padStart(2,"0")).join("")}async isConnected(){try{let e=await fetch(`${this.host}/api/auth/session`,{credentials:"include"});return e.ok?!!(await e.json()).user:!1}catch{return!1}}oauth={connect:async e=>{let t=await _(),s=await R(t),r=e.state||D();sessionStorage.setItem(w,JSON.stringify({verifier:t,state:r}));let o=new URL(`${this.host}/api/auth/oauth2/authorize`);return o.searchParams.set("client_id",e.clientId),o.searchParams.set("redirect_uri",e.redirectUri),o.searchParams.set("response_type","code"),o.searchParams.set("scope","openid"),o.searchParams.set("state",r),o.searchParams.set("code_challenge",s),o.searchParams.set("code_challenge_method","S256"),this.openOAuthFlow(o.toString(),r,e.redirectUri)},exchangeCode:async(e,t)=>{let s=sessionStorage.getItem(w);if(!s)throw new Error("No PKCE verifier found. Start a new OAuth flow.");let{verifier:r}=JSON.parse(s),o=await fetch(`${this.host}/api/auth/oauth2/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",code:e,redirect_uri:t.redirectUri,client_id:t.clientId,code_verifier:r})});if(sessionStorage.removeItem(w),!o.ok){let d=await o.json();throw new Error(d.error_description||"Token exchange failed")}return o.json()},verifyState:e=>{let t=sessionStorage.getItem(w);if(!t)return!1;let{state:s}=JSON.parse(t);return s===e},parseCallback:e=>{let t=new URL(e||window.location.href),s=t.searchParams.get("error");if(s)return{error:s,errorDescription:t.searchParams.get("error_description")||void 0};let r=t.searchParams.get("code"),o=t.searchParams.get("state");return!r||!o?{error:"missing_params",errorDescription:"Missing code or state parameter"}:{code:r,state:o}}};openOAuthFlow(e,t,s){return new Promise((r,o)=>{this.mode!=="redirect"?this.openOAuthPopup(e,t,s,r,o):window.location.href=e})}openOAuthPopup(e,t,s,r,o){let d=window.screenX+(window.outerWidth-400)/2,n=window.screenY+(window.outerHeight-600)/2;if(this.popup=window.open(e,"openkey-oauth",`width=400,height=600,left=${d},top=${n},popup=true`),!this.popup){o({code:"POPUP_BLOCKED",message:"Popup was blocked. Please allow popups or use redirect mode."});return}let l=new URL(s).origin,u=setInterval(()=>{try{if(this.popup?.closed){clearInterval(u),clearTimeout(c),o({code:"USER_CANCELLED",message:"User closed the popup"});return}let h=this.popup?.location?.href;if(h&&h.startsWith(l)){clearInterval(u),clearTimeout(c);let a=new URL(h),m=a.searchParams.get("code"),p=a.searchParams.get("state"),y=a.searchParams.get("error");if(this.popup?.close(),y){o({code:"UNAUTHORIZED",message:a.searchParams.get("error_description")||y});return}if(p!==t){o({code:"STATE_MISMATCH",message:"State mismatch - possible CSRF attack"});return}m?r({code:m,state:p}):o({code:"UNAUTHORIZED",message:"Authorization failed - no code returned"})}}catch{}},100),c=setTimeout(()=>{clearInterval(u),this.popup?.close(),o({code:"TIMEOUT",message:"OAuth flow timed out"})},3e5)}resolveMode(e){return e??this.mode}async openFlow(e,t,s){let r=this.resolveMode(s),o=encodeURIComponent(window.location.origin);if(r==="popup"){let n=`${this.host}/widget/${e}?origin=${o}`;return new Promise((l,u)=>this.openPopup(n,t,l,u))}if(r==="redirect"){let n=`${this.host}/widget/${e}?origin=${o}`;return window.location.href=n,new Promise(()=>{})}let d=`${this.host}/widget/embed/${e}?origin=${o}`;return this.openIframeModal(d,e,t,o)}openIframeModal(e,t,s,r){return new Promise((o,d)=>{let n=!1,l=!1,u=null,c=p=>{l||(l=!0,p())},h=()=>{clearTimeout(a),clearTimeout(m),u?.destroy(),u=null};u=new v({url:e,host:this.host,onClose:()=>{c(()=>{clearTimeout(a),clearTimeout(m),u=null,d({code:"USER_CANCELLED",message:"User cancelled the request"})})},onMessage:p=>{if(p.type==="openkey:ready"){n=!0,u?.postMessage(s);return}if(p.type==="openkey:close"){c(()=>{h(),d({code:"USER_CANCELLED",message:"User cancelled the request"})});return}if(p.type==="openkey:link-wallet:delegate"){this.handleWalletLinkDelegation(u);return}(p.type==="openkey:auth:response"||p.type==="openkey:sign:response"||p.type==="openkey:signTypedData:response"||p.type==="openkey:link-wallet:response")&&c(()=>{h(),p.success?p.type==="openkey:auth:response"?o({address:p.address,keyId:p.keyId,keyType:p.keyType||"MANAGED"}):p.type==="openkey:link-wallet:response"?o({address:p.address,keyId:p.keyId}):o({signature:p.signature,address:p.address}):d(p.error)})}});let a=setTimeout(()=>{if(n||l)return;h(),console.warn("OpenKey: iframe blocked by CSP, falling back to popup. Add frame-src https://openkey.so to your CSP."),g();let p=`${this.host}/widget/${t}?origin=${r}`;this.openPopup(p,s,y=>c(()=>o(y)),y=>c(()=>d(y)))},3e3),m=setTimeout(()=>{c(()=>{h(),d({code:"TIMEOUT",message:"Request timed out"})})},3e5)})}handleWalletLinkDelegation(e){let t=s=>e.postMessage(s);new T({providers:this.discoveredProviders,onCancel:()=>{t({type:"openkey:link-wallet:result",success:!1,error:{code:"USER_CANCELLED",message:"Wallet linking cancelled"}})},onSelect:async s=>{try{let o=(await s.request({method:"eth_requestAccounts"}))[0];if(!o)throw new Error("No account returned");let d=await fetch(`${this.host}/api/keys/link/challenge`,{method:"POST",credentials:"include"});if(!d.ok)throw new Error("Failed to get challenge");let{message:n}=await d.json(),l=this.toHex(n),u=await s.request({method:"personal_sign",params:[l,o]}),c=await fetch(`${this.host}/api/keys/link`,{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:JSON.stringify({address:o,signature:u,message:n})});if(!c.ok){let a=await c.json();throw new Error(a.error||"Failed to link wallet")}let{key:h}=await c.json();t({type:"openkey:link-wallet:result",success:!0,address:h.address,keyId:h.id})}catch(r){t({type:"openkey:link-wallet:result",success:!1,error:{code:"UNKNOWN",message:r.message||"Wallet linking failed"}})}}})}openPopup(e,t,s,r){let o=window.screenX+(window.outerWidth-400)/2,d=window.screenY+(window.outerHeight-600)/2;if(this.popup=window.open(e,"openkey",`width=400,height=600,left=${o},top=${d},popup=true`),!this.popup){r({code:"POPUP_BLOCKED",message:"Popup was blocked. Please allow popups for this site."});return}let n=()=>{window.removeEventListener("message",c),clearInterval(l),clearTimeout(u)},l=setInterval(()=>{this.popup?.closed&&(n(),r({code:"USER_CANCELLED",message:"User closed the popup"}))},500),u=setTimeout(()=>{n(),this.popup?.close(),r({code:"TIMEOUT",message:"Request timed out"})},3e5),c=h=>{if(h.origin!==this.host)return;let a=h.data;if(a.type==="openkey:ready"){this.popup?.postMessage(t,this.host);return}if(a.type==="openkey:close"){n(),this.popup?.close(),r({code:"USER_CANCELLED",message:"User cancelled the request"});return}(a.type==="openkey:auth:response"||a.type==="openkey:sign:response"||a.type==="openkey:signTypedData:response"||a.type==="openkey:link-wallet:response")&&(n(),this.popup?.close(),a.success?a.type==="openkey:auth:response"?s({address:a.address,keyId:a.keyId,keyType:a.keyType||"MANAGED"}):a.type==="openkey:link-wallet:response"?s({address:a.address,keyId:a.keyId}):s({signature:a.signature,address:a.address}):r(a.error))};window.addEventListener("message",c)}disconnect(){this.popup?.close(),this.popup=null}},P=class{openkey;address;keyId;keyType;constructor(e,t){this.openkey=e,this.address=t.address,this.keyId=t.keyId,this.keyType=t.keyType}async request({method:e,params:t}){switch(e){case"eth_accounts":case"eth_requestAccounts":return[this.address];case"eth_chainId":return"0x1";case"personal_sign":{if(this.keyType==="EXTERNAL")try{return await(await this.openkey.findWalletProvider(this.address)).request({method:e,params:t})}catch(o){throw g(o?.message||"External wallet signing failed","error"),o}let s=this.hexToString(t[0]);return(await this.openkey.signMessage({message:s,keyId:this.keyId})).signature}case"eth_signTypedData_v4":{if(this.keyType==="EXTERNAL")try{return await(await this.openkey.findWalletProvider(this.address)).request({method:e,params:t})}catch(o){throw g(o?.message||"External wallet signing failed","error"),o}let s=JSON.parse(t[1]);return(await this.openkey.signTypedData(s)).signature}default:throw new Error(`Unsupported method: ${e}`)}}hexToString(e){let t=new Uint8Array((e.startsWith("0x")?e.slice(2):e).match(/.{1,2}/g).map(s=>parseInt(s,16)));return new TextDecoder().decode(t)}},N=f,E=null;function L(i){return(!E||i)&&(E=new f(i)),E}0&&(module.exports={OpenKey,OpenKeyEIP1193Provider,getOpenKey});
21
41
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// OpenKey SDK - Browser client for third-party apps\n// Provides \"Sign with OpenKey\" functionality via popup or iframe\n\nexport interface EIP1193Provider {\n request(args: { method: string; params?: any[] }): Promise<any>;\n}\n\ninterface EIP6963ProviderInfo {\n uuid: string;\n name: string;\n icon: string;\n rdns: string;\n}\n\ninterface EIP6963ProviderDetail {\n info: EIP6963ProviderInfo;\n provider: EIP1193Provider;\n}\n\nexport interface OpenKeyConfig {\n /** OpenKey host URL (default: https://openkey.so) */\n host?: string;\n /** App identifier for display */\n appName?: string;\n /** Use popup instead of iframe (default: true) */\n usePopup?: boolean;\n /** App-provided wallet provider for external key signing */\n externalProvider?: EIP1193Provider;\n}\n\nexport interface SignMessageRequest {\n message: string;\n keyId?: string; // If not provided, user selects\n}\n\nexport interface SignTypedDataRequest {\n domain: {\n name?: string;\n version?: string;\n chainId?: number;\n verifyingContract?: string;\n };\n types: Record<string, Array<{ name: string; type: string }>>;\n primaryType: string;\n message: Record<string, unknown>;\n keyId?: string;\n}\n\nexport interface SignResult {\n signature: string;\n address: string;\n}\n\nexport interface AuthResult {\n address: string;\n keyId: string;\n keyType: 'MANAGED' | 'EXTERNAL';\n}\n\nexport interface OpenKeyError {\n code: 'USER_CANCELLED' | 'POPUP_BLOCKED' | 'TIMEOUT' | 'NO_KEY' | 'UNAUTHORIZED' | 'UNKNOWN' | 'STATE_MISMATCH';\n message: string;\n}\n\n// ======= OAuth 2.1 Types =======\n\n/** OAuth configuration for third-party apps */\nexport interface OAuthConfig {\n /** OAuth client_id (registered with OpenKey) */\n clientId: string;\n /** Redirect URI (must match registered URI) */\n redirectUri: string;\n /** State parameter for CSRF protection (auto-generated if not provided) */\n state?: string;\n}\n\n/** Result from OAuth authorization */\nexport interface OAuthResult {\n /** Authorization code (exchange for tokens) */\n code: string;\n /** State parameter (verify matches request) */\n state: string;\n}\n\n/** Response from token exchange */\nexport interface OAuthTokenResponse {\n access_token: string;\n token_type: 'Bearer';\n expires_in: number;\n id_token: string;\n scope: string;\n refresh_token?: string;\n}\n\ntype MessageType =\n | { type: 'openkey:auth:request'; appName: string }\n | { type: 'openkey:auth:response'; success: true; address: string; keyId: string; keyType?: 'MANAGED' | 'EXTERNAL' }\n | { type: 'openkey:auth:response'; success: false; error: OpenKeyError }\n | { type: 'openkey:sign:request'; message: string; keyId?: string }\n | { type: 'openkey:sign:response'; success: true; signature: string; address: string }\n | { type: 'openkey:sign:response'; success: false; error: OpenKeyError }\n | { type: 'openkey:signTypedData:request'; data: SignTypedDataRequest }\n | { type: 'openkey:signTypedData:response'; success: true; signature: string; address: string }\n | { type: 'openkey:signTypedData:response'; success: false; error: OpenKeyError }\n | { type: 'openkey:link-wallet:request' }\n | { type: 'openkey:link-wallet:response'; success: true; address: string; keyId: string }\n | { type: 'openkey:link-wallet:response'; success: false; error: OpenKeyError }\n | { type: 'openkey:ready' }\n | { type: 'openkey:close' };\n\nconst DEFAULT_HOST = 'https://openkey.so';\nconst POPUP_WIDTH = 400;\nconst POPUP_HEIGHT = 600;\nconst DEFAULT_TIMEOUT = 5 * 60 * 1000; // 5 minutes\nconst OAUTH_STORAGE_KEY = 'openkey_oauth';\n\n// ======= PKCE Utilities =======\n\nfunction base64UrlEncode(buffer: Uint8Array): string {\n const base64 = btoa(String.fromCharCode(...buffer));\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=/g, '');\n}\n\nasync function generateCodeVerifier(): Promise<string> {\n const array = new Uint8Array(32);\n crypto.getRandomValues(array);\n return base64UrlEncode(array);\n}\n\nasync function generateCodeChallenge(verifier: string): Promise<string> {\n const encoder = new TextEncoder();\n const data = encoder.encode(verifier);\n const digest = await crypto.subtle.digest('SHA-256', data);\n return base64UrlEncode(new Uint8Array(digest));\n}\n\nfunction generateState(): string {\n const array = new Uint8Array(16);\n crypto.getRandomValues(array);\n return base64UrlEncode(array);\n}\n\nexport class OpenKey {\n private host: string;\n private appName: string;\n private usePopup: boolean;\n private config: OpenKeyConfig;\n private popup: Window | null = null;\n private iframe: HTMLIFrameElement | null = null;\n private lastAuth: AuthResult | null = null;\n private discoveredProviders: EIP6963ProviderDetail[] = [];\n\n constructor(config: OpenKeyConfig = {}) {\n this.config = config;\n this.host = config.host || DEFAULT_HOST;\n this.appName = config.appName || window.location.hostname;\n this.usePopup = config.usePopup ?? true;\n\n // Listen for EIP-6963 wallet announcements\n if (typeof window !== 'undefined') {\n this.discoveredProviders = [];\n window.addEventListener('eip6963:announceProvider', (event: any) => {\n this.discoveredProviders.push(event.detail);\n });\n window.dispatchEvent(new Event('eip6963:requestProvider'));\n }\n }\n\n /**\n * Connect to OpenKey and get user's wallet address\n * Opens auth flow for user to select/create a key\n */\n async connect(): Promise<AuthResult> {\n const result = await this.openFlow<AuthResult>('connect', {\n type: 'openkey:auth:request',\n appName: this.appName,\n });\n this.lastAuth = result;\n return result;\n }\n\n /**\n * Link an external wallet to the user's OpenKey account\n * Opens link-wallet widget flow\n */\n async linkWallet(): Promise<{ address: string; keyId: string }> {\n return this.openFlow<{ address: string; keyId: string }>('link-wallet', {\n type: 'openkey:link-wallet:request',\n });\n }\n\n /**\n * Sign a message with the user's OpenKey wallet\n * For external keys, routes directly to the user's wallet provider\n */\n async signMessage(request: SignMessageRequest): Promise<SignResult> {\n if (this.lastAuth?.keyType === 'EXTERNAL') {\n return this.signWithExternalWallet(request);\n }\n return this.signWithPopup(request);\n }\n\n /**\n * Sign typed data (EIP-712) with the user's OpenKey wallet\n * For external keys, routes directly to the user's wallet provider\n */\n async signTypedData(request: SignTypedDataRequest): Promise<SignResult> {\n if (this.lastAuth?.keyType === 'EXTERNAL') {\n return this.signTypedDataWithExternalWallet(request);\n }\n return this.signTypedDataWithPopup(request);\n }\n\n /**\n * Find a wallet provider that controls the given address.\n * Checks: 1) app-provided externalProvider, 2) EIP-6963 discovered wallets, 3) window.ethereum\n */\n async findWalletProvider(targetAddress: string): Promise<EIP1193Provider> {\n // 1. Try app-provided externalProvider first\n if (this.config.externalProvider) {\n return this.config.externalProvider;\n }\n\n // 2. Use EIP-6963 to discover wallets, requesting authorization\n for (const { provider } of this.discoveredProviders) {\n try {\n const accounts = await provider.request({ method: 'eth_requestAccounts' }) as string[];\n if (accounts.some(a => a.toLowerCase() === targetAddress.toLowerCase())) {\n return provider;\n }\n } catch {}\n }\n\n // 3. Fall back to window.ethereum, requesting authorization\n if (typeof window !== 'undefined' && (window as any).ethereum) {\n const provider = (window as any).ethereum as EIP1193Provider;\n try {\n const accounts = await provider.request({ method: 'eth_requestAccounts' }) as string[];\n if (accounts.some(a => a.toLowerCase() === targetAddress.toLowerCase())) {\n return provider;\n }\n } catch {}\n return provider;\n }\n\n throw new Error('No wallet provider found for external key. Connect your wallet.');\n }\n\n private async signWithExternalWallet(request: SignMessageRequest): Promise<SignResult> {\n const provider = await this.findWalletProvider(this.lastAuth!.address);\n const hexMessage = this.toHex(request.message);\n const signature = await provider.request({\n method: 'personal_sign',\n params: [hexMessage, this.lastAuth!.address],\n });\n return { signature: signature as string, address: this.lastAuth!.address };\n }\n\n private async signTypedDataWithExternalWallet(request: SignTypedDataRequest): Promise<SignResult> {\n const provider = await this.findWalletProvider(this.lastAuth!.address);\n const signature = await provider.request({\n method: 'eth_signTypedData_v4',\n params: [this.lastAuth!.address, JSON.stringify(request)],\n });\n return { signature: signature as string, address: this.lastAuth!.address };\n }\n\n private signWithPopup(request: SignMessageRequest): Promise<SignResult> {\n return this.openFlow<SignResult>('sign', {\n type: 'openkey:sign:request',\n message: request.message,\n keyId: request.keyId,\n });\n }\n\n private signTypedDataWithPopup(request: SignTypedDataRequest): Promise<SignResult> {\n return this.openFlow<SignResult>('sign-typed-data', {\n type: 'openkey:signTypedData:request',\n data: request,\n });\n }\n\n private toHex(str: string): string {\n return '0x' + Array.from(new TextEncoder().encode(str)).map(b => b.toString(16).padStart(2, '0')).join('');\n }\n\n /**\n * Check if user is already authenticated\n */\n async isConnected(): Promise<boolean> {\n // Check for existing session via API\n try {\n const res = await fetch(`${this.host}/api/auth/session`, {\n credentials: 'include',\n });\n if (!res.ok) return false;\n const data = await res.json();\n return !!data.user;\n } catch {\n return false;\n }\n }\n\n // ======= OAuth 2.1 Provider Methods =======\n\n /**\n * OAuth 2.1 methods for third-party app authentication\n * Use this when your app has registered OAuth client credentials with OpenKey\n */\n oauth = {\n /**\n * Start OAuth authorization flow\n * Opens popup/redirect to OpenKey authorization endpoint\n * @returns Promise that resolves with authorization code after user consent\n */\n connect: async (config: OAuthConfig): Promise<OAuthResult> => {\n // Generate PKCE values\n const verifier = await generateCodeVerifier();\n const challenge = await generateCodeChallenge(verifier);\n const state = config.state || generateState();\n\n // Store for later token exchange\n sessionStorage.setItem(\n OAUTH_STORAGE_KEY,\n JSON.stringify({ verifier, state })\n );\n\n // Build authorization URL\n const authUrl = new URL(`${this.host}/api/auth/oauth2/authorize`);\n authUrl.searchParams.set('client_id', config.clientId);\n authUrl.searchParams.set('redirect_uri', config.redirectUri);\n authUrl.searchParams.set('response_type', 'code');\n authUrl.searchParams.set('scope', 'openid');\n authUrl.searchParams.set('state', state);\n authUrl.searchParams.set('code_challenge', challenge);\n authUrl.searchParams.set('code_challenge_method', 'S256');\n\n return this.openOAuthFlow(authUrl.toString(), state, config.redirectUri);\n },\n\n /**\n * Exchange authorization code for tokens\n * Call this after receiving the callback with authorization code\n * @param code - Authorization code from callback URL\n * @param config - Same config used for connect()\n * @returns Promise that resolves with access_token, id_token, etc.\n */\n exchangeCode: async (\n code: string,\n config: OAuthConfig\n ): Promise<OAuthTokenResponse> => {\n const stored = sessionStorage.getItem(OAUTH_STORAGE_KEY);\n if (!stored) {\n throw new Error('No PKCE verifier found. Start a new OAuth flow.');\n }\n\n const { verifier } = JSON.parse(stored);\n\n const response = await fetch(`${this.host}/api/auth/oauth2/token`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n code,\n redirect_uri: config.redirectUri,\n client_id: config.clientId,\n code_verifier: verifier,\n }),\n });\n\n // Clear stored verifier\n sessionStorage.removeItem(OAUTH_STORAGE_KEY);\n\n if (!response.ok) {\n const error = await response.json();\n throw new Error(error.error_description || 'Token exchange failed');\n }\n\n return response.json();\n },\n\n /**\n * Verify state parameter from callback\n * Call this before exchangeCode to prevent CSRF attacks\n * @param receivedState - State parameter from callback URL\n * @returns true if state matches, false otherwise\n */\n verifyState: (receivedState: string): boolean => {\n const stored = sessionStorage.getItem(OAUTH_STORAGE_KEY);\n if (!stored) return false;\n const { state } = JSON.parse(stored);\n return state === receivedState;\n },\n\n /**\n * Parse authorization response from callback URL\n * @param url - Callback URL (defaults to current window.location.href)\n * @returns Parsed code and state, or error\n */\n parseCallback: (\n url?: string\n ): { code: string; state: string } | { error: string; errorDescription?: string } => {\n const urlObj = new URL(url || window.location.href);\n const error = urlObj.searchParams.get('error');\n\n if (error) {\n return {\n error,\n errorDescription: urlObj.searchParams.get('error_description') || undefined,\n };\n }\n\n const code = urlObj.searchParams.get('code');\n const state = urlObj.searchParams.get('state');\n\n if (!code || !state) {\n return { error: 'missing_params', errorDescription: 'Missing code or state parameter' };\n }\n\n return { code, state };\n },\n };\n\n private openOAuthFlow(\n url: string,\n state: string,\n redirectUri: string\n ): Promise<OAuthResult> {\n return new Promise((resolve, reject) => {\n if (this.usePopup) {\n this.openOAuthPopup(url, state, redirectUri, resolve, reject);\n } else {\n // Full page redirect - won't resolve, page navigates away\n window.location.href = url;\n }\n });\n }\n\n private openOAuthPopup(\n url: string,\n state: string,\n redirectUri: string,\n resolve: (value: OAuthResult) => void,\n reject: (error: OpenKeyError) => void\n ) {\n const left = window.screenX + (window.outerWidth - POPUP_WIDTH) / 2;\n const top = window.screenY + (window.outerHeight - POPUP_HEIGHT) / 2;\n\n this.popup = window.open(\n url,\n 'openkey-oauth',\n `width=${POPUP_WIDTH},height=${POPUP_HEIGHT},left=${left},top=${top},popup=true`\n );\n\n if (!this.popup) {\n reject({\n code: 'POPUP_BLOCKED',\n message: 'Popup was blocked. Please allow popups or use redirect mode.',\n });\n return;\n }\n\n const redirectHost = new URL(redirectUri).origin;\n\n // Poll for redirect back with code\n const pollInterval = setInterval(() => {\n try {\n if (this.popup?.closed) {\n clearInterval(pollInterval);\n clearTimeout(timeout);\n reject({\n code: 'USER_CANCELLED',\n message: 'User closed the popup',\n });\n return;\n }\n\n // Check if redirected to callback URL\n const popupUrl = this.popup?.location?.href;\n if (popupUrl && popupUrl.startsWith(redirectHost)) {\n clearInterval(pollInterval);\n clearTimeout(timeout);\n\n const urlObj = new URL(popupUrl);\n const code = urlObj.searchParams.get('code');\n const returnedState = urlObj.searchParams.get('state');\n const error = urlObj.searchParams.get('error');\n\n this.popup?.close();\n\n if (error) {\n reject({\n code: 'UNAUTHORIZED',\n message: urlObj.searchParams.get('error_description') || error,\n });\n return;\n }\n\n if (returnedState !== state) {\n reject({\n code: 'STATE_MISMATCH',\n message: 'State mismatch - possible CSRF attack',\n });\n return;\n }\n\n if (code) {\n resolve({ code, state: returnedState });\n } else {\n reject({\n code: 'UNAUTHORIZED',\n message: 'Authorization failed - no code returned',\n });\n }\n }\n } catch {\n // Cross-origin - popup still on OpenKey domain, continue polling\n }\n }, 100);\n\n // Timeout\n const timeout = setTimeout(() => {\n clearInterval(pollInterval);\n this.popup?.close();\n reject({\n code: 'TIMEOUT',\n message: 'OAuth flow timed out',\n });\n }, DEFAULT_TIMEOUT);\n }\n\n private async openFlow<T>(action: string, message: object): Promise<T> {\n return new Promise((resolve, reject) => {\n const url = `${this.host}/widget/${action}?origin=${encodeURIComponent(window.location.origin)}`;\n\n if (this.usePopup) {\n this.openPopup(url, message, resolve, reject);\n } else {\n this.openIframe(url, message, resolve, reject);\n }\n });\n }\n\n private openPopup<T>(\n url: string,\n message: object,\n resolve: (value: T) => void,\n reject: (error: OpenKeyError) => void\n ) {\n // Calculate popup position\n const left = window.screenX + (window.outerWidth - POPUP_WIDTH) / 2;\n const top = window.screenY + (window.outerHeight - POPUP_HEIGHT) / 2;\n\n this.popup = window.open(\n url,\n 'openkey',\n `width=${POPUP_WIDTH},height=${POPUP_HEIGHT},left=${left},top=${top},popup=true`\n );\n\n if (!this.popup) {\n reject({\n code: 'POPUP_BLOCKED',\n message: 'Popup was blocked. Please allow popups for this site.',\n });\n return;\n }\n\n const cleanup = () => {\n window.removeEventListener('message', handleMessage);\n clearInterval(pollClosed);\n clearTimeout(timeout);\n };\n\n // Poll to detect popup close\n const pollClosed = setInterval(() => {\n if (this.popup?.closed) {\n cleanup();\n reject({\n code: 'USER_CANCELLED',\n message: 'User closed the popup',\n });\n }\n }, 500);\n\n // Timeout\n const timeout = setTimeout(() => {\n cleanup();\n this.popup?.close();\n reject({\n code: 'TIMEOUT',\n message: 'Request timed out',\n });\n }, DEFAULT_TIMEOUT);\n\n // Listen for messages\n const handleMessage = (event: MessageEvent) => {\n if (event.origin !== this.host) return;\n\n const data = event.data as MessageType;\n\n if (data.type === 'openkey:ready') {\n // Widget is ready, send our request\n this.popup?.postMessage(message, this.host);\n return;\n }\n\n if (data.type === 'openkey:close') {\n cleanup();\n this.popup?.close();\n reject({\n code: 'USER_CANCELLED',\n message: 'User cancelled the request',\n });\n return;\n }\n\n // Handle responses\n if (\n data.type === 'openkey:auth:response' ||\n data.type === 'openkey:sign:response' ||\n data.type === 'openkey:signTypedData:response' ||\n data.type === 'openkey:link-wallet:response'\n ) {\n cleanup();\n this.popup?.close();\n\n if (data.success) {\n if (data.type === 'openkey:auth:response') {\n resolve({ address: data.address, keyId: data.keyId, keyType: data.keyType || 'MANAGED' } as T);\n } else if (data.type === 'openkey:link-wallet:response') {\n resolve({ address: data.address, keyId: data.keyId } as T);\n } else {\n resolve({ signature: data.signature, address: data.address } as T);\n }\n } else {\n reject(data.error);\n }\n }\n };\n\n window.addEventListener('message', handleMessage);\n }\n\n private openIframe<T>(\n url: string,\n message: object,\n resolve: (value: T) => void,\n reject: (error: OpenKeyError) => void\n ) {\n // Create overlay\n const overlay = document.createElement('div');\n overlay.style.cssText = `\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 999999;\n `;\n\n // Create iframe container\n const container = document.createElement('div');\n container.style.cssText = `\n width: ${POPUP_WIDTH}px;\n height: ${POPUP_HEIGHT}px;\n background: #1a1a1a;\n border-radius: 12px;\n overflow: hidden;\n box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);\n `;\n\n // Create iframe\n this.iframe = document.createElement('iframe');\n this.iframe.src = url;\n this.iframe.style.cssText = `\n width: 100%;\n height: 100%;\n border: none;\n `;\n\n container.appendChild(this.iframe);\n overlay.appendChild(container);\n document.body.appendChild(overlay);\n\n const cleanup = () => {\n window.removeEventListener('message', handleMessage);\n clearTimeout(timeout);\n overlay.remove();\n this.iframe = null;\n };\n\n // Close on overlay click\n overlay.addEventListener('click', (e) => {\n if (e.target === overlay) {\n cleanup();\n reject({\n code: 'USER_CANCELLED',\n message: 'User cancelled the request',\n });\n }\n });\n\n // Timeout\n const timeout = setTimeout(() => {\n cleanup();\n reject({\n code: 'TIMEOUT',\n message: 'Request timed out',\n });\n }, DEFAULT_TIMEOUT);\n\n // Listen for messages\n const handleMessage = (event: MessageEvent) => {\n if (event.origin !== this.host) return;\n\n const data = event.data as MessageType;\n\n if (data.type === 'openkey:ready') {\n this.iframe?.contentWindow?.postMessage(message, this.host);\n return;\n }\n\n if (data.type === 'openkey:close') {\n cleanup();\n reject({\n code: 'USER_CANCELLED',\n message: 'User cancelled the request',\n });\n return;\n }\n\n // Handle responses\n if (\n data.type === 'openkey:auth:response' ||\n data.type === 'openkey:sign:response' ||\n data.type === 'openkey:signTypedData:response' ||\n data.type === 'openkey:link-wallet:response'\n ) {\n cleanup();\n\n if (data.success) {\n if (data.type === 'openkey:auth:response') {\n resolve({ address: data.address, keyId: data.keyId, keyType: data.keyType || 'MANAGED' } as T);\n } else if (data.type === 'openkey:link-wallet:response') {\n resolve({ address: data.address, keyId: data.keyId } as T);\n } else {\n resolve({ signature: data.signature, address: data.address } as T);\n }\n } else {\n reject(data.error);\n }\n }\n };\n\n window.addEventListener('message', handleMessage);\n }\n\n /**\n * Disconnect and close any open flows\n */\n disconnect() {\n this.popup?.close();\n this.popup = null;\n this.iframe?.parentElement?.parentElement?.remove();\n this.iframe = null;\n }\n}\n\n/**\n * Unified EIP-1193 provider that wraps an OpenKey instance.\n * Transparently routes signing to either OpenKey (managed keys) or the user's\n * wallet (external keys).\n */\nexport class OpenKeyEIP1193Provider implements EIP1193Provider {\n private openkey: OpenKey;\n private address: string;\n private keyId: string;\n private keyType: 'MANAGED' | 'EXTERNAL';\n\n constructor(openkey: OpenKey, authResult: AuthResult) {\n this.openkey = openkey;\n this.address = authResult.address;\n this.keyId = authResult.keyId;\n this.keyType = authResult.keyType;\n }\n\n async request({ method, params }: { method: string; params?: any[] }): Promise<any> {\n switch (method) {\n case 'eth_accounts':\n case 'eth_requestAccounts':\n return [this.address];\n\n case 'eth_chainId':\n return '0x1'; // mainnet\n\n case 'personal_sign': {\n if (this.keyType === 'EXTERNAL') {\n const provider = await this.openkey.findWalletProvider(this.address);\n return provider.request({ method, params });\n }\n // Managed key: route through OpenKey\n const message = this.hexToString(params![0] as string);\n const result = await this.openkey.signMessage({ message, keyId: this.keyId });\n return result.signature;\n }\n\n case 'eth_signTypedData_v4': {\n if (this.keyType === 'EXTERNAL') {\n const provider = await this.openkey.findWalletProvider(this.address);\n return provider.request({ method, params });\n }\n const data = JSON.parse(params![1] as string);\n const result = await this.openkey.signTypedData(data);\n return result.signature;\n }\n\n default:\n throw new Error(`Unsupported method: ${method}`);\n }\n }\n\n private hexToString(hex: string): string {\n const bytes = new Uint8Array(\n (hex.startsWith('0x') ? hex.slice(2) : hex).match(/.{1,2}/g)!.map(b => parseInt(b, 16))\n );\n return new TextDecoder().decode(bytes);\n }\n}\n\n// Default export for convenience\nexport default OpenKey;\n\n// Create singleton instance\nlet defaultInstance: OpenKey | null = null;\n\nexport function getOpenKey(config?: OpenKeyConfig): OpenKey {\n if (!defaultInstance || config) {\n defaultInstance = new OpenKey(config);\n }\n return defaultInstance;\n}\n"],"mappings":"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","names":["index_exports","__export","OpenKey","OpenKeyEIP1193Provider","index_default","getOpenKey","__toCommonJS","DEFAULT_HOST","OAUTH_STORAGE_KEY","base64UrlEncode","buffer","generateCodeVerifier","array","generateCodeChallenge","verifier","data","digest","generateState","OpenKey","config","DEFAULT_HOST","event","result","request","targetAddress","provider","a","hexMessage","str","b","res","challenge","state","authUrl","code","stored","response","error","receivedState","url","urlObj","redirectUri","resolve","reject","left","top","redirectHost","pollInterval","timeout","popupUrl","returnedState","action","message","cleanup","handleMessage","pollClosed","overlay","container","e","OpenKeyEIP1193Provider","openkey","authResult","method","params","hex","bytes","index_default","defaultInstance","getOpenKey"]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// OpenKey SDK - Browser client for third-party apps\n// Provides \"Sign with OpenKey\" functionality via popup or iframe\n\nexport interface EIP1193Provider {\n request(args: { method: string; params?: any[] }): Promise<any>;\n}\n\ninterface EIP6963ProviderInfo {\n uuid: string;\n name: string;\n icon: string;\n rdns: string;\n}\n\ninterface EIP6963ProviderDetail {\n info: EIP6963ProviderInfo;\n provider: EIP1193Provider;\n}\n\nexport type OpenKeyMode = 'iframe' | 'popup' | 'redirect';\n\nexport interface OpenKeyConfig {\n /** OpenKey host URL (default: https://openkey.so) */\n host?: string;\n /** App identifier for display */\n appName?: string;\n /** UI mode: 'iframe' (default), 'popup', or 'redirect' */\n mode?: OpenKeyMode;\n /** App-provided wallet provider for external key signing */\n externalProvider?: EIP1193Provider;\n}\n\nexport interface SignMessageRequest {\n message: string;\n keyId?: string; // If not provided, user selects\n}\n\nexport interface SignTypedDataRequest {\n domain: {\n name?: string;\n version?: string;\n chainId?: number;\n verifyingContract?: string;\n };\n types: Record<string, Array<{ name: string; type: string }>>;\n primaryType: string;\n message: Record<string, unknown>;\n keyId?: string;\n}\n\nexport interface SignResult {\n signature: string;\n address: string;\n}\n\nexport interface AuthResult {\n address: string;\n keyId: string;\n keyType: 'MANAGED' | 'EXTERNAL';\n}\n\nexport interface OpenKeyError {\n code: 'USER_CANCELLED' | 'POPUP_BLOCKED' | 'TIMEOUT' | 'NO_KEY' | 'UNAUTHORIZED' | 'UNKNOWN' | 'STATE_MISMATCH';\n message: string;\n}\n\n// ======= OAuth 2.1 Types =======\n\n/** OAuth configuration for third-party apps */\nexport interface OAuthConfig {\n /** OAuth client_id (registered with OpenKey) */\n clientId: string;\n /** Redirect URI (must match registered URI) */\n redirectUri: string;\n /** State parameter for CSRF protection (auto-generated if not provided) */\n state?: string;\n}\n\n/** Result from OAuth authorization */\nexport interface OAuthResult {\n /** Authorization code (exchange for tokens) */\n code: string;\n /** State parameter (verify matches request) */\n state: string;\n}\n\n/** Response from token exchange */\nexport interface OAuthTokenResponse {\n access_token: string;\n token_type: 'Bearer';\n expires_in: number;\n id_token: string;\n scope: string;\n refresh_token?: string;\n}\n\ntype MessageType =\n | { type: 'openkey:auth:request'; appName: string }\n | { type: 'openkey:auth:response'; success: true; address: string; keyId: string; keyType?: 'MANAGED' | 'EXTERNAL' }\n | { type: 'openkey:auth:response'; success: false; error: OpenKeyError }\n | { type: 'openkey:sign:request'; message: string; keyId?: string }\n | { type: 'openkey:sign:response'; success: true; signature: string; address: string }\n | { type: 'openkey:sign:response'; success: false; error: OpenKeyError }\n | { type: 'openkey:signTypedData:request'; data: SignTypedDataRequest }\n | { type: 'openkey:signTypedData:response'; success: true; signature: string; address: string }\n | { type: 'openkey:signTypedData:response'; success: false; error: OpenKeyError }\n | { type: 'openkey:link-wallet:request' }\n | { type: 'openkey:link-wallet:response'; success: true; address: string; keyId: string }\n | { type: 'openkey:link-wallet:response'; success: false; error: OpenKeyError }\n | { type: 'openkey:link-wallet:delegate' }\n | { type: 'openkey:link-wallet:result'; success: true; address: string; keyId: string }\n | { type: 'openkey:link-wallet:result'; success: false; error: OpenKeyError }\n | { type: 'openkey:resize'; height: number }\n | { type: 'openkey:ready' }\n | { type: 'openkey:close' };\n\nconst DEFAULT_HOST = 'https://openkey.so';\nconst POPUP_WIDTH = 400;\nconst POPUP_HEIGHT = 600;\nconst DEFAULT_TIMEOUT = 5 * 60 * 1000; // 5 minutes\nconst IFRAME_READY_TIMEOUT = 3000; // 3 seconds\nconst OAUTH_STORAGE_KEY = 'openkey_oauth';\n\nclass IframeModal {\n private root: HTMLDivElement;\n private shadow: ShadowRoot;\n private iframe: HTMLIFrameElement;\n private onClose: () => void;\n private onMessage: (data: MessageType) => void;\n private messageHandler: (event: MessageEvent) => void;\n private host: string;\n\n constructor(opts: { url: string; host: string; onClose: () => void; onMessage: (data: MessageType) => void }) {\n this.host = opts.host;\n this.onClose = opts.onClose;\n this.onMessage = opts.onMessage;\n\n const isMobile = window.matchMedia('(max-width: 639px)').matches;\n\n this.root = document.createElement('div');\n this.shadow = this.root.attachShadow({ mode: 'closed' });\n\n const style = document.createElement('style');\n style.textContent = `\n :host{all:initial}\n .ok-backdrop{position:fixed;inset:0;background:rgba(0,0,0,0.4);backdrop-filter:blur(4px);z-index:999999;display:flex;align-items:${isMobile ? 'flex-end' : 'center'};justify-content:center;animation:ok-fade-in 150ms ease-out}\n .ok-card{position:relative;background:#fafafa;width:${isMobile ? '100%' : '400px'};border-radius:${isMobile ? '16px 16px 0 0' : '16px'};box-shadow:0 25px 50px -12px rgba(0,0,0,0.25);overflow:hidden;overflow-y:auto;animation:${isMobile ? 'ok-slide-up 250ms ease-out' : 'ok-scale-in 200ms ease-out'};max-height:90vh}\n .ok-close{position:absolute;top:8px;right:8px;width:24px;height:24px;border:none;background:transparent;color:#6b7280;font-size:18px;cursor:pointer;z-index:1;display:flex;align-items:center;justify-content:center;border-radius:4px}\n .ok-close:hover{color:#111827}\n .ok-handle{width:40px;height:4px;background:#d4d4d4;border-radius:2px;margin:8px auto 0}\n iframe{border:none;width:100%;height:400px;display:block;transition:height 200ms ease}\n .ok-backdrop.ok-exit{animation:ok-fade-out 150ms ease-in}\n .ok-backdrop.ok-exit .ok-card{animation:${isMobile ? 'ok-slide-down 200ms ease-in' : 'ok-scale-out 150ms ease-in'}}\n @keyframes ok-fade-in{from{opacity:0}to{opacity:1}}\n @keyframes ok-fade-out{from{opacity:1}to{opacity:0}}\n @keyframes ok-scale-in{from{opacity:0;transform:scale(0.95)}to{opacity:1;transform:scale(1)}}\n @keyframes ok-scale-out{from{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(0.95)}}\n @keyframes ok-slide-up{from{transform:translateY(100%)}to{transform:translateY(0)}}\n @keyframes ok-slide-down{from{transform:translateY(0)}to{transform:translateY(100%)}}\n `;\n\n const backdrop = document.createElement('div');\n backdrop.className = 'ok-backdrop';\n backdrop.addEventListener('click', (e) => {\n if (e.target === backdrop) this.close();\n });\n\n const card = document.createElement('div');\n card.className = 'ok-card';\n\n if (isMobile) {\n const handle = document.createElement('div');\n handle.className = 'ok-handle';\n card.appendChild(handle);\n }\n\n const closeBtn = document.createElement('button');\n closeBtn.className = 'ok-close';\n closeBtn.textContent = '\\u00d7';\n closeBtn.addEventListener('click', () => this.close());\n card.appendChild(closeBtn);\n\n this.iframe = document.createElement('iframe');\n this.iframe.src = opts.url;\n this.iframe.setAttribute('allow', 'publickey-credentials-get *; publickey-credentials-create *');\n this.iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-popups');\n card.appendChild(this.iframe);\n\n backdrop.appendChild(card);\n this.shadow.appendChild(style);\n this.shadow.appendChild(backdrop);\n\n this.messageHandler = (event: MessageEvent) => {\n if (event.origin !== this.host) return;\n const data = event.data as MessageType;\n if (data.type === 'openkey:resize') {\n const h = Math.min(data.height, Math.floor(window.innerHeight * 0.85));\n this.iframe.style.height = `${h}px`;\n return;\n }\n this.onMessage(data);\n };\n window.addEventListener('message', this.messageHandler);\n document.body.appendChild(this.root);\n }\n\n postMessage(message: object) {\n this.iframe.contentWindow?.postMessage(message, this.host);\n }\n\n destroy() {\n window.removeEventListener('message', this.messageHandler);\n this.root.remove();\n }\n\n private close() {\n const backdrop = this.shadow.querySelector('.ok-backdrop');\n if (backdrop) {\n backdrop.classList.add('ok-exit');\n setTimeout(() => {\n this.destroy();\n this.onClose();\n }, 200);\n } else {\n this.destroy();\n this.onClose();\n }\n }\n}\n\nclass WalletPicker {\n private root: HTMLDivElement;\n private shadow: ShadowRoot;\n\n constructor(opts: { providers: EIP6963ProviderDetail[]; onSelect: (provider: EIP1193Provider) => void; onCancel: () => void }) {\n this.root = document.createElement('div');\n this.shadow = this.root.attachShadow({ mode: 'closed' });\n\n const style = document.createElement('style');\n style.textContent = `\n :host{all:initial}\n .wp-backdrop{position:fixed;inset:0;background:rgba(0,0,0,0.4);backdrop-filter:blur(4px);z-index:1000000;display:flex;align-items:center;justify-content:center;animation:wp-fade 150ms ease-out}\n .wp-card{background:#fff;width:340px;border-radius:16px;box-shadow:0 25px 50px -12px rgba(0,0,0,0.25);padding:20px;animation:wp-scale 200ms ease-out}\n .wp-title{font:600 16px/1.3 system-ui,sans-serif;color:#111;margin:0 0 12px}\n .wp-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}\n .wp-item{display:flex;align-items:center;gap:10px;padding:10px 12px;border:1px solid #e5e7eb;border-radius:10px;background:transparent;cursor:pointer;font:14px/1.3 system-ui,sans-serif;color:#111;width:100%;text-align:left;transition:border-color 150ms}\n .wp-item:hover{border-color:#6366f1}\n .wp-icon{width:28px;height:28px;border-radius:6px}\n .wp-empty{font:14px/1.4 system-ui,sans-serif;color:#6b7280;text-align:center;padding:16px 0}\n .wp-cancel{display:block;width:100%;margin-top:12px;padding:8px;border:none;background:transparent;color:#6b7280;font:14px/1.3 system-ui,sans-serif;cursor:pointer;border-radius:8px}\n .wp-cancel:hover{background:#f3f4f6;color:#111}\n @keyframes wp-fade{from{opacity:0}to{opacity:1}}\n @keyframes wp-scale{from{opacity:0;transform:scale(0.95)}to{opacity:1;transform:scale(1)}}\n @media(prefers-color-scheme:dark){\n .wp-card{background:#1a1a1a;box-shadow:0 25px 50px -12px rgba(0,0,0,0.5)}\n .wp-title{color:#f3f4f6}\n .wp-item{border-color:#374151;color:#f3f4f6}\n .wp-item:hover{border-color:#818cf8}\n .wp-empty{color:#9ca3af}\n .wp-cancel{color:#9ca3af}\n .wp-cancel:hover{background:#262626;color:#f3f4f6}\n }\n `;\n\n const backdrop = document.createElement('div');\n backdrop.className = 'wp-backdrop';\n backdrop.addEventListener('click', (e) => { if (e.target === backdrop) { this.destroy(); opts.onCancel(); } });\n\n const card = document.createElement('div');\n card.className = 'wp-card';\n\n const title = document.createElement('h2');\n title.className = 'wp-title';\n title.textContent = 'Select a wallet';\n card.appendChild(title);\n\n if (opts.providers.length === 0) {\n const empty = document.createElement('div');\n empty.className = 'wp-empty';\n empty.textContent = 'No wallets detected';\n card.appendChild(empty);\n } else {\n const list = document.createElement('ul');\n list.className = 'wp-list';\n for (const { info, provider } of opts.providers) {\n const item = document.createElement('button');\n item.className = 'wp-item';\n if (info.icon) {\n const icon = document.createElement('img');\n icon.className = 'wp-icon';\n icon.src = info.icon;\n icon.alt = info.name;\n item.appendChild(icon);\n }\n const name = document.createElement('span');\n name.textContent = info.name;\n item.appendChild(name);\n item.addEventListener('click', () => { this.destroy(); opts.onSelect(provider); });\n list.appendChild(item);\n }\n card.appendChild(list);\n }\n\n const cancel = document.createElement('button');\n cancel.className = 'wp-cancel';\n cancel.textContent = 'Cancel';\n cancel.addEventListener('click', () => { this.destroy(); opts.onCancel(); });\n card.appendChild(cancel);\n\n backdrop.appendChild(card);\n this.shadow.appendChild(style);\n this.shadow.appendChild(backdrop);\n document.body.appendChild(this.root);\n }\n\n destroy() {\n this.root.remove();\n }\n}\n\nfunction showToast(message = 'Opening in new window\\u2026', variant: 'info' | 'error' = 'info') {\n const bg = variant === 'error' ? '#dc2626' : '#1f2937';\n const root = document.createElement('div');\n const shadow = root.attachShadow({ mode: 'closed' });\n shadow.innerHTML = `<style>:host{all:initial}.ok-toast{position:fixed;top:16px;right:16px;z-index:1000000;background:${bg};color:#f9fafb;padding:10px 16px;border-radius:8px;font:14px/1.4 system-ui,sans-serif;box-shadow:0 4px 12px rgba(0,0,0,0.15);animation:ok-t-in 200ms ease-out;max-width:360px}@keyframes ok-t-in{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}</style><div class=\"ok-toast\">${message}</div>`;\n document.body.appendChild(root);\n setTimeout(() => root.remove(), 4000);\n}\n\n// ======= PKCE Utilities =======\n\nfunction base64UrlEncode(buffer: Uint8Array): string {\n const base64 = btoa(String.fromCharCode(...buffer));\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=/g, '');\n}\n\nasync function generateCodeVerifier(): Promise<string> {\n const array = new Uint8Array(32);\n crypto.getRandomValues(array);\n return base64UrlEncode(array);\n}\n\nasync function generateCodeChallenge(verifier: string): Promise<string> {\n const encoder = new TextEncoder();\n const data = encoder.encode(verifier);\n const digest = await crypto.subtle.digest('SHA-256', data);\n return base64UrlEncode(new Uint8Array(digest));\n}\n\nfunction generateState(): string {\n const array = new Uint8Array(16);\n crypto.getRandomValues(array);\n return base64UrlEncode(array);\n}\n\nclass ExternalWalletError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ExternalWalletError';\n }\n}\n\nexport class OpenKey {\n private host: string;\n private appName: string;\n private mode: OpenKeyMode;\n private config: OpenKeyConfig;\n private popup: Window | null = null;\n private lastAuth: AuthResult | null = null;\n private discoveredProviders: EIP6963ProviderDetail[] = [];\n\n constructor(config: OpenKeyConfig = {}) {\n this.config = config;\n this.host = config.host || DEFAULT_HOST;\n this.appName = config.appName || window.location.hostname;\n this.mode = config.mode ?? 'iframe';\n\n // Listen for EIP-6963 wallet announcements\n if (typeof window !== 'undefined') {\n this.discoveredProviders = [];\n window.addEventListener('eip6963:announceProvider', (event: any) => {\n this.discoveredProviders.push(event.detail);\n });\n window.dispatchEvent(new Event('eip6963:requestProvider'));\n }\n }\n\n /**\n * Connect to OpenKey and get user's wallet address\n * Opens auth flow for user to select/create a key\n */\n async connect(opts?: { mode?: OpenKeyMode }): Promise<AuthResult> {\n const result = await this.openFlow<AuthResult>('connect', {\n type: 'openkey:auth:request',\n appName: this.appName,\n }, opts?.mode);\n this.lastAuth = result;\n return result;\n }\n\n /**\n * Link an external wallet to the user's OpenKey account\n * Opens link-wallet widget flow\n */\n async linkWallet(opts?: { mode?: OpenKeyMode }): Promise<{ address: string; keyId: string }> {\n return this.openFlow<{ address: string; keyId: string }>('link-wallet', {\n type: 'openkey:link-wallet:request',\n }, opts?.mode);\n }\n\n /**\n * Sign a message with the user's OpenKey wallet\n * For external keys, routes directly to the user's wallet provider\n */\n async signMessage(request: SignMessageRequest, opts?: { mode?: OpenKeyMode }): Promise<SignResult> {\n if (this.lastAuth?.keyType === 'EXTERNAL') {\n return this.signWithExternalWallet(request);\n }\n return this.signWithOpenKey(request, opts?.mode);\n }\n\n /**\n * Sign typed data (EIP-712) with the user's OpenKey wallet\n * For external keys, routes directly to the user's wallet provider\n */\n async signTypedData(request: SignTypedDataRequest, opts?: { mode?: OpenKeyMode }): Promise<SignResult> {\n if (this.lastAuth?.keyType === 'EXTERNAL') {\n return this.signTypedDataWithExternalWallet(request);\n }\n return this.signTypedDataWithOpenKey(request, opts?.mode);\n }\n\n /**\n * Find a wallet provider that controls the given address.\n * Checks: 1) app-provided externalProvider, 2) EIP-6963 discovered wallets, 3) window.ethereum\n */\n async findWalletProvider(targetAddress: string): Promise<EIP1193Provider> {\n // 1. Try app-provided externalProvider first\n if (this.config.externalProvider) {\n return this.config.externalProvider;\n }\n\n // 2. Check EIP-6963 discovered wallets silently (no popup)\n for (const { provider } of this.discoveredProviders) {\n try {\n const accounts = await provider.request({ method: 'eth_accounts' }) as string[];\n if (accounts.some(a => a.toLowerCase() === targetAddress.toLowerCase())) {\n return provider;\n }\n } catch {}\n }\n\n // 3. Fall back to window.ethereum silently\n if (typeof window !== 'undefined' && (window as any).ethereum) {\n const provider = (window as any).ethereum as EIP1193Provider;\n try {\n const accounts = await provider.request({ method: 'eth_accounts' }) as string[];\n if (accounts.some(a => a.toLowerCase() === targetAddress.toLowerCase())) {\n return provider;\n }\n } catch {}\n }\n\n throw new ExternalWalletError('External wallet not found. Connect the wallet that owns this key and try again.');\n }\n\n private async signWithExternalWallet(request: SignMessageRequest): Promise<SignResult> {\n let provider: EIP1193Provider;\n try {\n provider = await this.findWalletProvider(this.lastAuth!.address);\n } catch (e) {\n showToast((e as Error).message, 'error');\n throw e;\n }\n try {\n const hexMessage = this.toHex(request.message);\n const signature = await provider.request({\n method: 'personal_sign',\n params: [hexMessage, this.lastAuth!.address],\n });\n return { signature: signature as string, address: this.lastAuth!.address };\n } catch (e: any) {\n const msg = e?.message?.includes('not been authorized')\n ? 'Wallet rejected the request. Approve the connection in your wallet and try again.'\n : e?.message || 'Signing failed';\n showToast(msg, 'error');\n throw new Error(msg);\n }\n }\n\n private async signTypedDataWithExternalWallet(request: SignTypedDataRequest): Promise<SignResult> {\n let provider: EIP1193Provider;\n try {\n provider = await this.findWalletProvider(this.lastAuth!.address);\n } catch (e) {\n showToast((e as Error).message, 'error');\n throw e;\n }\n try {\n const signature = await provider.request({\n method: 'eth_signTypedData_v4',\n params: [this.lastAuth!.address, JSON.stringify(request)],\n });\n return { signature: signature as string, address: this.lastAuth!.address };\n } catch (e: any) {\n const msg = e?.message?.includes('not been authorized')\n ? 'Wallet rejected the request. Approve the connection in your wallet and try again.'\n : e?.message || 'Signing failed';\n showToast(msg, 'error');\n throw new Error(msg);\n }\n }\n\n private signWithOpenKey(request: SignMessageRequest, mode?: OpenKeyMode): Promise<SignResult> {\n return this.openFlow<SignResult>('sign', {\n type: 'openkey:sign:request',\n message: request.message,\n keyId: request.keyId,\n }, mode);\n }\n\n private signTypedDataWithOpenKey(request: SignTypedDataRequest, mode?: OpenKeyMode): Promise<SignResult> {\n return this.openFlow<SignResult>('sign-typed-data', {\n type: 'openkey:signTypedData:request',\n data: request,\n }, mode);\n }\n\n private toHex(str: string): string {\n return '0x' + Array.from(new TextEncoder().encode(str)).map(b => b.toString(16).padStart(2, '0')).join('');\n }\n\n /**\n * Check if user is already authenticated\n */\n async isConnected(): Promise<boolean> {\n // Check for existing session via API\n try {\n const res = await fetch(`${this.host}/api/auth/session`, {\n credentials: 'include',\n });\n if (!res.ok) return false;\n const data = await res.json();\n return !!data.user;\n } catch {\n return false;\n }\n }\n\n // ======= OAuth 2.1 Provider Methods =======\n\n /**\n * OAuth 2.1 methods for third-party app authentication\n * Use this when your app has registered OAuth client credentials with OpenKey\n */\n oauth = {\n /**\n * Start OAuth authorization flow\n * Opens popup/redirect to OpenKey authorization endpoint\n * @returns Promise that resolves with authorization code after user consent\n */\n connect: async (config: OAuthConfig): Promise<OAuthResult> => {\n // Generate PKCE values\n const verifier = await generateCodeVerifier();\n const challenge = await generateCodeChallenge(verifier);\n const state = config.state || generateState();\n\n // Store for later token exchange\n sessionStorage.setItem(\n OAUTH_STORAGE_KEY,\n JSON.stringify({ verifier, state })\n );\n\n // Build authorization URL\n const authUrl = new URL(`${this.host}/api/auth/oauth2/authorize`);\n authUrl.searchParams.set('client_id', config.clientId);\n authUrl.searchParams.set('redirect_uri', config.redirectUri);\n authUrl.searchParams.set('response_type', 'code');\n authUrl.searchParams.set('scope', 'openid');\n authUrl.searchParams.set('state', state);\n authUrl.searchParams.set('code_challenge', challenge);\n authUrl.searchParams.set('code_challenge_method', 'S256');\n\n return this.openOAuthFlow(authUrl.toString(), state, config.redirectUri);\n },\n\n /**\n * Exchange authorization code for tokens\n * Call this after receiving the callback with authorization code\n * @param code - Authorization code from callback URL\n * @param config - Same config used for connect()\n * @returns Promise that resolves with access_token, id_token, etc.\n */\n exchangeCode: async (\n code: string,\n config: OAuthConfig\n ): Promise<OAuthTokenResponse> => {\n const stored = sessionStorage.getItem(OAUTH_STORAGE_KEY);\n if (!stored) {\n throw new Error('No PKCE verifier found. Start a new OAuth flow.');\n }\n\n const { verifier } = JSON.parse(stored);\n\n const response = await fetch(`${this.host}/api/auth/oauth2/token`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n code,\n redirect_uri: config.redirectUri,\n client_id: config.clientId,\n code_verifier: verifier,\n }),\n });\n\n // Clear stored verifier\n sessionStorage.removeItem(OAUTH_STORAGE_KEY);\n\n if (!response.ok) {\n const error = await response.json();\n throw new Error(error.error_description || 'Token exchange failed');\n }\n\n return response.json();\n },\n\n /**\n * Verify state parameter from callback\n * Call this before exchangeCode to prevent CSRF attacks\n * @param receivedState - State parameter from callback URL\n * @returns true if state matches, false otherwise\n */\n verifyState: (receivedState: string): boolean => {\n const stored = sessionStorage.getItem(OAUTH_STORAGE_KEY);\n if (!stored) return false;\n const { state } = JSON.parse(stored);\n return state === receivedState;\n },\n\n /**\n * Parse authorization response from callback URL\n * @param url - Callback URL (defaults to current window.location.href)\n * @returns Parsed code and state, or error\n */\n parseCallback: (\n url?: string\n ): { code: string; state: string } | { error: string; errorDescription?: string } => {\n const urlObj = new URL(url || window.location.href);\n const error = urlObj.searchParams.get('error');\n\n if (error) {\n return {\n error,\n errorDescription: urlObj.searchParams.get('error_description') || undefined,\n };\n }\n\n const code = urlObj.searchParams.get('code');\n const state = urlObj.searchParams.get('state');\n\n if (!code || !state) {\n return { error: 'missing_params', errorDescription: 'Missing code or state parameter' };\n }\n\n return { code, state };\n },\n };\n\n private openOAuthFlow(\n url: string,\n state: string,\n redirectUri: string\n ): Promise<OAuthResult> {\n return new Promise((resolve, reject) => {\n if (this.mode !== 'redirect') {\n this.openOAuthPopup(url, state, redirectUri, resolve, reject);\n } else {\n // Full page redirect - won't resolve, page navigates away\n window.location.href = url;\n }\n });\n }\n\n private openOAuthPopup(\n url: string,\n state: string,\n redirectUri: string,\n resolve: (value: OAuthResult) => void,\n reject: (error: OpenKeyError) => void\n ) {\n const left = window.screenX + (window.outerWidth - POPUP_WIDTH) / 2;\n const top = window.screenY + (window.outerHeight - POPUP_HEIGHT) / 2;\n\n this.popup = window.open(\n url,\n 'openkey-oauth',\n `width=${POPUP_WIDTH},height=${POPUP_HEIGHT},left=${left},top=${top},popup=true`\n );\n\n if (!this.popup) {\n reject({\n code: 'POPUP_BLOCKED',\n message: 'Popup was blocked. Please allow popups or use redirect mode.',\n });\n return;\n }\n\n const redirectHost = new URL(redirectUri).origin;\n\n // Poll for redirect back with code\n const pollInterval = setInterval(() => {\n try {\n if (this.popup?.closed) {\n clearInterval(pollInterval);\n clearTimeout(timeout);\n reject({\n code: 'USER_CANCELLED',\n message: 'User closed the popup',\n });\n return;\n }\n\n // Check if redirected to callback URL\n const popupUrl = this.popup?.location?.href;\n if (popupUrl && popupUrl.startsWith(redirectHost)) {\n clearInterval(pollInterval);\n clearTimeout(timeout);\n\n const urlObj = new URL(popupUrl);\n const code = urlObj.searchParams.get('code');\n const returnedState = urlObj.searchParams.get('state');\n const error = urlObj.searchParams.get('error');\n\n this.popup?.close();\n\n if (error) {\n reject({\n code: 'UNAUTHORIZED',\n message: urlObj.searchParams.get('error_description') || error,\n });\n return;\n }\n\n if (returnedState !== state) {\n reject({\n code: 'STATE_MISMATCH',\n message: 'State mismatch - possible CSRF attack',\n });\n return;\n }\n\n if (code) {\n resolve({ code, state: returnedState });\n } else {\n reject({\n code: 'UNAUTHORIZED',\n message: 'Authorization failed - no code returned',\n });\n }\n }\n } catch {\n // Cross-origin - popup still on OpenKey domain, continue polling\n }\n }, 100);\n\n // Timeout\n const timeout = setTimeout(() => {\n clearInterval(pollInterval);\n this.popup?.close();\n reject({\n code: 'TIMEOUT',\n message: 'OAuth flow timed out',\n });\n }, DEFAULT_TIMEOUT);\n }\n\n private resolveMode(override?: OpenKeyMode): OpenKeyMode {\n return override ?? this.mode;\n }\n\n private async openFlow<T>(action: string, message: object, modeOverride?: OpenKeyMode): Promise<T> {\n const mode = this.resolveMode(modeOverride);\n const origin = encodeURIComponent(window.location.origin);\n\n if (mode === 'popup') {\n const url = `${this.host}/widget/${action}?origin=${origin}`;\n return new Promise((resolve, reject) => this.openPopup(url, message, resolve, reject));\n }\n\n if (mode === 'redirect') {\n const url = `${this.host}/widget/${action}?origin=${origin}`;\n window.location.href = url;\n return new Promise(() => {}); // never resolves, page navigates\n }\n\n // iframe mode with auto-fallback\n const url = `${this.host}/widget/embed/${action}?origin=${origin}`;\n return this.openIframeModal<T>(url, action, message, origin);\n }\n\n private openIframeModal<T>(url: string, action: string, message: object, origin: string): Promise<T> {\n return new Promise((resolve, reject) => {\n let readyReceived = false;\n let settled = false;\n let modal: IframeModal | null = null;\n\n const settle = (fn: () => void) => {\n if (settled) return;\n settled = true;\n fn();\n };\n\n const cleanup = () => {\n clearTimeout(readyTimeout);\n clearTimeout(overallTimeout);\n modal?.destroy();\n modal = null;\n };\n\n modal = new IframeModal({\n url,\n host: this.host,\n onClose: () => {\n settle(() => {\n clearTimeout(readyTimeout);\n clearTimeout(overallTimeout);\n modal = null;\n reject({ code: 'USER_CANCELLED', message: 'User cancelled the request' } as OpenKeyError);\n });\n },\n onMessage: (data: MessageType) => {\n if (data.type === 'openkey:ready') {\n readyReceived = true;\n modal?.postMessage(message);\n return;\n }\n\n if (data.type === 'openkey:close') {\n settle(() => {\n cleanup();\n reject({ code: 'USER_CANCELLED', message: 'User cancelled the request' } as OpenKeyError);\n });\n return;\n }\n\n if (data.type === 'openkey:link-wallet:delegate') {\n this.handleWalletLinkDelegation(modal!);\n return;\n }\n\n if (\n data.type === 'openkey:auth:response' ||\n data.type === 'openkey:sign:response' ||\n data.type === 'openkey:signTypedData:response' ||\n data.type === 'openkey:link-wallet:response'\n ) {\n settle(() => {\n cleanup();\n if (data.success) {\n if (data.type === 'openkey:auth:response') {\n resolve({ address: data.address, keyId: data.keyId, keyType: data.keyType || 'MANAGED' } as T);\n } else if (data.type === 'openkey:link-wallet:response') {\n resolve({ address: data.address, keyId: data.keyId } as T);\n } else {\n resolve({ signature: data.signature, address: data.address } as T);\n }\n } else {\n reject(data.error);\n }\n });\n }\n },\n });\n\n // Auto-fallback: if no openkey:ready within 3s, fall back to popup\n const readyTimeout = setTimeout(() => {\n if (readyReceived || settled) return;\n cleanup();\n console.warn('OpenKey: iframe blocked by CSP, falling back to popup. Add frame-src https://openkey.so to your CSP.');\n showToast();\n const popupUrl = `${this.host}/widget/${action}?origin=${origin}`;\n this.openPopup(popupUrl, message, (val: any) => settle(() => resolve(val)), (err: any) => settle(() => reject(err)));\n }, IFRAME_READY_TIMEOUT);\n\n // Overall 5-minute timeout\n const overallTimeout = setTimeout(() => {\n settle(() => {\n cleanup();\n reject({ code: 'TIMEOUT', message: 'Request timed out' } as OpenKeyError);\n });\n }, DEFAULT_TIMEOUT);\n });\n }\n\n private handleWalletLinkDelegation(modal: IframeModal) {\n const sendResult = (result: MessageType) => modal.postMessage(result);\n\n new WalletPicker({\n providers: this.discoveredProviders,\n onCancel: () => {\n sendResult({ type: 'openkey:link-wallet:result', success: false, error: { code: 'USER_CANCELLED', message: 'Wallet linking cancelled' } });\n },\n onSelect: async (provider) => {\n try {\n const accounts = await provider.request({ method: 'eth_requestAccounts' }) as string[];\n const address = accounts[0];\n if (!address) throw new Error('No account returned');\n\n // Get challenge from API\n const challengeRes = await fetch(`${this.host}/api/keys/link/challenge`, {\n method: 'POST',\n credentials: 'include',\n });\n if (!challengeRes.ok) throw new Error('Failed to get challenge');\n const { message } = await challengeRes.json();\n\n // Sign challenge with wallet\n const hexMessage = this.toHex(message);\n const signature = await provider.request({\n method: 'personal_sign',\n params: [hexMessage, address],\n }) as string;\n\n // Submit to API\n const linkRes = await fetch(`${this.host}/api/keys/link`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify({ address, signature, message }),\n });\n if (!linkRes.ok) {\n const err = await linkRes.json();\n throw new Error(err.error || 'Failed to link wallet');\n }\n const { key } = await linkRes.json();\n\n sendResult({ type: 'openkey:link-wallet:result', success: true, address: key.address, keyId: key.id });\n } catch (e: any) {\n sendResult({ type: 'openkey:link-wallet:result', success: false, error: { code: 'UNKNOWN', message: e.message || 'Wallet linking failed' } });\n }\n },\n });\n }\n\n private openPopup<T>(\n url: string,\n message: object,\n resolve: (value: T) => void,\n reject: (error: OpenKeyError) => void\n ) {\n // Calculate popup position\n const left = window.screenX + (window.outerWidth - POPUP_WIDTH) / 2;\n const top = window.screenY + (window.outerHeight - POPUP_HEIGHT) / 2;\n\n this.popup = window.open(\n url,\n 'openkey',\n `width=${POPUP_WIDTH},height=${POPUP_HEIGHT},left=${left},top=${top},popup=true`\n );\n\n if (!this.popup) {\n reject({\n code: 'POPUP_BLOCKED',\n message: 'Popup was blocked. Please allow popups for this site.',\n });\n return;\n }\n\n const cleanup = () => {\n window.removeEventListener('message', handleMessage);\n clearInterval(pollClosed);\n clearTimeout(timeout);\n };\n\n // Poll to detect popup close\n const pollClosed = setInterval(() => {\n if (this.popup?.closed) {\n cleanup();\n reject({\n code: 'USER_CANCELLED',\n message: 'User closed the popup',\n });\n }\n }, 500);\n\n // Timeout\n const timeout = setTimeout(() => {\n cleanup();\n this.popup?.close();\n reject({\n code: 'TIMEOUT',\n message: 'Request timed out',\n });\n }, DEFAULT_TIMEOUT);\n\n // Listen for messages\n const handleMessage = (event: MessageEvent) => {\n if (event.origin !== this.host) return;\n\n const data = event.data as MessageType;\n\n if (data.type === 'openkey:ready') {\n // Widget is ready, send our request\n this.popup?.postMessage(message, this.host);\n return;\n }\n\n if (data.type === 'openkey:close') {\n cleanup();\n this.popup?.close();\n reject({\n code: 'USER_CANCELLED',\n message: 'User cancelled the request',\n });\n return;\n }\n\n // Handle responses\n if (\n data.type === 'openkey:auth:response' ||\n data.type === 'openkey:sign:response' ||\n data.type === 'openkey:signTypedData:response' ||\n data.type === 'openkey:link-wallet:response'\n ) {\n cleanup();\n this.popup?.close();\n\n if (data.success) {\n if (data.type === 'openkey:auth:response') {\n resolve({ address: data.address, keyId: data.keyId, keyType: data.keyType || 'MANAGED' } as T);\n } else if (data.type === 'openkey:link-wallet:response') {\n resolve({ address: data.address, keyId: data.keyId } as T);\n } else {\n resolve({ signature: data.signature, address: data.address } as T);\n }\n } else {\n reject(data.error);\n }\n }\n };\n\n window.addEventListener('message', handleMessage);\n }\n\n /**\n * Disconnect and close any open flows\n */\n disconnect() {\n this.popup?.close();\n this.popup = null;\n }\n}\n\n/**\n * Unified EIP-1193 provider that wraps an OpenKey instance.\n * Transparently routes signing to either OpenKey (managed keys) or the user's\n * wallet (external keys).\n */\nexport class OpenKeyEIP1193Provider implements EIP1193Provider {\n private openkey: OpenKey;\n private address: string;\n private keyId: string;\n private keyType: 'MANAGED' | 'EXTERNAL';\n\n constructor(openkey: OpenKey, authResult: AuthResult) {\n this.openkey = openkey;\n this.address = authResult.address;\n this.keyId = authResult.keyId;\n this.keyType = authResult.keyType;\n }\n\n async request({ method, params }: { method: string; params?: any[] }): Promise<any> {\n switch (method) {\n case 'eth_accounts':\n case 'eth_requestAccounts':\n return [this.address];\n\n case 'eth_chainId':\n return '0x1'; // mainnet\n\n case 'personal_sign': {\n if (this.keyType === 'EXTERNAL') {\n try {\n const provider = await this.openkey.findWalletProvider(this.address);\n return await provider.request({ method, params });\n } catch (e: any) {\n showToast(e?.message || 'External wallet signing failed', 'error');\n throw e;\n }\n }\n // Managed key: route through OpenKey\n const message = this.hexToString(params![0] as string);\n const result = await this.openkey.signMessage({ message, keyId: this.keyId });\n return result.signature;\n }\n\n case 'eth_signTypedData_v4': {\n if (this.keyType === 'EXTERNAL') {\n try {\n const provider = await this.openkey.findWalletProvider(this.address);\n return await provider.request({ method, params });\n } catch (e: any) {\n showToast(e?.message || 'External wallet signing failed', 'error');\n throw e;\n }\n }\n const data = JSON.parse(params![1] as string);\n const result = await this.openkey.signTypedData(data);\n return result.signature;\n }\n\n default:\n throw new Error(`Unsupported method: ${method}`);\n }\n }\n\n private hexToString(hex: string): string {\n const bytes = new Uint8Array(\n (hex.startsWith('0x') ? hex.slice(2) : hex).match(/.{1,2}/g)!.map(b => parseInt(b, 16))\n );\n return new TextDecoder().decode(bytes);\n }\n}\n\n// Default export for convenience\nexport default OpenKey;\n\n// Create singleton instance\nlet defaultInstance: OpenKey | null = null;\n\nexport function getOpenKey(config?: OpenKeyConfig): OpenKey {\n if (!defaultInstance || config) {\n defaultInstance = new OpenKey(config);\n }\n return defaultInstance;\n}\n"],"mappings":"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","names":["index_exports","__export","OpenKey","OpenKeyEIP1193Provider","index_default","getOpenKey","__toCommonJS","DEFAULT_HOST","OAUTH_STORAGE_KEY","IframeModal","opts","isMobile","style","backdrop","e","card","handle","closeBtn","event","data","h","message","WalletPicker","title","empty","list","info","provider","item","icon","name","cancel","showToast","variant","bg","root","shadow","base64UrlEncode","buffer","generateCodeVerifier","array","generateCodeChallenge","verifier","digest","generateState","ExternalWalletError","OpenKey","config","DEFAULT_HOST","result","request","targetAddress","a","hexMessage","msg","mode","str","b","res","challenge","state","authUrl","code","stored","response","error","receivedState","url","urlObj","redirectUri","resolve","reject","left","top","redirectHost","pollInterval","timeout","popupUrl","returnedState","override","action","modeOverride","origin","readyReceived","settled","modal","settle","fn","cleanup","readyTimeout","overallTimeout","val","err","sendResult","address","challengeRes","signature","linkRes","key","handleMessage","pollClosed","OpenKeyEIP1193Provider","openkey","authResult","method","params","hex","bytes","index_default","defaultInstance","getOpenKey"]}
package/dist/index.mjs CHANGED
@@ -1,21 +1,41 @@
1
- var E="https://openkey.so";var g="openkey_oauth";function m(a){return btoa(String.fromCharCode(...a)).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}async function k(){let a=new Uint8Array(32);return crypto.getRandomValues(a),m(a)}async function v(a){let t=new TextEncoder().encode(a),r=await crypto.subtle.digest("SHA-256",t);return m(new Uint8Array(r))}function I(){let a=new Uint8Array(16);return crypto.getRandomValues(a),m(a)}var h=class{host;appName;usePopup;config;popup=null;iframe=null;lastAuth=null;discoveredProviders=[];constructor(e={}){this.config=e,this.host=e.host||E,this.appName=e.appName||window.location.hostname,this.usePopup=e.usePopup??!0,typeof window<"u"&&(this.discoveredProviders=[],window.addEventListener("eip6963:announceProvider",t=>{this.discoveredProviders.push(t.detail)}),window.dispatchEvent(new Event("eip6963:requestProvider")))}async connect(){let e=await this.openFlow("connect",{type:"openkey:auth:request",appName:this.appName});return this.lastAuth=e,e}async linkWallet(){return this.openFlow("link-wallet",{type:"openkey:link-wallet:request"})}async signMessage(e){return this.lastAuth?.keyType==="EXTERNAL"?this.signWithExternalWallet(e):this.signWithPopup(e)}async signTypedData(e){return this.lastAuth?.keyType==="EXTERNAL"?this.signTypedDataWithExternalWallet(e):this.signTypedDataWithPopup(e)}async findWalletProvider(e){if(this.config.externalProvider)return this.config.externalProvider;for(let{provider:t}of this.discoveredProviders)try{if((await t.request({method:"eth_requestAccounts"})).some(n=>n.toLowerCase()===e.toLowerCase()))return t}catch{}if(typeof window<"u"&&window.ethereum){let t=window.ethereum;try{if((await t.request({method:"eth_requestAccounts"})).some(n=>n.toLowerCase()===e.toLowerCase()))return t}catch{}return t}throw new Error("No wallet provider found for external key. Connect your wallet.")}async signWithExternalWallet(e){let t=await this.findWalletProvider(this.lastAuth.address),r=this.toHex(e.message);return{signature:await t.request({method:"personal_sign",params:[r,this.lastAuth.address]}),address:this.lastAuth.address}}async signTypedDataWithExternalWallet(e){return{signature:await(await this.findWalletProvider(this.lastAuth.address)).request({method:"eth_signTypedData_v4",params:[this.lastAuth.address,JSON.stringify(e)]}),address:this.lastAuth.address}}signWithPopup(e){return this.openFlow("sign",{type:"openkey:sign:request",message:e.message,keyId:e.keyId})}signTypedDataWithPopup(e){return this.openFlow("sign-typed-data",{type:"openkey:signTypedData:request",data:e})}toHex(e){return"0x"+Array.from(new TextEncoder().encode(e)).map(t=>t.toString(16).padStart(2,"0")).join("")}async isConnected(){try{let e=await fetch(`${this.host}/api/auth/session`,{credentials:"include"});return e.ok?!!(await e.json()).user:!1}catch{return!1}}oauth={connect:async e=>{let t=await k(),r=await v(t),n=e.state||I();sessionStorage.setItem(g,JSON.stringify({verifier:t,state:n}));let s=new URL(`${this.host}/api/auth/oauth2/authorize`);return s.searchParams.set("client_id",e.clientId),s.searchParams.set("redirect_uri",e.redirectUri),s.searchParams.set("response_type","code"),s.searchParams.set("scope","openid"),s.searchParams.set("state",n),s.searchParams.set("code_challenge",r),s.searchParams.set("code_challenge_method","S256"),this.openOAuthFlow(s.toString(),n,e.redirectUri)},exchangeCode:async(e,t)=>{let r=sessionStorage.getItem(g);if(!r)throw new Error("No PKCE verifier found. Start a new OAuth flow.");let{verifier:n}=JSON.parse(r),s=await fetch(`${this.host}/api/auth/oauth2/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",code:e,redirect_uri:t.redirectUri,client_id:t.clientId,code_verifier:n})});if(sessionStorage.removeItem(g),!s.ok){let c=await s.json();throw new Error(c.error_description||"Token exchange failed")}return s.json()},verifyState:e=>{let t=sessionStorage.getItem(g);if(!t)return!1;let{state:r}=JSON.parse(t);return r===e},parseCallback:e=>{let t=new URL(e||window.location.href),r=t.searchParams.get("error");if(r)return{error:r,errorDescription:t.searchParams.get("error_description")||void 0};let n=t.searchParams.get("code"),s=t.searchParams.get("state");return!n||!s?{error:"missing_params",errorDescription:"Missing code or state parameter"}:{code:n,state:s}}};openOAuthFlow(e,t,r){return new Promise((n,s)=>{this.usePopup?this.openOAuthPopup(e,t,r,n,s):window.location.href=e})}openOAuthPopup(e,t,r,n,s){let c=window.screenX+(window.outerWidth-400)/2,p=window.screenY+(window.outerHeight-600)/2;if(this.popup=window.open(e,"openkey-oauth",`width=400,height=600,left=${c},top=${p},popup=true`),!this.popup){s({code:"POPUP_BLOCKED",message:"Popup was blocked. Please allow popups or use redirect mode."});return}let l=new URL(r).origin,u=setInterval(()=>{try{if(this.popup?.closed){clearInterval(u),clearTimeout(d),s({code:"USER_CANCELLED",message:"User closed the popup"});return}let i=this.popup?.location?.href;if(i&&i.startsWith(l)){clearInterval(u),clearTimeout(d);let o=new URL(i),w=o.searchParams.get("code"),P=o.searchParams.get("state"),f=o.searchParams.get("error");if(this.popup?.close(),f){s({code:"UNAUTHORIZED",message:o.searchParams.get("error_description")||f});return}if(P!==t){s({code:"STATE_MISMATCH",message:"State mismatch - possible CSRF attack"});return}w?n({code:w,state:P}):s({code:"UNAUTHORIZED",message:"Authorization failed - no code returned"})}}catch{}},100),d=setTimeout(()=>{clearInterval(u),this.popup?.close(),s({code:"TIMEOUT",message:"OAuth flow timed out"})},3e5)}async openFlow(e,t){return new Promise((r,n)=>{let s=`${this.host}/widget/${e}?origin=${encodeURIComponent(window.location.origin)}`;this.usePopup?this.openPopup(s,t,r,n):this.openIframe(s,t,r,n)})}openPopup(e,t,r,n){let s=window.screenX+(window.outerWidth-400)/2,c=window.screenY+(window.outerHeight-600)/2;if(this.popup=window.open(e,"openkey",`width=400,height=600,left=${s},top=${c},popup=true`),!this.popup){n({code:"POPUP_BLOCKED",message:"Popup was blocked. Please allow popups for this site."});return}let p=()=>{window.removeEventListener("message",d),clearInterval(l),clearTimeout(u)},l=setInterval(()=>{this.popup?.closed&&(p(),n({code:"USER_CANCELLED",message:"User closed the popup"}))},500),u=setTimeout(()=>{p(),this.popup?.close(),n({code:"TIMEOUT",message:"Request timed out"})},3e5),d=i=>{if(i.origin!==this.host)return;let o=i.data;if(o.type==="openkey:ready"){this.popup?.postMessage(t,this.host);return}if(o.type==="openkey:close"){p(),this.popup?.close(),n({code:"USER_CANCELLED",message:"User cancelled the request"});return}(o.type==="openkey:auth:response"||o.type==="openkey:sign:response"||o.type==="openkey:signTypedData:response"||o.type==="openkey:link-wallet:response")&&(p(),this.popup?.close(),o.success?o.type==="openkey:auth:response"?r({address:o.address,keyId:o.keyId,keyType:o.keyType||"MANAGED"}):o.type==="openkey:link-wallet:response"?r({address:o.address,keyId:o.keyId}):r({signature:o.signature,address:o.address}):n(o.error))};window.addEventListener("message",d)}openIframe(e,t,r,n){let s=document.createElement("div");s.style.cssText=`
2
- position: fixed;
3
- inset: 0;
4
- background: rgba(0, 0, 0, 0.5);
5
- display: flex;
6
- align-items: center;
7
- justify-content: center;
8
- z-index: 999999;
9
- `;let c=document.createElement("div");c.style.cssText=`
10
- width: 400px;
11
- height: 600px;
12
- background: #1a1a1a;
13
- border-radius: 12px;
14
- overflow: hidden;
15
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
16
- `,this.iframe=document.createElement("iframe"),this.iframe.src=e,this.iframe.style.cssText=`
17
- width: 100%;
18
- height: 100%;
19
- border: none;
20
- `,c.appendChild(this.iframe),s.appendChild(c),document.body.appendChild(s);let p=()=>{window.removeEventListener("message",u),clearTimeout(l),s.remove(),this.iframe=null};s.addEventListener("click",d=>{d.target===s&&(p(),n({code:"USER_CANCELLED",message:"User cancelled the request"}))});let l=setTimeout(()=>{p(),n({code:"TIMEOUT",message:"Request timed out"})},3e5),u=d=>{if(d.origin!==this.host)return;let i=d.data;if(i.type==="openkey:ready"){this.iframe?.contentWindow?.postMessage(t,this.host);return}if(i.type==="openkey:close"){p(),n({code:"USER_CANCELLED",message:"User cancelled the request"});return}(i.type==="openkey:auth:response"||i.type==="openkey:sign:response"||i.type==="openkey:signTypedData:response"||i.type==="openkey:link-wallet:response")&&(p(),i.success?i.type==="openkey:auth:response"?r({address:i.address,keyId:i.keyId,keyType:i.keyType||"MANAGED"}):i.type==="openkey:link-wallet:response"?r({address:i.address,keyId:i.keyId}):r({signature:i.signature,address:i.address}):n(i.error))};window.addEventListener("message",u)}disconnect(){this.popup?.close(),this.popup=null,this.iframe?.parentElement?.parentElement?.remove(),this.iframe=null}},T=class{openkey;address;keyId;keyType;constructor(e,t){this.openkey=e,this.address=t.address,this.keyId=t.keyId,this.keyType=t.keyType}async request({method:e,params:t}){switch(e){case"eth_accounts":case"eth_requestAccounts":return[this.address];case"eth_chainId":return"0x1";case"personal_sign":{if(this.keyType==="EXTERNAL")return(await this.openkey.findWalletProvider(this.address)).request({method:e,params:t});let r=this.hexToString(t[0]);return(await this.openkey.signMessage({message:r,keyId:this.keyId})).signature}case"eth_signTypedData_v4":{if(this.keyType==="EXTERNAL")return(await this.openkey.findWalletProvider(this.address)).request({method:e,params:t});let r=JSON.parse(t[1]);return(await this.openkey.signTypedData(r)).signature}default:throw new Error(`Unsupported method: ${e}`)}}hexToString(e){let t=new Uint8Array((e.startsWith("0x")?e.slice(2):e).match(/.{1,2}/g).map(r=>parseInt(r,16)));return new TextDecoder().decode(t)}},A=h,y=null;function O(a){return(!y||a)&&(y=new h(a)),y}export{h as OpenKey,T as OpenKeyEIP1193Provider,A as default,O as getOpenKey};
1
+ var b="https://openkey.so";var f="openkey_oauth",E=class{root;shadow;iframe;onClose;onMessage;messageHandler;host;constructor(e){this.host=e.host,this.onClose=e.onClose,this.onMessage=e.onMessage;let t=window.matchMedia("(max-width: 639px)").matches;this.root=document.createElement("div"),this.shadow=this.root.attachShadow({mode:"closed"});let s=document.createElement("style");s.textContent=`
2
+ :host{all:initial}
3
+ .ok-backdrop{position:fixed;inset:0;background:rgba(0,0,0,0.4);backdrop-filter:blur(4px);z-index:999999;display:flex;align-items:${t?"flex-end":"center"};justify-content:center;animation:ok-fade-in 150ms ease-out}
4
+ .ok-card{position:relative;background:#fafafa;width:${t?"100%":"400px"};border-radius:${t?"16px 16px 0 0":"16px"};box-shadow:0 25px 50px -12px rgba(0,0,0,0.25);overflow:hidden;overflow-y:auto;animation:${t?"ok-slide-up 250ms ease-out":"ok-scale-in 200ms ease-out"};max-height:90vh}
5
+ .ok-close{position:absolute;top:8px;right:8px;width:24px;height:24px;border:none;background:transparent;color:#6b7280;font-size:18px;cursor:pointer;z-index:1;display:flex;align-items:center;justify-content:center;border-radius:4px}
6
+ .ok-close:hover{color:#111827}
7
+ .ok-handle{width:40px;height:4px;background:#d4d4d4;border-radius:2px;margin:8px auto 0}
8
+ iframe{border:none;width:100%;height:400px;display:block;transition:height 200ms ease}
9
+ .ok-backdrop.ok-exit{animation:ok-fade-out 150ms ease-in}
10
+ .ok-backdrop.ok-exit .ok-card{animation:${t?"ok-slide-down 200ms ease-in":"ok-scale-out 150ms ease-in"}}
11
+ @keyframes ok-fade-in{from{opacity:0}to{opacity:1}}
12
+ @keyframes ok-fade-out{from{opacity:1}to{opacity:0}}
13
+ @keyframes ok-scale-in{from{opacity:0;transform:scale(0.95)}to{opacity:1;transform:scale(1)}}
14
+ @keyframes ok-scale-out{from{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(0.95)}}
15
+ @keyframes ok-slide-up{from{transform:translateY(100%)}to{transform:translateY(0)}}
16
+ @keyframes ok-slide-down{from{transform:translateY(0)}to{transform:translateY(100%)}}
17
+ `;let o=document.createElement("div");o.className="ok-backdrop",o.addEventListener("click",n=>{n.target===o&&this.close()});let r=document.createElement("div");if(r.className="ok-card",t){let n=document.createElement("div");n.className="ok-handle",r.appendChild(n)}let i=document.createElement("button");i.className="ok-close",i.textContent="\xD7",i.addEventListener("click",()=>this.close()),r.appendChild(i),this.iframe=document.createElement("iframe"),this.iframe.src=e.url,this.iframe.setAttribute("allow","publickey-credentials-get *; publickey-credentials-create *"),this.iframe.setAttribute("sandbox","allow-scripts allow-same-origin allow-forms allow-popups"),r.appendChild(this.iframe),o.appendChild(r),this.shadow.appendChild(s),this.shadow.appendChild(o),this.messageHandler=n=>{if(n.origin!==this.host)return;let c=n.data;if(c.type==="openkey:resize"){let l=Math.min(c.height,Math.floor(window.innerHeight*.85));this.iframe.style.height=`${l}px`;return}this.onMessage(c)},window.addEventListener("message",this.messageHandler),document.body.appendChild(this.root)}postMessage(e){this.iframe.contentWindow?.postMessage(e,this.host)}destroy(){window.removeEventListener("message",this.messageHandler),this.root.remove()}close(){let e=this.shadow.querySelector(".ok-backdrop");e?(e.classList.add("ok-exit"),setTimeout(()=>{this.destroy(),this.onClose()},200)):(this.destroy(),this.onClose())}},v=class{root;shadow;constructor(e){this.root=document.createElement("div"),this.shadow=this.root.attachShadow({mode:"closed"});let t=document.createElement("style");t.textContent=`
18
+ :host{all:initial}
19
+ .wp-backdrop{position:fixed;inset:0;background:rgba(0,0,0,0.4);backdrop-filter:blur(4px);z-index:1000000;display:flex;align-items:center;justify-content:center;animation:wp-fade 150ms ease-out}
20
+ .wp-card{background:#fff;width:340px;border-radius:16px;box-shadow:0 25px 50px -12px rgba(0,0,0,0.25);padding:20px;animation:wp-scale 200ms ease-out}
21
+ .wp-title{font:600 16px/1.3 system-ui,sans-serif;color:#111;margin:0 0 12px}
22
+ .wp-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}
23
+ .wp-item{display:flex;align-items:center;gap:10px;padding:10px 12px;border:1px solid #e5e7eb;border-radius:10px;background:transparent;cursor:pointer;font:14px/1.3 system-ui,sans-serif;color:#111;width:100%;text-align:left;transition:border-color 150ms}
24
+ .wp-item:hover{border-color:#6366f1}
25
+ .wp-icon{width:28px;height:28px;border-radius:6px}
26
+ .wp-empty{font:14px/1.4 system-ui,sans-serif;color:#6b7280;text-align:center;padding:16px 0}
27
+ .wp-cancel{display:block;width:100%;margin-top:12px;padding:8px;border:none;background:transparent;color:#6b7280;font:14px/1.3 system-ui,sans-serif;cursor:pointer;border-radius:8px}
28
+ .wp-cancel:hover{background:#f3f4f6;color:#111}
29
+ @keyframes wp-fade{from{opacity:0}to{opacity:1}}
30
+ @keyframes wp-scale{from{opacity:0;transform:scale(0.95)}to{opacity:1;transform:scale(1)}}
31
+ @media(prefers-color-scheme:dark){
32
+ .wp-card{background:#1a1a1a;box-shadow:0 25px 50px -12px rgba(0,0,0,0.5)}
33
+ .wp-title{color:#f3f4f6}
34
+ .wp-item{border-color:#374151;color:#f3f4f6}
35
+ .wp-item:hover{border-color:#818cf8}
36
+ .wp-empty{color:#9ca3af}
37
+ .wp-cancel{color:#9ca3af}
38
+ .wp-cancel:hover{background:#262626;color:#f3f4f6}
39
+ }
40
+ `;let s=document.createElement("div");s.className="wp-backdrop",s.addEventListener("click",n=>{n.target===s&&(this.destroy(),e.onCancel())});let o=document.createElement("div");o.className="wp-card";let r=document.createElement("h2");if(r.className="wp-title",r.textContent="Select a wallet",o.appendChild(r),e.providers.length===0){let n=document.createElement("div");n.className="wp-empty",n.textContent="No wallets detected",o.appendChild(n)}else{let n=document.createElement("ul");n.className="wp-list";for(let{info:c,provider:l}of e.providers){let p=document.createElement("button");if(p.className="wp-item",c.icon){let a=document.createElement("img");a.className="wp-icon",a.src=c.icon,a.alt=c.name,p.appendChild(a)}let h=document.createElement("span");h.textContent=c.name,p.appendChild(h),p.addEventListener("click",()=>{this.destroy(),e.onSelect(l)}),n.appendChild(p)}o.appendChild(n)}let i=document.createElement("button");i.className="wp-cancel",i.textContent="Cancel",i.addEventListener("click",()=>{this.destroy(),e.onCancel()}),o.appendChild(i),s.appendChild(o),this.shadow.appendChild(t),this.shadow.appendChild(s),document.body.appendChild(this.root)}destroy(){this.root.remove()}};function g(u="Opening in new window\u2026",e="info"){let t=e==="error"?"#dc2626":"#1f2937",s=document.createElement("div"),o=s.attachShadow({mode:"closed"});o.innerHTML=`<style>:host{all:initial}.ok-toast{position:fixed;top:16px;right:16px;z-index:1000000;background:${t};color:#f9fafb;padding:10px 16px;border-radius:8px;font:14px/1.4 system-ui,sans-serif;box-shadow:0 4px 12px rgba(0,0,0,0.15);animation:ok-t-in 200ms ease-out;max-width:360px}@keyframes ok-t-in{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}</style><div class="ok-toast">${u}</div>`,document.body.appendChild(s),setTimeout(()=>s.remove(),4e3)}function x(u){return btoa(String.fromCharCode(...u)).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}async function O(){let u=new Uint8Array(32);return crypto.getRandomValues(u),x(u)}async function I(u){let t=new TextEncoder().encode(u),s=await crypto.subtle.digest("SHA-256",t);return x(new Uint8Array(s))}function A(){let u=new Uint8Array(16);return crypto.getRandomValues(u),x(u)}var T=class extends Error{constructor(e){super(e),this.name="ExternalWalletError"}},w=class{host;appName;mode;config;popup=null;lastAuth=null;discoveredProviders=[];constructor(e={}){this.config=e,this.host=e.host||b,this.appName=e.appName||window.location.hostname,this.mode=e.mode??"iframe",typeof window<"u"&&(this.discoveredProviders=[],window.addEventListener("eip6963:announceProvider",t=>{this.discoveredProviders.push(t.detail)}),window.dispatchEvent(new Event("eip6963:requestProvider")))}async connect(e){let t=await this.openFlow("connect",{type:"openkey:auth:request",appName:this.appName},e?.mode);return this.lastAuth=t,t}async linkWallet(e){return this.openFlow("link-wallet",{type:"openkey:link-wallet:request"},e?.mode)}async signMessage(e,t){return this.lastAuth?.keyType==="EXTERNAL"?this.signWithExternalWallet(e):this.signWithOpenKey(e,t?.mode)}async signTypedData(e,t){return this.lastAuth?.keyType==="EXTERNAL"?this.signTypedDataWithExternalWallet(e):this.signTypedDataWithOpenKey(e,t?.mode)}async findWalletProvider(e){if(this.config.externalProvider)return this.config.externalProvider;for(let{provider:t}of this.discoveredProviders)try{if((await t.request({method:"eth_accounts"})).some(o=>o.toLowerCase()===e.toLowerCase()))return t}catch{}if(typeof window<"u"&&window.ethereum){let t=window.ethereum;try{if((await t.request({method:"eth_accounts"})).some(o=>o.toLowerCase()===e.toLowerCase()))return t}catch{}}throw new T("External wallet not found. Connect the wallet that owns this key and try again.")}async signWithExternalWallet(e){let t;try{t=await this.findWalletProvider(this.lastAuth.address)}catch(s){throw g(s.message,"error"),s}try{let s=this.toHex(e.message);return{signature:await t.request({method:"personal_sign",params:[s,this.lastAuth.address]}),address:this.lastAuth.address}}catch(s){let o=s?.message?.includes("not been authorized")?"Wallet rejected the request. Approve the connection in your wallet and try again.":s?.message||"Signing failed";throw g(o,"error"),new Error(o)}}async signTypedDataWithExternalWallet(e){let t;try{t=await this.findWalletProvider(this.lastAuth.address)}catch(s){throw g(s.message,"error"),s}try{return{signature:await t.request({method:"eth_signTypedData_v4",params:[this.lastAuth.address,JSON.stringify(e)]}),address:this.lastAuth.address}}catch(s){let o=s?.message?.includes("not been authorized")?"Wallet rejected the request. Approve the connection in your wallet and try again.":s?.message||"Signing failed";throw g(o,"error"),new Error(o)}}signWithOpenKey(e,t){return this.openFlow("sign",{type:"openkey:sign:request",message:e.message,keyId:e.keyId},t)}signTypedDataWithOpenKey(e,t){return this.openFlow("sign-typed-data",{type:"openkey:signTypedData:request",data:e},t)}toHex(e){return"0x"+Array.from(new TextEncoder().encode(e)).map(t=>t.toString(16).padStart(2,"0")).join("")}async isConnected(){try{let e=await fetch(`${this.host}/api/auth/session`,{credentials:"include"});return e.ok?!!(await e.json()).user:!1}catch{return!1}}oauth={connect:async e=>{let t=await O(),s=await I(t),o=e.state||A();sessionStorage.setItem(f,JSON.stringify({verifier:t,state:o}));let r=new URL(`${this.host}/api/auth/oauth2/authorize`);return r.searchParams.set("client_id",e.clientId),r.searchParams.set("redirect_uri",e.redirectUri),r.searchParams.set("response_type","code"),r.searchParams.set("scope","openid"),r.searchParams.set("state",o),r.searchParams.set("code_challenge",s),r.searchParams.set("code_challenge_method","S256"),this.openOAuthFlow(r.toString(),o,e.redirectUri)},exchangeCode:async(e,t)=>{let s=sessionStorage.getItem(f);if(!s)throw new Error("No PKCE verifier found. Start a new OAuth flow.");let{verifier:o}=JSON.parse(s),r=await fetch(`${this.host}/api/auth/oauth2/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",code:e,redirect_uri:t.redirectUri,client_id:t.clientId,code_verifier:o})});if(sessionStorage.removeItem(f),!r.ok){let i=await r.json();throw new Error(i.error_description||"Token exchange failed")}return r.json()},verifyState:e=>{let t=sessionStorage.getItem(f);if(!t)return!1;let{state:s}=JSON.parse(t);return s===e},parseCallback:e=>{let t=new URL(e||window.location.href),s=t.searchParams.get("error");if(s)return{error:s,errorDescription:t.searchParams.get("error_description")||void 0};let o=t.searchParams.get("code"),r=t.searchParams.get("state");return!o||!r?{error:"missing_params",errorDescription:"Missing code or state parameter"}:{code:o,state:r}}};openOAuthFlow(e,t,s){return new Promise((o,r)=>{this.mode!=="redirect"?this.openOAuthPopup(e,t,s,o,r):window.location.href=e})}openOAuthPopup(e,t,s,o,r){let i=window.screenX+(window.outerWidth-400)/2,n=window.screenY+(window.outerHeight-600)/2;if(this.popup=window.open(e,"openkey-oauth",`width=400,height=600,left=${i},top=${n},popup=true`),!this.popup){r({code:"POPUP_BLOCKED",message:"Popup was blocked. Please allow popups or use redirect mode."});return}let c=new URL(s).origin,l=setInterval(()=>{try{if(this.popup?.closed){clearInterval(l),clearTimeout(p),r({code:"USER_CANCELLED",message:"User closed the popup"});return}let h=this.popup?.location?.href;if(h&&h.startsWith(c)){clearInterval(l),clearTimeout(p);let a=new URL(h),m=a.searchParams.get("code"),d=a.searchParams.get("state"),y=a.searchParams.get("error");if(this.popup?.close(),y){r({code:"UNAUTHORIZED",message:a.searchParams.get("error_description")||y});return}if(d!==t){r({code:"STATE_MISMATCH",message:"State mismatch - possible CSRF attack"});return}m?o({code:m,state:d}):r({code:"UNAUTHORIZED",message:"Authorization failed - no code returned"})}}catch{}},100),p=setTimeout(()=>{clearInterval(l),this.popup?.close(),r({code:"TIMEOUT",message:"OAuth flow timed out"})},3e5)}resolveMode(e){return e??this.mode}async openFlow(e,t,s){let o=this.resolveMode(s),r=encodeURIComponent(window.location.origin);if(o==="popup"){let n=`${this.host}/widget/${e}?origin=${r}`;return new Promise((c,l)=>this.openPopup(n,t,c,l))}if(o==="redirect"){let n=`${this.host}/widget/${e}?origin=${r}`;return window.location.href=n,new Promise(()=>{})}let i=`${this.host}/widget/embed/${e}?origin=${r}`;return this.openIframeModal(i,e,t,r)}openIframeModal(e,t,s,o){return new Promise((r,i)=>{let n=!1,c=!1,l=null,p=d=>{c||(c=!0,d())},h=()=>{clearTimeout(a),clearTimeout(m),l?.destroy(),l=null};l=new E({url:e,host:this.host,onClose:()=>{p(()=>{clearTimeout(a),clearTimeout(m),l=null,i({code:"USER_CANCELLED",message:"User cancelled the request"})})},onMessage:d=>{if(d.type==="openkey:ready"){n=!0,l?.postMessage(s);return}if(d.type==="openkey:close"){p(()=>{h(),i({code:"USER_CANCELLED",message:"User cancelled the request"})});return}if(d.type==="openkey:link-wallet:delegate"){this.handleWalletLinkDelegation(l);return}(d.type==="openkey:auth:response"||d.type==="openkey:sign:response"||d.type==="openkey:signTypedData:response"||d.type==="openkey:link-wallet:response")&&p(()=>{h(),d.success?d.type==="openkey:auth:response"?r({address:d.address,keyId:d.keyId,keyType:d.keyType||"MANAGED"}):d.type==="openkey:link-wallet:response"?r({address:d.address,keyId:d.keyId}):r({signature:d.signature,address:d.address}):i(d.error)})}});let a=setTimeout(()=>{if(n||c)return;h(),console.warn("OpenKey: iframe blocked by CSP, falling back to popup. Add frame-src https://openkey.so to your CSP."),g();let d=`${this.host}/widget/${t}?origin=${o}`;this.openPopup(d,s,y=>p(()=>r(y)),y=>p(()=>i(y)))},3e3),m=setTimeout(()=>{p(()=>{h(),i({code:"TIMEOUT",message:"Request timed out"})})},3e5)})}handleWalletLinkDelegation(e){let t=s=>e.postMessage(s);new v({providers:this.discoveredProviders,onCancel:()=>{t({type:"openkey:link-wallet:result",success:!1,error:{code:"USER_CANCELLED",message:"Wallet linking cancelled"}})},onSelect:async s=>{try{let r=(await s.request({method:"eth_requestAccounts"}))[0];if(!r)throw new Error("No account returned");let i=await fetch(`${this.host}/api/keys/link/challenge`,{method:"POST",credentials:"include"});if(!i.ok)throw new Error("Failed to get challenge");let{message:n}=await i.json(),c=this.toHex(n),l=await s.request({method:"personal_sign",params:[c,r]}),p=await fetch(`${this.host}/api/keys/link`,{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:JSON.stringify({address:r,signature:l,message:n})});if(!p.ok){let a=await p.json();throw new Error(a.error||"Failed to link wallet")}let{key:h}=await p.json();t({type:"openkey:link-wallet:result",success:!0,address:h.address,keyId:h.id})}catch(o){t({type:"openkey:link-wallet:result",success:!1,error:{code:"UNKNOWN",message:o.message||"Wallet linking failed"}})}}})}openPopup(e,t,s,o){let r=window.screenX+(window.outerWidth-400)/2,i=window.screenY+(window.outerHeight-600)/2;if(this.popup=window.open(e,"openkey",`width=400,height=600,left=${r},top=${i},popup=true`),!this.popup){o({code:"POPUP_BLOCKED",message:"Popup was blocked. Please allow popups for this site."});return}let n=()=>{window.removeEventListener("message",p),clearInterval(c),clearTimeout(l)},c=setInterval(()=>{this.popup?.closed&&(n(),o({code:"USER_CANCELLED",message:"User closed the popup"}))},500),l=setTimeout(()=>{n(),this.popup?.close(),o({code:"TIMEOUT",message:"Request timed out"})},3e5),p=h=>{if(h.origin!==this.host)return;let a=h.data;if(a.type==="openkey:ready"){this.popup?.postMessage(t,this.host);return}if(a.type==="openkey:close"){n(),this.popup?.close(),o({code:"USER_CANCELLED",message:"User cancelled the request"});return}(a.type==="openkey:auth:response"||a.type==="openkey:sign:response"||a.type==="openkey:signTypedData:response"||a.type==="openkey:link-wallet:response")&&(n(),this.popup?.close(),a.success?a.type==="openkey:auth:response"?s({address:a.address,keyId:a.keyId,keyType:a.keyType||"MANAGED"}):a.type==="openkey:link-wallet:response"?s({address:a.address,keyId:a.keyId}):s({signature:a.signature,address:a.address}):o(a.error))};window.addEventListener("message",p)}disconnect(){this.popup?.close(),this.popup=null}},P=class{openkey;address;keyId;keyType;constructor(e,t){this.openkey=e,this.address=t.address,this.keyId=t.keyId,this.keyType=t.keyType}async request({method:e,params:t}){switch(e){case"eth_accounts":case"eth_requestAccounts":return[this.address];case"eth_chainId":return"0x1";case"personal_sign":{if(this.keyType==="EXTERNAL")try{return await(await this.openkey.findWalletProvider(this.address)).request({method:e,params:t})}catch(r){throw g(r?.message||"External wallet signing failed","error"),r}let s=this.hexToString(t[0]);return(await this.openkey.signMessage({message:s,keyId:this.keyId})).signature}case"eth_signTypedData_v4":{if(this.keyType==="EXTERNAL")try{return await(await this.openkey.findWalletProvider(this.address)).request({method:e,params:t})}catch(r){throw g(r?.message||"External wallet signing failed","error"),r}let s=JSON.parse(t[1]);return(await this.openkey.signTypedData(s)).signature}default:throw new Error(`Unsupported method: ${e}`)}}hexToString(e){let t=new Uint8Array((e.startsWith("0x")?e.slice(2):e).match(/.{1,2}/g).map(s=>parseInt(s,16)));return new TextDecoder().decode(t)}},C=w,k=null;function M(u){return(!k||u)&&(k=new w(u)),k}export{w as OpenKey,P as OpenKeyEIP1193Provider,C as default,M as getOpenKey};
21
41
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// OpenKey SDK - Browser client for third-party apps\n// Provides \"Sign with OpenKey\" functionality via popup or iframe\n\nexport interface EIP1193Provider {\n request(args: { method: string; params?: any[] }): Promise<any>;\n}\n\ninterface EIP6963ProviderInfo {\n uuid: string;\n name: string;\n icon: string;\n rdns: string;\n}\n\ninterface EIP6963ProviderDetail {\n info: EIP6963ProviderInfo;\n provider: EIP1193Provider;\n}\n\nexport interface OpenKeyConfig {\n /** OpenKey host URL (default: https://openkey.so) */\n host?: string;\n /** App identifier for display */\n appName?: string;\n /** Use popup instead of iframe (default: true) */\n usePopup?: boolean;\n /** App-provided wallet provider for external key signing */\n externalProvider?: EIP1193Provider;\n}\n\nexport interface SignMessageRequest {\n message: string;\n keyId?: string; // If not provided, user selects\n}\n\nexport interface SignTypedDataRequest {\n domain: {\n name?: string;\n version?: string;\n chainId?: number;\n verifyingContract?: string;\n };\n types: Record<string, Array<{ name: string; type: string }>>;\n primaryType: string;\n message: Record<string, unknown>;\n keyId?: string;\n}\n\nexport interface SignResult {\n signature: string;\n address: string;\n}\n\nexport interface AuthResult {\n address: string;\n keyId: string;\n keyType: 'MANAGED' | 'EXTERNAL';\n}\n\nexport interface OpenKeyError {\n code: 'USER_CANCELLED' | 'POPUP_BLOCKED' | 'TIMEOUT' | 'NO_KEY' | 'UNAUTHORIZED' | 'UNKNOWN' | 'STATE_MISMATCH';\n message: string;\n}\n\n// ======= OAuth 2.1 Types =======\n\n/** OAuth configuration for third-party apps */\nexport interface OAuthConfig {\n /** OAuth client_id (registered with OpenKey) */\n clientId: string;\n /** Redirect URI (must match registered URI) */\n redirectUri: string;\n /** State parameter for CSRF protection (auto-generated if not provided) */\n state?: string;\n}\n\n/** Result from OAuth authorization */\nexport interface OAuthResult {\n /** Authorization code (exchange for tokens) */\n code: string;\n /** State parameter (verify matches request) */\n state: string;\n}\n\n/** Response from token exchange */\nexport interface OAuthTokenResponse {\n access_token: string;\n token_type: 'Bearer';\n expires_in: number;\n id_token: string;\n scope: string;\n refresh_token?: string;\n}\n\ntype MessageType =\n | { type: 'openkey:auth:request'; appName: string }\n | { type: 'openkey:auth:response'; success: true; address: string; keyId: string; keyType?: 'MANAGED' | 'EXTERNAL' }\n | { type: 'openkey:auth:response'; success: false; error: OpenKeyError }\n | { type: 'openkey:sign:request'; message: string; keyId?: string }\n | { type: 'openkey:sign:response'; success: true; signature: string; address: string }\n | { type: 'openkey:sign:response'; success: false; error: OpenKeyError }\n | { type: 'openkey:signTypedData:request'; data: SignTypedDataRequest }\n | { type: 'openkey:signTypedData:response'; success: true; signature: string; address: string }\n | { type: 'openkey:signTypedData:response'; success: false; error: OpenKeyError }\n | { type: 'openkey:link-wallet:request' }\n | { type: 'openkey:link-wallet:response'; success: true; address: string; keyId: string }\n | { type: 'openkey:link-wallet:response'; success: false; error: OpenKeyError }\n | { type: 'openkey:ready' }\n | { type: 'openkey:close' };\n\nconst DEFAULT_HOST = 'https://openkey.so';\nconst POPUP_WIDTH = 400;\nconst POPUP_HEIGHT = 600;\nconst DEFAULT_TIMEOUT = 5 * 60 * 1000; // 5 minutes\nconst OAUTH_STORAGE_KEY = 'openkey_oauth';\n\n// ======= PKCE Utilities =======\n\nfunction base64UrlEncode(buffer: Uint8Array): string {\n const base64 = btoa(String.fromCharCode(...buffer));\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=/g, '');\n}\n\nasync function generateCodeVerifier(): Promise<string> {\n const array = new Uint8Array(32);\n crypto.getRandomValues(array);\n return base64UrlEncode(array);\n}\n\nasync function generateCodeChallenge(verifier: string): Promise<string> {\n const encoder = new TextEncoder();\n const data = encoder.encode(verifier);\n const digest = await crypto.subtle.digest('SHA-256', data);\n return base64UrlEncode(new Uint8Array(digest));\n}\n\nfunction generateState(): string {\n const array = new Uint8Array(16);\n crypto.getRandomValues(array);\n return base64UrlEncode(array);\n}\n\nexport class OpenKey {\n private host: string;\n private appName: string;\n private usePopup: boolean;\n private config: OpenKeyConfig;\n private popup: Window | null = null;\n private iframe: HTMLIFrameElement | null = null;\n private lastAuth: AuthResult | null = null;\n private discoveredProviders: EIP6963ProviderDetail[] = [];\n\n constructor(config: OpenKeyConfig = {}) {\n this.config = config;\n this.host = config.host || DEFAULT_HOST;\n this.appName = config.appName || window.location.hostname;\n this.usePopup = config.usePopup ?? true;\n\n // Listen for EIP-6963 wallet announcements\n if (typeof window !== 'undefined') {\n this.discoveredProviders = [];\n window.addEventListener('eip6963:announceProvider', (event: any) => {\n this.discoveredProviders.push(event.detail);\n });\n window.dispatchEvent(new Event('eip6963:requestProvider'));\n }\n }\n\n /**\n * Connect to OpenKey and get user's wallet address\n * Opens auth flow for user to select/create a key\n */\n async connect(): Promise<AuthResult> {\n const result = await this.openFlow<AuthResult>('connect', {\n type: 'openkey:auth:request',\n appName: this.appName,\n });\n this.lastAuth = result;\n return result;\n }\n\n /**\n * Link an external wallet to the user's OpenKey account\n * Opens link-wallet widget flow\n */\n async linkWallet(): Promise<{ address: string; keyId: string }> {\n return this.openFlow<{ address: string; keyId: string }>('link-wallet', {\n type: 'openkey:link-wallet:request',\n });\n }\n\n /**\n * Sign a message with the user's OpenKey wallet\n * For external keys, routes directly to the user's wallet provider\n */\n async signMessage(request: SignMessageRequest): Promise<SignResult> {\n if (this.lastAuth?.keyType === 'EXTERNAL') {\n return this.signWithExternalWallet(request);\n }\n return this.signWithPopup(request);\n }\n\n /**\n * Sign typed data (EIP-712) with the user's OpenKey wallet\n * For external keys, routes directly to the user's wallet provider\n */\n async signTypedData(request: SignTypedDataRequest): Promise<SignResult> {\n if (this.lastAuth?.keyType === 'EXTERNAL') {\n return this.signTypedDataWithExternalWallet(request);\n }\n return this.signTypedDataWithPopup(request);\n }\n\n /**\n * Find a wallet provider that controls the given address.\n * Checks: 1) app-provided externalProvider, 2) EIP-6963 discovered wallets, 3) window.ethereum\n */\n async findWalletProvider(targetAddress: string): Promise<EIP1193Provider> {\n // 1. Try app-provided externalProvider first\n if (this.config.externalProvider) {\n return this.config.externalProvider;\n }\n\n // 2. Use EIP-6963 to discover wallets, requesting authorization\n for (const { provider } of this.discoveredProviders) {\n try {\n const accounts = await provider.request({ method: 'eth_requestAccounts' }) as string[];\n if (accounts.some(a => a.toLowerCase() === targetAddress.toLowerCase())) {\n return provider;\n }\n } catch {}\n }\n\n // 3. Fall back to window.ethereum, requesting authorization\n if (typeof window !== 'undefined' && (window as any).ethereum) {\n const provider = (window as any).ethereum as EIP1193Provider;\n try {\n const accounts = await provider.request({ method: 'eth_requestAccounts' }) as string[];\n if (accounts.some(a => a.toLowerCase() === targetAddress.toLowerCase())) {\n return provider;\n }\n } catch {}\n return provider;\n }\n\n throw new Error('No wallet provider found for external key. Connect your wallet.');\n }\n\n private async signWithExternalWallet(request: SignMessageRequest): Promise<SignResult> {\n const provider = await this.findWalletProvider(this.lastAuth!.address);\n const hexMessage = this.toHex(request.message);\n const signature = await provider.request({\n method: 'personal_sign',\n params: [hexMessage, this.lastAuth!.address],\n });\n return { signature: signature as string, address: this.lastAuth!.address };\n }\n\n private async signTypedDataWithExternalWallet(request: SignTypedDataRequest): Promise<SignResult> {\n const provider = await this.findWalletProvider(this.lastAuth!.address);\n const signature = await provider.request({\n method: 'eth_signTypedData_v4',\n params: [this.lastAuth!.address, JSON.stringify(request)],\n });\n return { signature: signature as string, address: this.lastAuth!.address };\n }\n\n private signWithPopup(request: SignMessageRequest): Promise<SignResult> {\n return this.openFlow<SignResult>('sign', {\n type: 'openkey:sign:request',\n message: request.message,\n keyId: request.keyId,\n });\n }\n\n private signTypedDataWithPopup(request: SignTypedDataRequest): Promise<SignResult> {\n return this.openFlow<SignResult>('sign-typed-data', {\n type: 'openkey:signTypedData:request',\n data: request,\n });\n }\n\n private toHex(str: string): string {\n return '0x' + Array.from(new TextEncoder().encode(str)).map(b => b.toString(16).padStart(2, '0')).join('');\n }\n\n /**\n * Check if user is already authenticated\n */\n async isConnected(): Promise<boolean> {\n // Check for existing session via API\n try {\n const res = await fetch(`${this.host}/api/auth/session`, {\n credentials: 'include',\n });\n if (!res.ok) return false;\n const data = await res.json();\n return !!data.user;\n } catch {\n return false;\n }\n }\n\n // ======= OAuth 2.1 Provider Methods =======\n\n /**\n * OAuth 2.1 methods for third-party app authentication\n * Use this when your app has registered OAuth client credentials with OpenKey\n */\n oauth = {\n /**\n * Start OAuth authorization flow\n * Opens popup/redirect to OpenKey authorization endpoint\n * @returns Promise that resolves with authorization code after user consent\n */\n connect: async (config: OAuthConfig): Promise<OAuthResult> => {\n // Generate PKCE values\n const verifier = await generateCodeVerifier();\n const challenge = await generateCodeChallenge(verifier);\n const state = config.state || generateState();\n\n // Store for later token exchange\n sessionStorage.setItem(\n OAUTH_STORAGE_KEY,\n JSON.stringify({ verifier, state })\n );\n\n // Build authorization URL\n const authUrl = new URL(`${this.host}/api/auth/oauth2/authorize`);\n authUrl.searchParams.set('client_id', config.clientId);\n authUrl.searchParams.set('redirect_uri', config.redirectUri);\n authUrl.searchParams.set('response_type', 'code');\n authUrl.searchParams.set('scope', 'openid');\n authUrl.searchParams.set('state', state);\n authUrl.searchParams.set('code_challenge', challenge);\n authUrl.searchParams.set('code_challenge_method', 'S256');\n\n return this.openOAuthFlow(authUrl.toString(), state, config.redirectUri);\n },\n\n /**\n * Exchange authorization code for tokens\n * Call this after receiving the callback with authorization code\n * @param code - Authorization code from callback URL\n * @param config - Same config used for connect()\n * @returns Promise that resolves with access_token, id_token, etc.\n */\n exchangeCode: async (\n code: string,\n config: OAuthConfig\n ): Promise<OAuthTokenResponse> => {\n const stored = sessionStorage.getItem(OAUTH_STORAGE_KEY);\n if (!stored) {\n throw new Error('No PKCE verifier found. Start a new OAuth flow.');\n }\n\n const { verifier } = JSON.parse(stored);\n\n const response = await fetch(`${this.host}/api/auth/oauth2/token`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n code,\n redirect_uri: config.redirectUri,\n client_id: config.clientId,\n code_verifier: verifier,\n }),\n });\n\n // Clear stored verifier\n sessionStorage.removeItem(OAUTH_STORAGE_KEY);\n\n if (!response.ok) {\n const error = await response.json();\n throw new Error(error.error_description || 'Token exchange failed');\n }\n\n return response.json();\n },\n\n /**\n * Verify state parameter from callback\n * Call this before exchangeCode to prevent CSRF attacks\n * @param receivedState - State parameter from callback URL\n * @returns true if state matches, false otherwise\n */\n verifyState: (receivedState: string): boolean => {\n const stored = sessionStorage.getItem(OAUTH_STORAGE_KEY);\n if (!stored) return false;\n const { state } = JSON.parse(stored);\n return state === receivedState;\n },\n\n /**\n * Parse authorization response from callback URL\n * @param url - Callback URL (defaults to current window.location.href)\n * @returns Parsed code and state, or error\n */\n parseCallback: (\n url?: string\n ): { code: string; state: string } | { error: string; errorDescription?: string } => {\n const urlObj = new URL(url || window.location.href);\n const error = urlObj.searchParams.get('error');\n\n if (error) {\n return {\n error,\n errorDescription: urlObj.searchParams.get('error_description') || undefined,\n };\n }\n\n const code = urlObj.searchParams.get('code');\n const state = urlObj.searchParams.get('state');\n\n if (!code || !state) {\n return { error: 'missing_params', errorDescription: 'Missing code or state parameter' };\n }\n\n return { code, state };\n },\n };\n\n private openOAuthFlow(\n url: string,\n state: string,\n redirectUri: string\n ): Promise<OAuthResult> {\n return new Promise((resolve, reject) => {\n if (this.usePopup) {\n this.openOAuthPopup(url, state, redirectUri, resolve, reject);\n } else {\n // Full page redirect - won't resolve, page navigates away\n window.location.href = url;\n }\n });\n }\n\n private openOAuthPopup(\n url: string,\n state: string,\n redirectUri: string,\n resolve: (value: OAuthResult) => void,\n reject: (error: OpenKeyError) => void\n ) {\n const left = window.screenX + (window.outerWidth - POPUP_WIDTH) / 2;\n const top = window.screenY + (window.outerHeight - POPUP_HEIGHT) / 2;\n\n this.popup = window.open(\n url,\n 'openkey-oauth',\n `width=${POPUP_WIDTH},height=${POPUP_HEIGHT},left=${left},top=${top},popup=true`\n );\n\n if (!this.popup) {\n reject({\n code: 'POPUP_BLOCKED',\n message: 'Popup was blocked. Please allow popups or use redirect mode.',\n });\n return;\n }\n\n const redirectHost = new URL(redirectUri).origin;\n\n // Poll for redirect back with code\n const pollInterval = setInterval(() => {\n try {\n if (this.popup?.closed) {\n clearInterval(pollInterval);\n clearTimeout(timeout);\n reject({\n code: 'USER_CANCELLED',\n message: 'User closed the popup',\n });\n return;\n }\n\n // Check if redirected to callback URL\n const popupUrl = this.popup?.location?.href;\n if (popupUrl && popupUrl.startsWith(redirectHost)) {\n clearInterval(pollInterval);\n clearTimeout(timeout);\n\n const urlObj = new URL(popupUrl);\n const code = urlObj.searchParams.get('code');\n const returnedState = urlObj.searchParams.get('state');\n const error = urlObj.searchParams.get('error');\n\n this.popup?.close();\n\n if (error) {\n reject({\n code: 'UNAUTHORIZED',\n message: urlObj.searchParams.get('error_description') || error,\n });\n return;\n }\n\n if (returnedState !== state) {\n reject({\n code: 'STATE_MISMATCH',\n message: 'State mismatch - possible CSRF attack',\n });\n return;\n }\n\n if (code) {\n resolve({ code, state: returnedState });\n } else {\n reject({\n code: 'UNAUTHORIZED',\n message: 'Authorization failed - no code returned',\n });\n }\n }\n } catch {\n // Cross-origin - popup still on OpenKey domain, continue polling\n }\n }, 100);\n\n // Timeout\n const timeout = setTimeout(() => {\n clearInterval(pollInterval);\n this.popup?.close();\n reject({\n code: 'TIMEOUT',\n message: 'OAuth flow timed out',\n });\n }, DEFAULT_TIMEOUT);\n }\n\n private async openFlow<T>(action: string, message: object): Promise<T> {\n return new Promise((resolve, reject) => {\n const url = `${this.host}/widget/${action}?origin=${encodeURIComponent(window.location.origin)}`;\n\n if (this.usePopup) {\n this.openPopup(url, message, resolve, reject);\n } else {\n this.openIframe(url, message, resolve, reject);\n }\n });\n }\n\n private openPopup<T>(\n url: string,\n message: object,\n resolve: (value: T) => void,\n reject: (error: OpenKeyError) => void\n ) {\n // Calculate popup position\n const left = window.screenX + (window.outerWidth - POPUP_WIDTH) / 2;\n const top = window.screenY + (window.outerHeight - POPUP_HEIGHT) / 2;\n\n this.popup = window.open(\n url,\n 'openkey',\n `width=${POPUP_WIDTH},height=${POPUP_HEIGHT},left=${left},top=${top},popup=true`\n );\n\n if (!this.popup) {\n reject({\n code: 'POPUP_BLOCKED',\n message: 'Popup was blocked. Please allow popups for this site.',\n });\n return;\n }\n\n const cleanup = () => {\n window.removeEventListener('message', handleMessage);\n clearInterval(pollClosed);\n clearTimeout(timeout);\n };\n\n // Poll to detect popup close\n const pollClosed = setInterval(() => {\n if (this.popup?.closed) {\n cleanup();\n reject({\n code: 'USER_CANCELLED',\n message: 'User closed the popup',\n });\n }\n }, 500);\n\n // Timeout\n const timeout = setTimeout(() => {\n cleanup();\n this.popup?.close();\n reject({\n code: 'TIMEOUT',\n message: 'Request timed out',\n });\n }, DEFAULT_TIMEOUT);\n\n // Listen for messages\n const handleMessage = (event: MessageEvent) => {\n if (event.origin !== this.host) return;\n\n const data = event.data as MessageType;\n\n if (data.type === 'openkey:ready') {\n // Widget is ready, send our request\n this.popup?.postMessage(message, this.host);\n return;\n }\n\n if (data.type === 'openkey:close') {\n cleanup();\n this.popup?.close();\n reject({\n code: 'USER_CANCELLED',\n message: 'User cancelled the request',\n });\n return;\n }\n\n // Handle responses\n if (\n data.type === 'openkey:auth:response' ||\n data.type === 'openkey:sign:response' ||\n data.type === 'openkey:signTypedData:response' ||\n data.type === 'openkey:link-wallet:response'\n ) {\n cleanup();\n this.popup?.close();\n\n if (data.success) {\n if (data.type === 'openkey:auth:response') {\n resolve({ address: data.address, keyId: data.keyId, keyType: data.keyType || 'MANAGED' } as T);\n } else if (data.type === 'openkey:link-wallet:response') {\n resolve({ address: data.address, keyId: data.keyId } as T);\n } else {\n resolve({ signature: data.signature, address: data.address } as T);\n }\n } else {\n reject(data.error);\n }\n }\n };\n\n window.addEventListener('message', handleMessage);\n }\n\n private openIframe<T>(\n url: string,\n message: object,\n resolve: (value: T) => void,\n reject: (error: OpenKeyError) => void\n ) {\n // Create overlay\n const overlay = document.createElement('div');\n overlay.style.cssText = `\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 999999;\n `;\n\n // Create iframe container\n const container = document.createElement('div');\n container.style.cssText = `\n width: ${POPUP_WIDTH}px;\n height: ${POPUP_HEIGHT}px;\n background: #1a1a1a;\n border-radius: 12px;\n overflow: hidden;\n box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);\n `;\n\n // Create iframe\n this.iframe = document.createElement('iframe');\n this.iframe.src = url;\n this.iframe.style.cssText = `\n width: 100%;\n height: 100%;\n border: none;\n `;\n\n container.appendChild(this.iframe);\n overlay.appendChild(container);\n document.body.appendChild(overlay);\n\n const cleanup = () => {\n window.removeEventListener('message', handleMessage);\n clearTimeout(timeout);\n overlay.remove();\n this.iframe = null;\n };\n\n // Close on overlay click\n overlay.addEventListener('click', (e) => {\n if (e.target === overlay) {\n cleanup();\n reject({\n code: 'USER_CANCELLED',\n message: 'User cancelled the request',\n });\n }\n });\n\n // Timeout\n const timeout = setTimeout(() => {\n cleanup();\n reject({\n code: 'TIMEOUT',\n message: 'Request timed out',\n });\n }, DEFAULT_TIMEOUT);\n\n // Listen for messages\n const handleMessage = (event: MessageEvent) => {\n if (event.origin !== this.host) return;\n\n const data = event.data as MessageType;\n\n if (data.type === 'openkey:ready') {\n this.iframe?.contentWindow?.postMessage(message, this.host);\n return;\n }\n\n if (data.type === 'openkey:close') {\n cleanup();\n reject({\n code: 'USER_CANCELLED',\n message: 'User cancelled the request',\n });\n return;\n }\n\n // Handle responses\n if (\n data.type === 'openkey:auth:response' ||\n data.type === 'openkey:sign:response' ||\n data.type === 'openkey:signTypedData:response' ||\n data.type === 'openkey:link-wallet:response'\n ) {\n cleanup();\n\n if (data.success) {\n if (data.type === 'openkey:auth:response') {\n resolve({ address: data.address, keyId: data.keyId, keyType: data.keyType || 'MANAGED' } as T);\n } else if (data.type === 'openkey:link-wallet:response') {\n resolve({ address: data.address, keyId: data.keyId } as T);\n } else {\n resolve({ signature: data.signature, address: data.address } as T);\n }\n } else {\n reject(data.error);\n }\n }\n };\n\n window.addEventListener('message', handleMessage);\n }\n\n /**\n * Disconnect and close any open flows\n */\n disconnect() {\n this.popup?.close();\n this.popup = null;\n this.iframe?.parentElement?.parentElement?.remove();\n this.iframe = null;\n }\n}\n\n/**\n * Unified EIP-1193 provider that wraps an OpenKey instance.\n * Transparently routes signing to either OpenKey (managed keys) or the user's\n * wallet (external keys).\n */\nexport class OpenKeyEIP1193Provider implements EIP1193Provider {\n private openkey: OpenKey;\n private address: string;\n private keyId: string;\n private keyType: 'MANAGED' | 'EXTERNAL';\n\n constructor(openkey: OpenKey, authResult: AuthResult) {\n this.openkey = openkey;\n this.address = authResult.address;\n this.keyId = authResult.keyId;\n this.keyType = authResult.keyType;\n }\n\n async request({ method, params }: { method: string; params?: any[] }): Promise<any> {\n switch (method) {\n case 'eth_accounts':\n case 'eth_requestAccounts':\n return [this.address];\n\n case 'eth_chainId':\n return '0x1'; // mainnet\n\n case 'personal_sign': {\n if (this.keyType === 'EXTERNAL') {\n const provider = await this.openkey.findWalletProvider(this.address);\n return provider.request({ method, params });\n }\n // Managed key: route through OpenKey\n const message = this.hexToString(params![0] as string);\n const result = await this.openkey.signMessage({ message, keyId: this.keyId });\n return result.signature;\n }\n\n case 'eth_signTypedData_v4': {\n if (this.keyType === 'EXTERNAL') {\n const provider = await this.openkey.findWalletProvider(this.address);\n return provider.request({ method, params });\n }\n const data = JSON.parse(params![1] as string);\n const result = await this.openkey.signTypedData(data);\n return result.signature;\n }\n\n default:\n throw new Error(`Unsupported method: ${method}`);\n }\n }\n\n private hexToString(hex: string): string {\n const bytes = new Uint8Array(\n (hex.startsWith('0x') ? hex.slice(2) : hex).match(/.{1,2}/g)!.map(b => parseInt(b, 16))\n );\n return new TextDecoder().decode(bytes);\n }\n}\n\n// Default export for convenience\nexport default OpenKey;\n\n// Create singleton instance\nlet defaultInstance: OpenKey | null = null;\n\nexport function getOpenKey(config?: OpenKeyConfig): OpenKey {\n if (!defaultInstance || config) {\n defaultInstance = new OpenKey(config);\n }\n return defaultInstance;\n}\n"],"mappings":"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","names":["DEFAULT_HOST","OAUTH_STORAGE_KEY","base64UrlEncode","buffer","generateCodeVerifier","array","generateCodeChallenge","verifier","data","digest","generateState","OpenKey","config","DEFAULT_HOST","event","result","request","targetAddress","provider","a","hexMessage","str","b","res","challenge","state","authUrl","code","stored","response","error","receivedState","url","urlObj","redirectUri","resolve","reject","left","top","redirectHost","pollInterval","timeout","popupUrl","returnedState","action","message","cleanup","handleMessage","pollClosed","overlay","container","e","OpenKeyEIP1193Provider","openkey","authResult","method","params","hex","bytes","index_default","defaultInstance","getOpenKey"]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// OpenKey SDK - Browser client for third-party apps\n// Provides \"Sign with OpenKey\" functionality via popup or iframe\n\nexport interface EIP1193Provider {\n request(args: { method: string; params?: any[] }): Promise<any>;\n}\n\ninterface EIP6963ProviderInfo {\n uuid: string;\n name: string;\n icon: string;\n rdns: string;\n}\n\ninterface EIP6963ProviderDetail {\n info: EIP6963ProviderInfo;\n provider: EIP1193Provider;\n}\n\nexport type OpenKeyMode = 'iframe' | 'popup' | 'redirect';\n\nexport interface OpenKeyConfig {\n /** OpenKey host URL (default: https://openkey.so) */\n host?: string;\n /** App identifier for display */\n appName?: string;\n /** UI mode: 'iframe' (default), 'popup', or 'redirect' */\n mode?: OpenKeyMode;\n /** App-provided wallet provider for external key signing */\n externalProvider?: EIP1193Provider;\n}\n\nexport interface SignMessageRequest {\n message: string;\n keyId?: string; // If not provided, user selects\n}\n\nexport interface SignTypedDataRequest {\n domain: {\n name?: string;\n version?: string;\n chainId?: number;\n verifyingContract?: string;\n };\n types: Record<string, Array<{ name: string; type: string }>>;\n primaryType: string;\n message: Record<string, unknown>;\n keyId?: string;\n}\n\nexport interface SignResult {\n signature: string;\n address: string;\n}\n\nexport interface AuthResult {\n address: string;\n keyId: string;\n keyType: 'MANAGED' | 'EXTERNAL';\n}\n\nexport interface OpenKeyError {\n code: 'USER_CANCELLED' | 'POPUP_BLOCKED' | 'TIMEOUT' | 'NO_KEY' | 'UNAUTHORIZED' | 'UNKNOWN' | 'STATE_MISMATCH';\n message: string;\n}\n\n// ======= OAuth 2.1 Types =======\n\n/** OAuth configuration for third-party apps */\nexport interface OAuthConfig {\n /** OAuth client_id (registered with OpenKey) */\n clientId: string;\n /** Redirect URI (must match registered URI) */\n redirectUri: string;\n /** State parameter for CSRF protection (auto-generated if not provided) */\n state?: string;\n}\n\n/** Result from OAuth authorization */\nexport interface OAuthResult {\n /** Authorization code (exchange for tokens) */\n code: string;\n /** State parameter (verify matches request) */\n state: string;\n}\n\n/** Response from token exchange */\nexport interface OAuthTokenResponse {\n access_token: string;\n token_type: 'Bearer';\n expires_in: number;\n id_token: string;\n scope: string;\n refresh_token?: string;\n}\n\ntype MessageType =\n | { type: 'openkey:auth:request'; appName: string }\n | { type: 'openkey:auth:response'; success: true; address: string; keyId: string; keyType?: 'MANAGED' | 'EXTERNAL' }\n | { type: 'openkey:auth:response'; success: false; error: OpenKeyError }\n | { type: 'openkey:sign:request'; message: string; keyId?: string }\n | { type: 'openkey:sign:response'; success: true; signature: string; address: string }\n | { type: 'openkey:sign:response'; success: false; error: OpenKeyError }\n | { type: 'openkey:signTypedData:request'; data: SignTypedDataRequest }\n | { type: 'openkey:signTypedData:response'; success: true; signature: string; address: string }\n | { type: 'openkey:signTypedData:response'; success: false; error: OpenKeyError }\n | { type: 'openkey:link-wallet:request' }\n | { type: 'openkey:link-wallet:response'; success: true; address: string; keyId: string }\n | { type: 'openkey:link-wallet:response'; success: false; error: OpenKeyError }\n | { type: 'openkey:link-wallet:delegate' }\n | { type: 'openkey:link-wallet:result'; success: true; address: string; keyId: string }\n | { type: 'openkey:link-wallet:result'; success: false; error: OpenKeyError }\n | { type: 'openkey:resize'; height: number }\n | { type: 'openkey:ready' }\n | { type: 'openkey:close' };\n\nconst DEFAULT_HOST = 'https://openkey.so';\nconst POPUP_WIDTH = 400;\nconst POPUP_HEIGHT = 600;\nconst DEFAULT_TIMEOUT = 5 * 60 * 1000; // 5 minutes\nconst IFRAME_READY_TIMEOUT = 3000; // 3 seconds\nconst OAUTH_STORAGE_KEY = 'openkey_oauth';\n\nclass IframeModal {\n private root: HTMLDivElement;\n private shadow: ShadowRoot;\n private iframe: HTMLIFrameElement;\n private onClose: () => void;\n private onMessage: (data: MessageType) => void;\n private messageHandler: (event: MessageEvent) => void;\n private host: string;\n\n constructor(opts: { url: string; host: string; onClose: () => void; onMessage: (data: MessageType) => void }) {\n this.host = opts.host;\n this.onClose = opts.onClose;\n this.onMessage = opts.onMessage;\n\n const isMobile = window.matchMedia('(max-width: 639px)').matches;\n\n this.root = document.createElement('div');\n this.shadow = this.root.attachShadow({ mode: 'closed' });\n\n const style = document.createElement('style');\n style.textContent = `\n :host{all:initial}\n .ok-backdrop{position:fixed;inset:0;background:rgba(0,0,0,0.4);backdrop-filter:blur(4px);z-index:999999;display:flex;align-items:${isMobile ? 'flex-end' : 'center'};justify-content:center;animation:ok-fade-in 150ms ease-out}\n .ok-card{position:relative;background:#fafafa;width:${isMobile ? '100%' : '400px'};border-radius:${isMobile ? '16px 16px 0 0' : '16px'};box-shadow:0 25px 50px -12px rgba(0,0,0,0.25);overflow:hidden;overflow-y:auto;animation:${isMobile ? 'ok-slide-up 250ms ease-out' : 'ok-scale-in 200ms ease-out'};max-height:90vh}\n .ok-close{position:absolute;top:8px;right:8px;width:24px;height:24px;border:none;background:transparent;color:#6b7280;font-size:18px;cursor:pointer;z-index:1;display:flex;align-items:center;justify-content:center;border-radius:4px}\n .ok-close:hover{color:#111827}\n .ok-handle{width:40px;height:4px;background:#d4d4d4;border-radius:2px;margin:8px auto 0}\n iframe{border:none;width:100%;height:400px;display:block;transition:height 200ms ease}\n .ok-backdrop.ok-exit{animation:ok-fade-out 150ms ease-in}\n .ok-backdrop.ok-exit .ok-card{animation:${isMobile ? 'ok-slide-down 200ms ease-in' : 'ok-scale-out 150ms ease-in'}}\n @keyframes ok-fade-in{from{opacity:0}to{opacity:1}}\n @keyframes ok-fade-out{from{opacity:1}to{opacity:0}}\n @keyframes ok-scale-in{from{opacity:0;transform:scale(0.95)}to{opacity:1;transform:scale(1)}}\n @keyframes ok-scale-out{from{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(0.95)}}\n @keyframes ok-slide-up{from{transform:translateY(100%)}to{transform:translateY(0)}}\n @keyframes ok-slide-down{from{transform:translateY(0)}to{transform:translateY(100%)}}\n `;\n\n const backdrop = document.createElement('div');\n backdrop.className = 'ok-backdrop';\n backdrop.addEventListener('click', (e) => {\n if (e.target === backdrop) this.close();\n });\n\n const card = document.createElement('div');\n card.className = 'ok-card';\n\n if (isMobile) {\n const handle = document.createElement('div');\n handle.className = 'ok-handle';\n card.appendChild(handle);\n }\n\n const closeBtn = document.createElement('button');\n closeBtn.className = 'ok-close';\n closeBtn.textContent = '\\u00d7';\n closeBtn.addEventListener('click', () => this.close());\n card.appendChild(closeBtn);\n\n this.iframe = document.createElement('iframe');\n this.iframe.src = opts.url;\n this.iframe.setAttribute('allow', 'publickey-credentials-get *; publickey-credentials-create *');\n this.iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-popups');\n card.appendChild(this.iframe);\n\n backdrop.appendChild(card);\n this.shadow.appendChild(style);\n this.shadow.appendChild(backdrop);\n\n this.messageHandler = (event: MessageEvent) => {\n if (event.origin !== this.host) return;\n const data = event.data as MessageType;\n if (data.type === 'openkey:resize') {\n const h = Math.min(data.height, Math.floor(window.innerHeight * 0.85));\n this.iframe.style.height = `${h}px`;\n return;\n }\n this.onMessage(data);\n };\n window.addEventListener('message', this.messageHandler);\n document.body.appendChild(this.root);\n }\n\n postMessage(message: object) {\n this.iframe.contentWindow?.postMessage(message, this.host);\n }\n\n destroy() {\n window.removeEventListener('message', this.messageHandler);\n this.root.remove();\n }\n\n private close() {\n const backdrop = this.shadow.querySelector('.ok-backdrop');\n if (backdrop) {\n backdrop.classList.add('ok-exit');\n setTimeout(() => {\n this.destroy();\n this.onClose();\n }, 200);\n } else {\n this.destroy();\n this.onClose();\n }\n }\n}\n\nclass WalletPicker {\n private root: HTMLDivElement;\n private shadow: ShadowRoot;\n\n constructor(opts: { providers: EIP6963ProviderDetail[]; onSelect: (provider: EIP1193Provider) => void; onCancel: () => void }) {\n this.root = document.createElement('div');\n this.shadow = this.root.attachShadow({ mode: 'closed' });\n\n const style = document.createElement('style');\n style.textContent = `\n :host{all:initial}\n .wp-backdrop{position:fixed;inset:0;background:rgba(0,0,0,0.4);backdrop-filter:blur(4px);z-index:1000000;display:flex;align-items:center;justify-content:center;animation:wp-fade 150ms ease-out}\n .wp-card{background:#fff;width:340px;border-radius:16px;box-shadow:0 25px 50px -12px rgba(0,0,0,0.25);padding:20px;animation:wp-scale 200ms ease-out}\n .wp-title{font:600 16px/1.3 system-ui,sans-serif;color:#111;margin:0 0 12px}\n .wp-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}\n .wp-item{display:flex;align-items:center;gap:10px;padding:10px 12px;border:1px solid #e5e7eb;border-radius:10px;background:transparent;cursor:pointer;font:14px/1.3 system-ui,sans-serif;color:#111;width:100%;text-align:left;transition:border-color 150ms}\n .wp-item:hover{border-color:#6366f1}\n .wp-icon{width:28px;height:28px;border-radius:6px}\n .wp-empty{font:14px/1.4 system-ui,sans-serif;color:#6b7280;text-align:center;padding:16px 0}\n .wp-cancel{display:block;width:100%;margin-top:12px;padding:8px;border:none;background:transparent;color:#6b7280;font:14px/1.3 system-ui,sans-serif;cursor:pointer;border-radius:8px}\n .wp-cancel:hover{background:#f3f4f6;color:#111}\n @keyframes wp-fade{from{opacity:0}to{opacity:1}}\n @keyframes wp-scale{from{opacity:0;transform:scale(0.95)}to{opacity:1;transform:scale(1)}}\n @media(prefers-color-scheme:dark){\n .wp-card{background:#1a1a1a;box-shadow:0 25px 50px -12px rgba(0,0,0,0.5)}\n .wp-title{color:#f3f4f6}\n .wp-item{border-color:#374151;color:#f3f4f6}\n .wp-item:hover{border-color:#818cf8}\n .wp-empty{color:#9ca3af}\n .wp-cancel{color:#9ca3af}\n .wp-cancel:hover{background:#262626;color:#f3f4f6}\n }\n `;\n\n const backdrop = document.createElement('div');\n backdrop.className = 'wp-backdrop';\n backdrop.addEventListener('click', (e) => { if (e.target === backdrop) { this.destroy(); opts.onCancel(); } });\n\n const card = document.createElement('div');\n card.className = 'wp-card';\n\n const title = document.createElement('h2');\n title.className = 'wp-title';\n title.textContent = 'Select a wallet';\n card.appendChild(title);\n\n if (opts.providers.length === 0) {\n const empty = document.createElement('div');\n empty.className = 'wp-empty';\n empty.textContent = 'No wallets detected';\n card.appendChild(empty);\n } else {\n const list = document.createElement('ul');\n list.className = 'wp-list';\n for (const { info, provider } of opts.providers) {\n const item = document.createElement('button');\n item.className = 'wp-item';\n if (info.icon) {\n const icon = document.createElement('img');\n icon.className = 'wp-icon';\n icon.src = info.icon;\n icon.alt = info.name;\n item.appendChild(icon);\n }\n const name = document.createElement('span');\n name.textContent = info.name;\n item.appendChild(name);\n item.addEventListener('click', () => { this.destroy(); opts.onSelect(provider); });\n list.appendChild(item);\n }\n card.appendChild(list);\n }\n\n const cancel = document.createElement('button');\n cancel.className = 'wp-cancel';\n cancel.textContent = 'Cancel';\n cancel.addEventListener('click', () => { this.destroy(); opts.onCancel(); });\n card.appendChild(cancel);\n\n backdrop.appendChild(card);\n this.shadow.appendChild(style);\n this.shadow.appendChild(backdrop);\n document.body.appendChild(this.root);\n }\n\n destroy() {\n this.root.remove();\n }\n}\n\nfunction showToast(message = 'Opening in new window\\u2026', variant: 'info' | 'error' = 'info') {\n const bg = variant === 'error' ? '#dc2626' : '#1f2937';\n const root = document.createElement('div');\n const shadow = root.attachShadow({ mode: 'closed' });\n shadow.innerHTML = `<style>:host{all:initial}.ok-toast{position:fixed;top:16px;right:16px;z-index:1000000;background:${bg};color:#f9fafb;padding:10px 16px;border-radius:8px;font:14px/1.4 system-ui,sans-serif;box-shadow:0 4px 12px rgba(0,0,0,0.15);animation:ok-t-in 200ms ease-out;max-width:360px}@keyframes ok-t-in{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}</style><div class=\"ok-toast\">${message}</div>`;\n document.body.appendChild(root);\n setTimeout(() => root.remove(), 4000);\n}\n\n// ======= PKCE Utilities =======\n\nfunction base64UrlEncode(buffer: Uint8Array): string {\n const base64 = btoa(String.fromCharCode(...buffer));\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=/g, '');\n}\n\nasync function generateCodeVerifier(): Promise<string> {\n const array = new Uint8Array(32);\n crypto.getRandomValues(array);\n return base64UrlEncode(array);\n}\n\nasync function generateCodeChallenge(verifier: string): Promise<string> {\n const encoder = new TextEncoder();\n const data = encoder.encode(verifier);\n const digest = await crypto.subtle.digest('SHA-256', data);\n return base64UrlEncode(new Uint8Array(digest));\n}\n\nfunction generateState(): string {\n const array = new Uint8Array(16);\n crypto.getRandomValues(array);\n return base64UrlEncode(array);\n}\n\nclass ExternalWalletError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ExternalWalletError';\n }\n}\n\nexport class OpenKey {\n private host: string;\n private appName: string;\n private mode: OpenKeyMode;\n private config: OpenKeyConfig;\n private popup: Window | null = null;\n private lastAuth: AuthResult | null = null;\n private discoveredProviders: EIP6963ProviderDetail[] = [];\n\n constructor(config: OpenKeyConfig = {}) {\n this.config = config;\n this.host = config.host || DEFAULT_HOST;\n this.appName = config.appName || window.location.hostname;\n this.mode = config.mode ?? 'iframe';\n\n // Listen for EIP-6963 wallet announcements\n if (typeof window !== 'undefined') {\n this.discoveredProviders = [];\n window.addEventListener('eip6963:announceProvider', (event: any) => {\n this.discoveredProviders.push(event.detail);\n });\n window.dispatchEvent(new Event('eip6963:requestProvider'));\n }\n }\n\n /**\n * Connect to OpenKey and get user's wallet address\n * Opens auth flow for user to select/create a key\n */\n async connect(opts?: { mode?: OpenKeyMode }): Promise<AuthResult> {\n const result = await this.openFlow<AuthResult>('connect', {\n type: 'openkey:auth:request',\n appName: this.appName,\n }, opts?.mode);\n this.lastAuth = result;\n return result;\n }\n\n /**\n * Link an external wallet to the user's OpenKey account\n * Opens link-wallet widget flow\n */\n async linkWallet(opts?: { mode?: OpenKeyMode }): Promise<{ address: string; keyId: string }> {\n return this.openFlow<{ address: string; keyId: string }>('link-wallet', {\n type: 'openkey:link-wallet:request',\n }, opts?.mode);\n }\n\n /**\n * Sign a message with the user's OpenKey wallet\n * For external keys, routes directly to the user's wallet provider\n */\n async signMessage(request: SignMessageRequest, opts?: { mode?: OpenKeyMode }): Promise<SignResult> {\n if (this.lastAuth?.keyType === 'EXTERNAL') {\n return this.signWithExternalWallet(request);\n }\n return this.signWithOpenKey(request, opts?.mode);\n }\n\n /**\n * Sign typed data (EIP-712) with the user's OpenKey wallet\n * For external keys, routes directly to the user's wallet provider\n */\n async signTypedData(request: SignTypedDataRequest, opts?: { mode?: OpenKeyMode }): Promise<SignResult> {\n if (this.lastAuth?.keyType === 'EXTERNAL') {\n return this.signTypedDataWithExternalWallet(request);\n }\n return this.signTypedDataWithOpenKey(request, opts?.mode);\n }\n\n /**\n * Find a wallet provider that controls the given address.\n * Checks: 1) app-provided externalProvider, 2) EIP-6963 discovered wallets, 3) window.ethereum\n */\n async findWalletProvider(targetAddress: string): Promise<EIP1193Provider> {\n // 1. Try app-provided externalProvider first\n if (this.config.externalProvider) {\n return this.config.externalProvider;\n }\n\n // 2. Check EIP-6963 discovered wallets silently (no popup)\n for (const { provider } of this.discoveredProviders) {\n try {\n const accounts = await provider.request({ method: 'eth_accounts' }) as string[];\n if (accounts.some(a => a.toLowerCase() === targetAddress.toLowerCase())) {\n return provider;\n }\n } catch {}\n }\n\n // 3. Fall back to window.ethereum silently\n if (typeof window !== 'undefined' && (window as any).ethereum) {\n const provider = (window as any).ethereum as EIP1193Provider;\n try {\n const accounts = await provider.request({ method: 'eth_accounts' }) as string[];\n if (accounts.some(a => a.toLowerCase() === targetAddress.toLowerCase())) {\n return provider;\n }\n } catch {}\n }\n\n throw new ExternalWalletError('External wallet not found. Connect the wallet that owns this key and try again.');\n }\n\n private async signWithExternalWallet(request: SignMessageRequest): Promise<SignResult> {\n let provider: EIP1193Provider;\n try {\n provider = await this.findWalletProvider(this.lastAuth!.address);\n } catch (e) {\n showToast((e as Error).message, 'error');\n throw e;\n }\n try {\n const hexMessage = this.toHex(request.message);\n const signature = await provider.request({\n method: 'personal_sign',\n params: [hexMessage, this.lastAuth!.address],\n });\n return { signature: signature as string, address: this.lastAuth!.address };\n } catch (e: any) {\n const msg = e?.message?.includes('not been authorized')\n ? 'Wallet rejected the request. Approve the connection in your wallet and try again.'\n : e?.message || 'Signing failed';\n showToast(msg, 'error');\n throw new Error(msg);\n }\n }\n\n private async signTypedDataWithExternalWallet(request: SignTypedDataRequest): Promise<SignResult> {\n let provider: EIP1193Provider;\n try {\n provider = await this.findWalletProvider(this.lastAuth!.address);\n } catch (e) {\n showToast((e as Error).message, 'error');\n throw e;\n }\n try {\n const signature = await provider.request({\n method: 'eth_signTypedData_v4',\n params: [this.lastAuth!.address, JSON.stringify(request)],\n });\n return { signature: signature as string, address: this.lastAuth!.address };\n } catch (e: any) {\n const msg = e?.message?.includes('not been authorized')\n ? 'Wallet rejected the request. Approve the connection in your wallet and try again.'\n : e?.message || 'Signing failed';\n showToast(msg, 'error');\n throw new Error(msg);\n }\n }\n\n private signWithOpenKey(request: SignMessageRequest, mode?: OpenKeyMode): Promise<SignResult> {\n return this.openFlow<SignResult>('sign', {\n type: 'openkey:sign:request',\n message: request.message,\n keyId: request.keyId,\n }, mode);\n }\n\n private signTypedDataWithOpenKey(request: SignTypedDataRequest, mode?: OpenKeyMode): Promise<SignResult> {\n return this.openFlow<SignResult>('sign-typed-data', {\n type: 'openkey:signTypedData:request',\n data: request,\n }, mode);\n }\n\n private toHex(str: string): string {\n return '0x' + Array.from(new TextEncoder().encode(str)).map(b => b.toString(16).padStart(2, '0')).join('');\n }\n\n /**\n * Check if user is already authenticated\n */\n async isConnected(): Promise<boolean> {\n // Check for existing session via API\n try {\n const res = await fetch(`${this.host}/api/auth/session`, {\n credentials: 'include',\n });\n if (!res.ok) return false;\n const data = await res.json();\n return !!data.user;\n } catch {\n return false;\n }\n }\n\n // ======= OAuth 2.1 Provider Methods =======\n\n /**\n * OAuth 2.1 methods for third-party app authentication\n * Use this when your app has registered OAuth client credentials with OpenKey\n */\n oauth = {\n /**\n * Start OAuth authorization flow\n * Opens popup/redirect to OpenKey authorization endpoint\n * @returns Promise that resolves with authorization code after user consent\n */\n connect: async (config: OAuthConfig): Promise<OAuthResult> => {\n // Generate PKCE values\n const verifier = await generateCodeVerifier();\n const challenge = await generateCodeChallenge(verifier);\n const state = config.state || generateState();\n\n // Store for later token exchange\n sessionStorage.setItem(\n OAUTH_STORAGE_KEY,\n JSON.stringify({ verifier, state })\n );\n\n // Build authorization URL\n const authUrl = new URL(`${this.host}/api/auth/oauth2/authorize`);\n authUrl.searchParams.set('client_id', config.clientId);\n authUrl.searchParams.set('redirect_uri', config.redirectUri);\n authUrl.searchParams.set('response_type', 'code');\n authUrl.searchParams.set('scope', 'openid');\n authUrl.searchParams.set('state', state);\n authUrl.searchParams.set('code_challenge', challenge);\n authUrl.searchParams.set('code_challenge_method', 'S256');\n\n return this.openOAuthFlow(authUrl.toString(), state, config.redirectUri);\n },\n\n /**\n * Exchange authorization code for tokens\n * Call this after receiving the callback with authorization code\n * @param code - Authorization code from callback URL\n * @param config - Same config used for connect()\n * @returns Promise that resolves with access_token, id_token, etc.\n */\n exchangeCode: async (\n code: string,\n config: OAuthConfig\n ): Promise<OAuthTokenResponse> => {\n const stored = sessionStorage.getItem(OAUTH_STORAGE_KEY);\n if (!stored) {\n throw new Error('No PKCE verifier found. Start a new OAuth flow.');\n }\n\n const { verifier } = JSON.parse(stored);\n\n const response = await fetch(`${this.host}/api/auth/oauth2/token`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n code,\n redirect_uri: config.redirectUri,\n client_id: config.clientId,\n code_verifier: verifier,\n }),\n });\n\n // Clear stored verifier\n sessionStorage.removeItem(OAUTH_STORAGE_KEY);\n\n if (!response.ok) {\n const error = await response.json();\n throw new Error(error.error_description || 'Token exchange failed');\n }\n\n return response.json();\n },\n\n /**\n * Verify state parameter from callback\n * Call this before exchangeCode to prevent CSRF attacks\n * @param receivedState - State parameter from callback URL\n * @returns true if state matches, false otherwise\n */\n verifyState: (receivedState: string): boolean => {\n const stored = sessionStorage.getItem(OAUTH_STORAGE_KEY);\n if (!stored) return false;\n const { state } = JSON.parse(stored);\n return state === receivedState;\n },\n\n /**\n * Parse authorization response from callback URL\n * @param url - Callback URL (defaults to current window.location.href)\n * @returns Parsed code and state, or error\n */\n parseCallback: (\n url?: string\n ): { code: string; state: string } | { error: string; errorDescription?: string } => {\n const urlObj = new URL(url || window.location.href);\n const error = urlObj.searchParams.get('error');\n\n if (error) {\n return {\n error,\n errorDescription: urlObj.searchParams.get('error_description') || undefined,\n };\n }\n\n const code = urlObj.searchParams.get('code');\n const state = urlObj.searchParams.get('state');\n\n if (!code || !state) {\n return { error: 'missing_params', errorDescription: 'Missing code or state parameter' };\n }\n\n return { code, state };\n },\n };\n\n private openOAuthFlow(\n url: string,\n state: string,\n redirectUri: string\n ): Promise<OAuthResult> {\n return new Promise((resolve, reject) => {\n if (this.mode !== 'redirect') {\n this.openOAuthPopup(url, state, redirectUri, resolve, reject);\n } else {\n // Full page redirect - won't resolve, page navigates away\n window.location.href = url;\n }\n });\n }\n\n private openOAuthPopup(\n url: string,\n state: string,\n redirectUri: string,\n resolve: (value: OAuthResult) => void,\n reject: (error: OpenKeyError) => void\n ) {\n const left = window.screenX + (window.outerWidth - POPUP_WIDTH) / 2;\n const top = window.screenY + (window.outerHeight - POPUP_HEIGHT) / 2;\n\n this.popup = window.open(\n url,\n 'openkey-oauth',\n `width=${POPUP_WIDTH},height=${POPUP_HEIGHT},left=${left},top=${top},popup=true`\n );\n\n if (!this.popup) {\n reject({\n code: 'POPUP_BLOCKED',\n message: 'Popup was blocked. Please allow popups or use redirect mode.',\n });\n return;\n }\n\n const redirectHost = new URL(redirectUri).origin;\n\n // Poll for redirect back with code\n const pollInterval = setInterval(() => {\n try {\n if (this.popup?.closed) {\n clearInterval(pollInterval);\n clearTimeout(timeout);\n reject({\n code: 'USER_CANCELLED',\n message: 'User closed the popup',\n });\n return;\n }\n\n // Check if redirected to callback URL\n const popupUrl = this.popup?.location?.href;\n if (popupUrl && popupUrl.startsWith(redirectHost)) {\n clearInterval(pollInterval);\n clearTimeout(timeout);\n\n const urlObj = new URL(popupUrl);\n const code = urlObj.searchParams.get('code');\n const returnedState = urlObj.searchParams.get('state');\n const error = urlObj.searchParams.get('error');\n\n this.popup?.close();\n\n if (error) {\n reject({\n code: 'UNAUTHORIZED',\n message: urlObj.searchParams.get('error_description') || error,\n });\n return;\n }\n\n if (returnedState !== state) {\n reject({\n code: 'STATE_MISMATCH',\n message: 'State mismatch - possible CSRF attack',\n });\n return;\n }\n\n if (code) {\n resolve({ code, state: returnedState });\n } else {\n reject({\n code: 'UNAUTHORIZED',\n message: 'Authorization failed - no code returned',\n });\n }\n }\n } catch {\n // Cross-origin - popup still on OpenKey domain, continue polling\n }\n }, 100);\n\n // Timeout\n const timeout = setTimeout(() => {\n clearInterval(pollInterval);\n this.popup?.close();\n reject({\n code: 'TIMEOUT',\n message: 'OAuth flow timed out',\n });\n }, DEFAULT_TIMEOUT);\n }\n\n private resolveMode(override?: OpenKeyMode): OpenKeyMode {\n return override ?? this.mode;\n }\n\n private async openFlow<T>(action: string, message: object, modeOverride?: OpenKeyMode): Promise<T> {\n const mode = this.resolveMode(modeOverride);\n const origin = encodeURIComponent(window.location.origin);\n\n if (mode === 'popup') {\n const url = `${this.host}/widget/${action}?origin=${origin}`;\n return new Promise((resolve, reject) => this.openPopup(url, message, resolve, reject));\n }\n\n if (mode === 'redirect') {\n const url = `${this.host}/widget/${action}?origin=${origin}`;\n window.location.href = url;\n return new Promise(() => {}); // never resolves, page navigates\n }\n\n // iframe mode with auto-fallback\n const url = `${this.host}/widget/embed/${action}?origin=${origin}`;\n return this.openIframeModal<T>(url, action, message, origin);\n }\n\n private openIframeModal<T>(url: string, action: string, message: object, origin: string): Promise<T> {\n return new Promise((resolve, reject) => {\n let readyReceived = false;\n let settled = false;\n let modal: IframeModal | null = null;\n\n const settle = (fn: () => void) => {\n if (settled) return;\n settled = true;\n fn();\n };\n\n const cleanup = () => {\n clearTimeout(readyTimeout);\n clearTimeout(overallTimeout);\n modal?.destroy();\n modal = null;\n };\n\n modal = new IframeModal({\n url,\n host: this.host,\n onClose: () => {\n settle(() => {\n clearTimeout(readyTimeout);\n clearTimeout(overallTimeout);\n modal = null;\n reject({ code: 'USER_CANCELLED', message: 'User cancelled the request' } as OpenKeyError);\n });\n },\n onMessage: (data: MessageType) => {\n if (data.type === 'openkey:ready') {\n readyReceived = true;\n modal?.postMessage(message);\n return;\n }\n\n if (data.type === 'openkey:close') {\n settle(() => {\n cleanup();\n reject({ code: 'USER_CANCELLED', message: 'User cancelled the request' } as OpenKeyError);\n });\n return;\n }\n\n if (data.type === 'openkey:link-wallet:delegate') {\n this.handleWalletLinkDelegation(modal!);\n return;\n }\n\n if (\n data.type === 'openkey:auth:response' ||\n data.type === 'openkey:sign:response' ||\n data.type === 'openkey:signTypedData:response' ||\n data.type === 'openkey:link-wallet:response'\n ) {\n settle(() => {\n cleanup();\n if (data.success) {\n if (data.type === 'openkey:auth:response') {\n resolve({ address: data.address, keyId: data.keyId, keyType: data.keyType || 'MANAGED' } as T);\n } else if (data.type === 'openkey:link-wallet:response') {\n resolve({ address: data.address, keyId: data.keyId } as T);\n } else {\n resolve({ signature: data.signature, address: data.address } as T);\n }\n } else {\n reject(data.error);\n }\n });\n }\n },\n });\n\n // Auto-fallback: if no openkey:ready within 3s, fall back to popup\n const readyTimeout = setTimeout(() => {\n if (readyReceived || settled) return;\n cleanup();\n console.warn('OpenKey: iframe blocked by CSP, falling back to popup. Add frame-src https://openkey.so to your CSP.');\n showToast();\n const popupUrl = `${this.host}/widget/${action}?origin=${origin}`;\n this.openPopup(popupUrl, message, (val: any) => settle(() => resolve(val)), (err: any) => settle(() => reject(err)));\n }, IFRAME_READY_TIMEOUT);\n\n // Overall 5-minute timeout\n const overallTimeout = setTimeout(() => {\n settle(() => {\n cleanup();\n reject({ code: 'TIMEOUT', message: 'Request timed out' } as OpenKeyError);\n });\n }, DEFAULT_TIMEOUT);\n });\n }\n\n private handleWalletLinkDelegation(modal: IframeModal) {\n const sendResult = (result: MessageType) => modal.postMessage(result);\n\n new WalletPicker({\n providers: this.discoveredProviders,\n onCancel: () => {\n sendResult({ type: 'openkey:link-wallet:result', success: false, error: { code: 'USER_CANCELLED', message: 'Wallet linking cancelled' } });\n },\n onSelect: async (provider) => {\n try {\n const accounts = await provider.request({ method: 'eth_requestAccounts' }) as string[];\n const address = accounts[0];\n if (!address) throw new Error('No account returned');\n\n // Get challenge from API\n const challengeRes = await fetch(`${this.host}/api/keys/link/challenge`, {\n method: 'POST',\n credentials: 'include',\n });\n if (!challengeRes.ok) throw new Error('Failed to get challenge');\n const { message } = await challengeRes.json();\n\n // Sign challenge with wallet\n const hexMessage = this.toHex(message);\n const signature = await provider.request({\n method: 'personal_sign',\n params: [hexMessage, address],\n }) as string;\n\n // Submit to API\n const linkRes = await fetch(`${this.host}/api/keys/link`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify({ address, signature, message }),\n });\n if (!linkRes.ok) {\n const err = await linkRes.json();\n throw new Error(err.error || 'Failed to link wallet');\n }\n const { key } = await linkRes.json();\n\n sendResult({ type: 'openkey:link-wallet:result', success: true, address: key.address, keyId: key.id });\n } catch (e: any) {\n sendResult({ type: 'openkey:link-wallet:result', success: false, error: { code: 'UNKNOWN', message: e.message || 'Wallet linking failed' } });\n }\n },\n });\n }\n\n private openPopup<T>(\n url: string,\n message: object,\n resolve: (value: T) => void,\n reject: (error: OpenKeyError) => void\n ) {\n // Calculate popup position\n const left = window.screenX + (window.outerWidth - POPUP_WIDTH) / 2;\n const top = window.screenY + (window.outerHeight - POPUP_HEIGHT) / 2;\n\n this.popup = window.open(\n url,\n 'openkey',\n `width=${POPUP_WIDTH},height=${POPUP_HEIGHT},left=${left},top=${top},popup=true`\n );\n\n if (!this.popup) {\n reject({\n code: 'POPUP_BLOCKED',\n message: 'Popup was blocked. Please allow popups for this site.',\n });\n return;\n }\n\n const cleanup = () => {\n window.removeEventListener('message', handleMessage);\n clearInterval(pollClosed);\n clearTimeout(timeout);\n };\n\n // Poll to detect popup close\n const pollClosed = setInterval(() => {\n if (this.popup?.closed) {\n cleanup();\n reject({\n code: 'USER_CANCELLED',\n message: 'User closed the popup',\n });\n }\n }, 500);\n\n // Timeout\n const timeout = setTimeout(() => {\n cleanup();\n this.popup?.close();\n reject({\n code: 'TIMEOUT',\n message: 'Request timed out',\n });\n }, DEFAULT_TIMEOUT);\n\n // Listen for messages\n const handleMessage = (event: MessageEvent) => {\n if (event.origin !== this.host) return;\n\n const data = event.data as MessageType;\n\n if (data.type === 'openkey:ready') {\n // Widget is ready, send our request\n this.popup?.postMessage(message, this.host);\n return;\n }\n\n if (data.type === 'openkey:close') {\n cleanup();\n this.popup?.close();\n reject({\n code: 'USER_CANCELLED',\n message: 'User cancelled the request',\n });\n return;\n }\n\n // Handle responses\n if (\n data.type === 'openkey:auth:response' ||\n data.type === 'openkey:sign:response' ||\n data.type === 'openkey:signTypedData:response' ||\n data.type === 'openkey:link-wallet:response'\n ) {\n cleanup();\n this.popup?.close();\n\n if (data.success) {\n if (data.type === 'openkey:auth:response') {\n resolve({ address: data.address, keyId: data.keyId, keyType: data.keyType || 'MANAGED' } as T);\n } else if (data.type === 'openkey:link-wallet:response') {\n resolve({ address: data.address, keyId: data.keyId } as T);\n } else {\n resolve({ signature: data.signature, address: data.address } as T);\n }\n } else {\n reject(data.error);\n }\n }\n };\n\n window.addEventListener('message', handleMessage);\n }\n\n /**\n * Disconnect and close any open flows\n */\n disconnect() {\n this.popup?.close();\n this.popup = null;\n }\n}\n\n/**\n * Unified EIP-1193 provider that wraps an OpenKey instance.\n * Transparently routes signing to either OpenKey (managed keys) or the user's\n * wallet (external keys).\n */\nexport class OpenKeyEIP1193Provider implements EIP1193Provider {\n private openkey: OpenKey;\n private address: string;\n private keyId: string;\n private keyType: 'MANAGED' | 'EXTERNAL';\n\n constructor(openkey: OpenKey, authResult: AuthResult) {\n this.openkey = openkey;\n this.address = authResult.address;\n this.keyId = authResult.keyId;\n this.keyType = authResult.keyType;\n }\n\n async request({ method, params }: { method: string; params?: any[] }): Promise<any> {\n switch (method) {\n case 'eth_accounts':\n case 'eth_requestAccounts':\n return [this.address];\n\n case 'eth_chainId':\n return '0x1'; // mainnet\n\n case 'personal_sign': {\n if (this.keyType === 'EXTERNAL') {\n try {\n const provider = await this.openkey.findWalletProvider(this.address);\n return await provider.request({ method, params });\n } catch (e: any) {\n showToast(e?.message || 'External wallet signing failed', 'error');\n throw e;\n }\n }\n // Managed key: route through OpenKey\n const message = this.hexToString(params![0] as string);\n const result = await this.openkey.signMessage({ message, keyId: this.keyId });\n return result.signature;\n }\n\n case 'eth_signTypedData_v4': {\n if (this.keyType === 'EXTERNAL') {\n try {\n const provider = await this.openkey.findWalletProvider(this.address);\n return await provider.request({ method, params });\n } catch (e: any) {\n showToast(e?.message || 'External wallet signing failed', 'error');\n throw e;\n }\n }\n const data = JSON.parse(params![1] as string);\n const result = await this.openkey.signTypedData(data);\n return result.signature;\n }\n\n default:\n throw new Error(`Unsupported method: ${method}`);\n }\n }\n\n private hexToString(hex: string): string {\n const bytes = new Uint8Array(\n (hex.startsWith('0x') ? hex.slice(2) : hex).match(/.{1,2}/g)!.map(b => parseInt(b, 16))\n );\n return new TextDecoder().decode(bytes);\n }\n}\n\n// Default export for convenience\nexport default OpenKey;\n\n// Create singleton instance\nlet defaultInstance: OpenKey | null = null;\n\nexport function getOpenKey(config?: OpenKeyConfig): OpenKey {\n if (!defaultInstance || config) {\n defaultInstance = new OpenKey(config);\n }\n return defaultInstance;\n}\n"],"mappings":"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","names":["DEFAULT_HOST","OAUTH_STORAGE_KEY","IframeModal","opts","isMobile","style","backdrop","e","card","handle","closeBtn","event","data","h","message","WalletPicker","title","empty","list","info","provider","item","icon","name","cancel","showToast","variant","bg","root","shadow","base64UrlEncode","buffer","generateCodeVerifier","array","generateCodeChallenge","verifier","digest","generateState","ExternalWalletError","OpenKey","config","DEFAULT_HOST","result","request","targetAddress","a","hexMessage","msg","mode","str","b","res","challenge","state","authUrl","code","stored","response","error","receivedState","url","urlObj","redirectUri","resolve","reject","left","top","redirectHost","pollInterval","timeout","popupUrl","returnedState","override","action","modeOverride","origin","readyReceived","settled","modal","settle","fn","cleanup","readyTimeout","overallTimeout","val","err","sendResult","address","challengeRes","signature","linkRes","key","handleMessage","pollClosed","OpenKeyEIP1193Provider","openkey","authResult","method","params","hex","bytes","index_default","defaultInstance","getOpenKey"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openkey/sdk",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",