@openkey/sdk 0.0.1 → 0.0.3

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/src/index.ts DELETED
@@ -1,369 +0,0 @@
1
- // OpenKey SDK - Browser client for third-party apps
2
- // Provides "Sign with OpenKey" functionality via popup or iframe
3
-
4
- export interface OpenKeyConfig {
5
- /** OpenKey host URL (default: https://openkey.so) */
6
- host?: string;
7
- /** App identifier for display */
8
- appName?: string;
9
- /** Use popup instead of iframe (default: true) */
10
- usePopup?: boolean;
11
- }
12
-
13
- export interface SignMessageRequest {
14
- message: string;
15
- keyId?: string; // If not provided, user selects
16
- }
17
-
18
- export interface SignTypedDataRequest {
19
- domain: {
20
- name?: string;
21
- version?: string;
22
- chainId?: number;
23
- verifyingContract?: string;
24
- };
25
- types: Record<string, Array<{ name: string; type: string }>>;
26
- primaryType: string;
27
- message: Record<string, unknown>;
28
- keyId?: string;
29
- }
30
-
31
- export interface SignResult {
32
- signature: string;
33
- address: string;
34
- }
35
-
36
- export interface AuthResult {
37
- address: string;
38
- keyId: string;
39
- }
40
-
41
- export interface OpenKeyError {
42
- code: 'USER_CANCELLED' | 'POPUP_BLOCKED' | 'TIMEOUT' | 'NO_KEY' | 'UNAUTHORIZED' | 'UNKNOWN';
43
- message: string;
44
- }
45
-
46
- type MessageType =
47
- | { type: 'openkey:auth:request'; appName: string }
48
- | { type: 'openkey:auth:response'; success: true; address: string; keyId: string }
49
- | { type: 'openkey:auth:response'; success: false; error: OpenKeyError }
50
- | { type: 'openkey:sign:request'; message: string; keyId?: string }
51
- | { type: 'openkey:sign:response'; success: true; signature: string; address: string }
52
- | { type: 'openkey:sign:response'; success: false; error: OpenKeyError }
53
- | { type: 'openkey:signTypedData:request'; data: SignTypedDataRequest }
54
- | { type: 'openkey:signTypedData:response'; success: true; signature: string; address: string }
55
- | { type: 'openkey:signTypedData:response'; success: false; error: OpenKeyError }
56
- | { type: 'openkey:ready' }
57
- | { type: 'openkey:close' };
58
-
59
- const DEFAULT_HOST = 'https://openkey.so';
60
- const POPUP_WIDTH = 400;
61
- const POPUP_HEIGHT = 600;
62
- const DEFAULT_TIMEOUT = 5 * 60 * 1000; // 5 minutes
63
-
64
- export class OpenKey {
65
- private host: string;
66
- private appName: string;
67
- private usePopup: boolean;
68
- private popup: Window | null = null;
69
- private iframe: HTMLIFrameElement | null = null;
70
-
71
- constructor(config: OpenKeyConfig = {}) {
72
- this.host = config.host || DEFAULT_HOST;
73
- this.appName = config.appName || window.location.hostname;
74
- this.usePopup = config.usePopup ?? true;
75
- }
76
-
77
- /**
78
- * Connect to OpenKey and get user's wallet address
79
- * Opens auth flow for user to select/create a key
80
- */
81
- async connect(): Promise<AuthResult> {
82
- return this.openFlow<AuthResult>('connect', {
83
- type: 'openkey:auth:request',
84
- appName: this.appName,
85
- });
86
- }
87
-
88
- /**
89
- * Sign a message with the user's OpenKey wallet
90
- */
91
- async signMessage(request: SignMessageRequest): Promise<SignResult> {
92
- return this.openFlow<SignResult>('sign', {
93
- type: 'openkey:sign:request',
94
- message: request.message,
95
- keyId: request.keyId,
96
- });
97
- }
98
-
99
- /**
100
- * Sign typed data (EIP-712) with the user's OpenKey wallet
101
- */
102
- async signTypedData(request: SignTypedDataRequest): Promise<SignResult> {
103
- return this.openFlow<SignResult>('sign-typed-data', {
104
- type: 'openkey:signTypedData:request',
105
- data: request,
106
- });
107
- }
108
-
109
- /**
110
- * Check if user is already authenticated
111
- */
112
- async isConnected(): Promise<boolean> {
113
- // Check for existing session via API
114
- try {
115
- const res = await fetch(`${this.host}/api/auth/session`, {
116
- credentials: 'include',
117
- });
118
- if (!res.ok) return false;
119
- const data = await res.json();
120
- return !!data.user;
121
- } catch {
122
- return false;
123
- }
124
- }
125
-
126
- private async openFlow<T>(action: string, message: object): Promise<T> {
127
- return new Promise((resolve, reject) => {
128
- const url = `${this.host}/widget/${action}?origin=${encodeURIComponent(window.location.origin)}`;
129
-
130
- if (this.usePopup) {
131
- this.openPopup(url, message, resolve, reject);
132
- } else {
133
- this.openIframe(url, message, resolve, reject);
134
- }
135
- });
136
- }
137
-
138
- private openPopup<T>(
139
- url: string,
140
- message: object,
141
- resolve: (value: T) => void,
142
- reject: (error: OpenKeyError) => void
143
- ) {
144
- // Calculate popup position
145
- const left = window.screenX + (window.outerWidth - POPUP_WIDTH) / 2;
146
- const top = window.screenY + (window.outerHeight - POPUP_HEIGHT) / 2;
147
-
148
- this.popup = window.open(
149
- url,
150
- 'openkey',
151
- `width=${POPUP_WIDTH},height=${POPUP_HEIGHT},left=${left},top=${top},popup=true`
152
- );
153
-
154
- if (!this.popup) {
155
- reject({
156
- code: 'POPUP_BLOCKED',
157
- message: 'Popup was blocked. Please allow popups for this site.',
158
- });
159
- return;
160
- }
161
-
162
- const cleanup = () => {
163
- window.removeEventListener('message', handleMessage);
164
- clearInterval(pollClosed);
165
- clearTimeout(timeout);
166
- };
167
-
168
- // Poll to detect popup close
169
- const pollClosed = setInterval(() => {
170
- if (this.popup?.closed) {
171
- cleanup();
172
- reject({
173
- code: 'USER_CANCELLED',
174
- message: 'User closed the popup',
175
- });
176
- }
177
- }, 500);
178
-
179
- // Timeout
180
- const timeout = setTimeout(() => {
181
- cleanup();
182
- this.popup?.close();
183
- reject({
184
- code: 'TIMEOUT',
185
- message: 'Request timed out',
186
- });
187
- }, DEFAULT_TIMEOUT);
188
-
189
- // Listen for messages
190
- const handleMessage = (event: MessageEvent) => {
191
- if (event.origin !== this.host) return;
192
-
193
- const data = event.data as MessageType;
194
-
195
- if (data.type === 'openkey:ready') {
196
- // Widget is ready, send our request
197
- this.popup?.postMessage(message, this.host);
198
- return;
199
- }
200
-
201
- if (data.type === 'openkey:close') {
202
- cleanup();
203
- this.popup?.close();
204
- reject({
205
- code: 'USER_CANCELLED',
206
- message: 'User cancelled the request',
207
- });
208
- return;
209
- }
210
-
211
- // Handle responses
212
- if (
213
- data.type === 'openkey:auth:response' ||
214
- data.type === 'openkey:sign:response' ||
215
- data.type === 'openkey:signTypedData:response'
216
- ) {
217
- cleanup();
218
- this.popup?.close();
219
-
220
- if (data.success) {
221
- if (data.type === 'openkey:auth:response') {
222
- resolve({ address: data.address, keyId: data.keyId } as T);
223
- } else {
224
- resolve({ signature: data.signature, address: data.address } as T);
225
- }
226
- } else {
227
- reject(data.error);
228
- }
229
- }
230
- };
231
-
232
- window.addEventListener('message', handleMessage);
233
- }
234
-
235
- private openIframe<T>(
236
- url: string,
237
- message: object,
238
- resolve: (value: T) => void,
239
- reject: (error: OpenKeyError) => void
240
- ) {
241
- // Create overlay
242
- const overlay = document.createElement('div');
243
- overlay.style.cssText = `
244
- position: fixed;
245
- inset: 0;
246
- background: rgba(0, 0, 0, 0.5);
247
- display: flex;
248
- align-items: center;
249
- justify-content: center;
250
- z-index: 999999;
251
- `;
252
-
253
- // Create iframe container
254
- const container = document.createElement('div');
255
- container.style.cssText = `
256
- width: ${POPUP_WIDTH}px;
257
- height: ${POPUP_HEIGHT}px;
258
- background: #1a1a1a;
259
- border-radius: 12px;
260
- overflow: hidden;
261
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
262
- `;
263
-
264
- // Create iframe
265
- this.iframe = document.createElement('iframe');
266
- this.iframe.src = url;
267
- this.iframe.style.cssText = `
268
- width: 100%;
269
- height: 100%;
270
- border: none;
271
- `;
272
-
273
- container.appendChild(this.iframe);
274
- overlay.appendChild(container);
275
- document.body.appendChild(overlay);
276
-
277
- const cleanup = () => {
278
- window.removeEventListener('message', handleMessage);
279
- clearTimeout(timeout);
280
- overlay.remove();
281
- this.iframe = null;
282
- };
283
-
284
- // Close on overlay click
285
- overlay.addEventListener('click', (e) => {
286
- if (e.target === overlay) {
287
- cleanup();
288
- reject({
289
- code: 'USER_CANCELLED',
290
- message: 'User cancelled the request',
291
- });
292
- }
293
- });
294
-
295
- // Timeout
296
- const timeout = setTimeout(() => {
297
- cleanup();
298
- reject({
299
- code: 'TIMEOUT',
300
- message: 'Request timed out',
301
- });
302
- }, DEFAULT_TIMEOUT);
303
-
304
- // Listen for messages
305
- const handleMessage = (event: MessageEvent) => {
306
- if (event.origin !== this.host) return;
307
-
308
- const data = event.data as MessageType;
309
-
310
- if (data.type === 'openkey:ready') {
311
- this.iframe?.contentWindow?.postMessage(message, this.host);
312
- return;
313
- }
314
-
315
- if (data.type === 'openkey:close') {
316
- cleanup();
317
- reject({
318
- code: 'USER_CANCELLED',
319
- message: 'User cancelled the request',
320
- });
321
- return;
322
- }
323
-
324
- // Handle responses
325
- if (
326
- data.type === 'openkey:auth:response' ||
327
- data.type === 'openkey:sign:response' ||
328
- data.type === 'openkey:signTypedData:response'
329
- ) {
330
- cleanup();
331
-
332
- if (data.success) {
333
- if (data.type === 'openkey:auth:response') {
334
- resolve({ address: data.address, keyId: data.keyId } as T);
335
- } else {
336
- resolve({ signature: data.signature, address: data.address } as T);
337
- }
338
- } else {
339
- reject(data.error);
340
- }
341
- }
342
- };
343
-
344
- window.addEventListener('message', handleMessage);
345
- }
346
-
347
- /**
348
- * Disconnect and close any open flows
349
- */
350
- disconnect() {
351
- this.popup?.close();
352
- this.popup = null;
353
- this.iframe?.parentElement?.parentElement?.remove();
354
- this.iframe = null;
355
- }
356
- }
357
-
358
- // Default export for convenience
359
- export default OpenKey;
360
-
361
- // Create singleton instance
362
- let defaultInstance: OpenKey | null = null;
363
-
364
- export function getOpenKey(config?: OpenKeyConfig): OpenKey {
365
- if (!defaultInstance || config) {
366
- defaultInstance = new OpenKey(config);
367
- }
368
- return defaultInstance;
369
- }
package/tsconfig.json DELETED
@@ -1,10 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "./dist",
5
- "rootDir": "./src",
6
- "lib": ["ES2022", "DOM"]
7
- },
8
- "include": ["src/**/*"],
9
- "exclude": ["node_modules", "dist"]
10
- }
package/tsup.config.ts DELETED
@@ -1,10 +0,0 @@
1
- import { defineConfig } from 'tsup';
2
-
3
- export default defineConfig({
4
- entry: ['src/index.ts'],
5
- format: ['esm', 'cjs'],
6
- dts: true,
7
- clean: true,
8
- sourcemap: true,
9
- minify: true,
10
- });