@openkey/sdk 0.0.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/.turbo/turbo-build.log +79 -0
- package/dist/index.cjs +21 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +75 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/package.json +25 -0
- package/src/index.ts +369 -0
- package/tsconfig.json +10 -0
- package/tsup.config.ts +10 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
$ tsup
|
|
2
|
+
▲ [WARNING] The condition "types" here will never be used as it comes after both "import" and "require" [package.json]
|
|
3
|
+
|
|
4
|
+
package.json:12:6:
|
|
5
|
+
12 │ "types": "./dist/index.d.ts"
|
|
6
|
+
╵ ~~~~~~~
|
|
7
|
+
|
|
8
|
+
The "import" condition comes earlier and will be used for all "import" statements:
|
|
9
|
+
|
|
10
|
+
package.json:10:6:
|
|
11
|
+
10 │ "import": "./dist/index.js",
|
|
12
|
+
╵ ~~~~~~~~
|
|
13
|
+
|
|
14
|
+
The "require" condition comes earlier and will be used for all "require" calls:
|
|
15
|
+
|
|
16
|
+
package.json:11:6:
|
|
17
|
+
11 │ "require": "./dist/index.cjs",
|
|
18
|
+
╵ ~~~~~~~~~
|
|
19
|
+
|
|
20
|
+
CLI Building entry: src/index.ts
|
|
21
|
+
CLI Using tsconfig: tsconfig.json
|
|
22
|
+
CLI tsup v8.5.1
|
|
23
|
+
CLI Using tsup config: /Users/samgbafa/Documents/github/tinycloud/openkey/packages/sdk/tsup.config.ts
|
|
24
|
+
CLI Target: es2022
|
|
25
|
+
CLI Cleaning output folder
|
|
26
|
+
ESM Build start
|
|
27
|
+
CJS Build start
|
|
28
|
+
|
|
29
|
+
WARN [33m▲ [43;33m[[43;30mWARNING[43;33m][0m [1mThe condition "types" here will never be used as it comes after both "import" and "require"[0m [package.json]
|
|
30
|
+
|
|
31
|
+
package.json:12:6:
|
|
32
|
+
[37m 12 │ [32m"types"[37m: "./dist/index.d.ts"
|
|
33
|
+
╵ [32m~~~~~~~[0m
|
|
34
|
+
|
|
35
|
+
The "import" condition comes earlier and will be used for all "import" statements:
|
|
36
|
+
|
|
37
|
+
package.json:10:6:
|
|
38
|
+
[37m 10 │ [32m"import"[37m: "./dist/index.js",
|
|
39
|
+
╵ [32m~~~~~~~~[0m
|
|
40
|
+
|
|
41
|
+
The "require" condition comes earlier and will be used for all "require" calls:
|
|
42
|
+
|
|
43
|
+
package.json:11:6:
|
|
44
|
+
[37m 11 │ [32m"require"[37m: "./dist/index.cjs",
|
|
45
|
+
╵ [32m~~~~~~~~~[0m
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
WARN [33m▲ [43;33m[[43;30mWARNING[43;33m][0m [1mThe condition "types" here will never be used as it comes after both "import" and "require"[0m [package.json]
|
|
51
|
+
|
|
52
|
+
package.json:12:6:
|
|
53
|
+
[37m 12 │ [32m"types"[37m: "./dist/index.d.ts"
|
|
54
|
+
╵ [32m~~~~~~~[0m
|
|
55
|
+
|
|
56
|
+
The "import" condition comes earlier and will be used for all "import" statements:
|
|
57
|
+
|
|
58
|
+
package.json:10:6:
|
|
59
|
+
[37m 10 │ [32m"import"[37m: "./dist/index.js",
|
|
60
|
+
╵ [32m~~~~~~~~[0m
|
|
61
|
+
|
|
62
|
+
The "require" condition comes earlier and will be used for all "require" calls:
|
|
63
|
+
|
|
64
|
+
package.json:11:6:
|
|
65
|
+
[37m 11 │ [32m"require"[37m: "./dist/index.cjs",
|
|
66
|
+
╵ [32m~~~~~~~~~[0m
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
CJS dist/index.cjs 4.24 KB
|
|
71
|
+
CJS dist/index.cjs.map 13.89 KB
|
|
72
|
+
CJS ⚡️ Build success in 50ms
|
|
73
|
+
ESM dist/index.js 3.76 KB
|
|
74
|
+
ESM dist/index.js.map 13.74 KB
|
|
75
|
+
ESM ⚡️ Build success in 51ms
|
|
76
|
+
DTS Build start
|
|
77
|
+
DTS ⚡️ Build success in 2139ms
|
|
78
|
+
DTS dist/index.d.ts 2.00 KB
|
|
79
|
+
DTS dist/index.d.cts 2.00 KB
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";var l=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var T=Object.getOwnPropertyNames;var f=Object.prototype.hasOwnProperty;var E=(o,e)=>{for(var n in e)l(o,n,{get:e[n],enumerable:!0})},k=(o,e,n,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of T(e))!f.call(o,s)&&s!==n&&l(o,s,{get:()=>e[s],enumerable:!(i=m(e,s))||i.enumerable});return o};var P=o=>k(l({},"__esModule",{value:!0}),o);var U={};E(U,{OpenKey:()=>c,default:()=>I,getOpenKey:()=>O});module.exports=P(U);var w="https://openkey.so";var c=class{host;appName;usePopup;popup=null;iframe=null;constructor(e={}){this.host=e.host||w,this.appName=e.appName||window.location.hostname,this.usePopup=e.usePopup??!0}async connect(){return this.openFlow("connect",{type:"openkey:auth:request",appName:this.appName})}async signMessage(e){return this.openFlow("sign",{type:"openkey:sign:request",message:e.message,keyId:e.keyId})}async signTypedData(e){return this.openFlow("sign-typed-data",{type:"openkey:signTypedData:request",data:e})}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}}async openFlow(e,n){return new Promise((i,s)=>{let a=`${this.host}/widget/${e}?origin=${encodeURIComponent(window.location.origin)}`;this.usePopup?this.openPopup(a,n,i,s):this.openIframe(a,n,i,s)})}openPopup(e,n,i,s){let a=window.screenX+(window.outerWidth-400)/2,u=window.screenY+(window.outerHeight-600)/2;if(this.popup=window.open(e,"openkey",`width=400,height=600,left=${a},top=${u},popup=true`),!this.popup){s({code:"POPUP_BLOCKED",message:"Popup was blocked. Please allow popups for this site."});return}let p=()=>{window.removeEventListener("message",d),clearInterval(g),clearTimeout(y)},g=setInterval(()=>{this.popup?.closed&&(p(),s({code:"USER_CANCELLED",message:"User closed the popup"}))},500),y=setTimeout(()=>{p(),this.popup?.close(),s({code:"TIMEOUT",message:"Request timed out"})},3e5),d=t=>{if(t.origin!==this.host)return;let r=t.data;if(r.type==="openkey:ready"){this.popup?.postMessage(n,this.host);return}if(r.type==="openkey:close"){p(),this.popup?.close(),s({code:"USER_CANCELLED",message:"User cancelled the request"});return}(r.type==="openkey:auth:response"||r.type==="openkey:sign:response"||r.type==="openkey:signTypedData:response")&&(p(),this.popup?.close(),r.success?r.type==="openkey:auth:response"?i({address:r.address,keyId:r.keyId}):i({signature:r.signature,address:r.address}):s(r.error))};window.addEventListener("message",d)}openIframe(e,n,i,s){let a=document.createElement("div");a.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 u=document.createElement("div");u.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
|
+
`,u.appendChild(this.iframe),a.appendChild(u),document.body.appendChild(a);let p=()=>{window.removeEventListener("message",y),clearTimeout(g),a.remove(),this.iframe=null};a.addEventListener("click",d=>{d.target===a&&(p(),s({code:"USER_CANCELLED",message:"User cancelled the request"}))});let g=setTimeout(()=>{p(),s({code:"TIMEOUT",message:"Request timed out"})},3e5),y=d=>{if(d.origin!==this.host)return;let t=d.data;if(t.type==="openkey:ready"){this.iframe?.contentWindow?.postMessage(n,this.host);return}if(t.type==="openkey:close"){p(),s({code:"USER_CANCELLED",message:"User cancelled the request"});return}(t.type==="openkey:auth:response"||t.type==="openkey:sign:response"||t.type==="openkey:signTypedData:response")&&(p(),t.success?t.type==="openkey:auth:response"?i({address:t.address,keyId:t.keyId}):i({signature:t.signature,address:t.address}):s(t.error))};window.addEventListener("message",y)}disconnect(){this.popup?.close(),this.popup=null,this.iframe?.parentElement?.parentElement?.remove(),this.iframe=null}},I=c,h=null;function O(o){return(!h||o)&&(h=new c(o)),h}0&&(module.exports={OpenKey,getOpenKey});
|
|
21
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +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 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}\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}\n\nexport interface OpenKeyError {\n code: 'USER_CANCELLED' | 'POPUP_BLOCKED' | 'TIMEOUT' | 'NO_KEY' | 'UNAUTHORIZED' | 'UNKNOWN';\n message: string;\n}\n\ntype MessageType =\n | { type: 'openkey:auth:request'; appName: string }\n | { type: 'openkey:auth:response'; success: true; address: string; keyId: string }\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: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\n\nexport class OpenKey {\n private host: string;\n private appName: string;\n private usePopup: boolean;\n private popup: Window | null = null;\n private iframe: HTMLIFrameElement | null = null;\n\n constructor(config: OpenKeyConfig = {}) {\n this.host = config.host || DEFAULT_HOST;\n this.appName = config.appName || window.location.hostname;\n this.usePopup = config.usePopup ?? true;\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 return this.openFlow<AuthResult>('connect', {\n type: 'openkey:auth:request',\n appName: this.appName,\n });\n }\n\n /**\n * Sign a message with the user's OpenKey wallet\n */\n async signMessage(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 /**\n * Sign typed data (EIP-712) with the user's OpenKey wallet\n */\n async signTypedData(request: SignTypedDataRequest): Promise<SignResult> {\n return this.openFlow<SignResult>('sign-typed-data', {\n type: 'openkey:signTypedData:request',\n data: request,\n });\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 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 ) {\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 } 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 ) {\n cleanup();\n\n if (data.success) {\n if (data.type === 'openkey:auth: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// 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","index_default","getOpenKey","__toCommonJS","DEFAULT_HOST","OpenKey","config","DEFAULT_HOST","request","res","action","message","resolve","reject","url","left","top","cleanup","handleMessage","pollClosed","timeout","event","data","overlay","container","e","index_default","defaultInstance","getOpenKey"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
interface OpenKeyConfig {
|
|
2
|
+
/** OpenKey host URL (default: https://openkey.so) */
|
|
3
|
+
host?: string;
|
|
4
|
+
/** App identifier for display */
|
|
5
|
+
appName?: string;
|
|
6
|
+
/** Use popup instead of iframe (default: true) */
|
|
7
|
+
usePopup?: boolean;
|
|
8
|
+
}
|
|
9
|
+
interface SignMessageRequest {
|
|
10
|
+
message: string;
|
|
11
|
+
keyId?: string;
|
|
12
|
+
}
|
|
13
|
+
interface SignTypedDataRequest {
|
|
14
|
+
domain: {
|
|
15
|
+
name?: string;
|
|
16
|
+
version?: string;
|
|
17
|
+
chainId?: number;
|
|
18
|
+
verifyingContract?: string;
|
|
19
|
+
};
|
|
20
|
+
types: Record<string, Array<{
|
|
21
|
+
name: string;
|
|
22
|
+
type: string;
|
|
23
|
+
}>>;
|
|
24
|
+
primaryType: string;
|
|
25
|
+
message: Record<string, unknown>;
|
|
26
|
+
keyId?: string;
|
|
27
|
+
}
|
|
28
|
+
interface SignResult {
|
|
29
|
+
signature: string;
|
|
30
|
+
address: string;
|
|
31
|
+
}
|
|
32
|
+
interface AuthResult {
|
|
33
|
+
address: string;
|
|
34
|
+
keyId: string;
|
|
35
|
+
}
|
|
36
|
+
interface OpenKeyError {
|
|
37
|
+
code: 'USER_CANCELLED' | 'POPUP_BLOCKED' | 'TIMEOUT' | 'NO_KEY' | 'UNAUTHORIZED' | 'UNKNOWN';
|
|
38
|
+
message: string;
|
|
39
|
+
}
|
|
40
|
+
declare class OpenKey {
|
|
41
|
+
private host;
|
|
42
|
+
private appName;
|
|
43
|
+
private usePopup;
|
|
44
|
+
private popup;
|
|
45
|
+
private iframe;
|
|
46
|
+
constructor(config?: OpenKeyConfig);
|
|
47
|
+
/**
|
|
48
|
+
* Connect to OpenKey and get user's wallet address
|
|
49
|
+
* Opens auth flow for user to select/create a key
|
|
50
|
+
*/
|
|
51
|
+
connect(): Promise<AuthResult>;
|
|
52
|
+
/**
|
|
53
|
+
* Sign a message with the user's OpenKey wallet
|
|
54
|
+
*/
|
|
55
|
+
signMessage(request: SignMessageRequest): Promise<SignResult>;
|
|
56
|
+
/**
|
|
57
|
+
* Sign typed data (EIP-712) with the user's OpenKey wallet
|
|
58
|
+
*/
|
|
59
|
+
signTypedData(request: SignTypedDataRequest): Promise<SignResult>;
|
|
60
|
+
/**
|
|
61
|
+
* Check if user is already authenticated
|
|
62
|
+
*/
|
|
63
|
+
isConnected(): Promise<boolean>;
|
|
64
|
+
private openFlow;
|
|
65
|
+
private openPopup;
|
|
66
|
+
private openIframe;
|
|
67
|
+
/**
|
|
68
|
+
* Disconnect and close any open flows
|
|
69
|
+
*/
|
|
70
|
+
disconnect(): void;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
declare function getOpenKey(config?: OpenKeyConfig): OpenKey;
|
|
74
|
+
|
|
75
|
+
export { type AuthResult, OpenKey, type OpenKeyConfig, type OpenKeyError, type SignMessageRequest, type SignResult, type SignTypedDataRequest, OpenKey as default, getOpenKey };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
interface OpenKeyConfig {
|
|
2
|
+
/** OpenKey host URL (default: https://openkey.so) */
|
|
3
|
+
host?: string;
|
|
4
|
+
/** App identifier for display */
|
|
5
|
+
appName?: string;
|
|
6
|
+
/** Use popup instead of iframe (default: true) */
|
|
7
|
+
usePopup?: boolean;
|
|
8
|
+
}
|
|
9
|
+
interface SignMessageRequest {
|
|
10
|
+
message: string;
|
|
11
|
+
keyId?: string;
|
|
12
|
+
}
|
|
13
|
+
interface SignTypedDataRequest {
|
|
14
|
+
domain: {
|
|
15
|
+
name?: string;
|
|
16
|
+
version?: string;
|
|
17
|
+
chainId?: number;
|
|
18
|
+
verifyingContract?: string;
|
|
19
|
+
};
|
|
20
|
+
types: Record<string, Array<{
|
|
21
|
+
name: string;
|
|
22
|
+
type: string;
|
|
23
|
+
}>>;
|
|
24
|
+
primaryType: string;
|
|
25
|
+
message: Record<string, unknown>;
|
|
26
|
+
keyId?: string;
|
|
27
|
+
}
|
|
28
|
+
interface SignResult {
|
|
29
|
+
signature: string;
|
|
30
|
+
address: string;
|
|
31
|
+
}
|
|
32
|
+
interface AuthResult {
|
|
33
|
+
address: string;
|
|
34
|
+
keyId: string;
|
|
35
|
+
}
|
|
36
|
+
interface OpenKeyError {
|
|
37
|
+
code: 'USER_CANCELLED' | 'POPUP_BLOCKED' | 'TIMEOUT' | 'NO_KEY' | 'UNAUTHORIZED' | 'UNKNOWN';
|
|
38
|
+
message: string;
|
|
39
|
+
}
|
|
40
|
+
declare class OpenKey {
|
|
41
|
+
private host;
|
|
42
|
+
private appName;
|
|
43
|
+
private usePopup;
|
|
44
|
+
private popup;
|
|
45
|
+
private iframe;
|
|
46
|
+
constructor(config?: OpenKeyConfig);
|
|
47
|
+
/**
|
|
48
|
+
* Connect to OpenKey and get user's wallet address
|
|
49
|
+
* Opens auth flow for user to select/create a key
|
|
50
|
+
*/
|
|
51
|
+
connect(): Promise<AuthResult>;
|
|
52
|
+
/**
|
|
53
|
+
* Sign a message with the user's OpenKey wallet
|
|
54
|
+
*/
|
|
55
|
+
signMessage(request: SignMessageRequest): Promise<SignResult>;
|
|
56
|
+
/**
|
|
57
|
+
* Sign typed data (EIP-712) with the user's OpenKey wallet
|
|
58
|
+
*/
|
|
59
|
+
signTypedData(request: SignTypedDataRequest): Promise<SignResult>;
|
|
60
|
+
/**
|
|
61
|
+
* Check if user is already authenticated
|
|
62
|
+
*/
|
|
63
|
+
isConnected(): Promise<boolean>;
|
|
64
|
+
private openFlow;
|
|
65
|
+
private openPopup;
|
|
66
|
+
private openIframe;
|
|
67
|
+
/**
|
|
68
|
+
* Disconnect and close any open flows
|
|
69
|
+
*/
|
|
70
|
+
disconnect(): void;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
declare function getOpenKey(config?: OpenKeyConfig): OpenKey;
|
|
74
|
+
|
|
75
|
+
export { type AuthResult, OpenKey, type OpenKeyConfig, type OpenKeyError, type SignMessageRequest, type SignResult, type SignTypedDataRequest, OpenKey as default, getOpenKey };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
var h="https://openkey.so";var c=class{host;appName;usePopup;popup=null;iframe=null;constructor(e={}){this.host=e.host||h,this.appName=e.appName||window.location.hostname,this.usePopup=e.usePopup??!0}async connect(){return this.openFlow("connect",{type:"openkey:auth:request",appName:this.appName})}async signMessage(e){return this.openFlow("sign",{type:"openkey:sign:request",message:e.message,keyId:e.keyId})}async signTypedData(e){return this.openFlow("sign-typed-data",{type:"openkey:signTypedData:request",data:e})}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}}async openFlow(e,a){return new Promise((i,t)=>{let r=`${this.host}/widget/${e}?origin=${encodeURIComponent(window.location.origin)}`;this.usePopup?this.openPopup(r,a,i,t):this.openIframe(r,a,i,t)})}openPopup(e,a,i,t){let r=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=${r},top=${d},popup=true`),!this.popup){t({code:"POPUP_BLOCKED",message:"Popup was blocked. Please allow popups for this site."});return}let o=()=>{window.removeEventListener("message",p),clearInterval(g),clearTimeout(u)},g=setInterval(()=>{this.popup?.closed&&(o(),t({code:"USER_CANCELLED",message:"User closed the popup"}))},500),u=setTimeout(()=>{o(),this.popup?.close(),t({code:"TIMEOUT",message:"Request timed out"})},3e5),p=s=>{if(s.origin!==this.host)return;let n=s.data;if(n.type==="openkey:ready"){this.popup?.postMessage(a,this.host);return}if(n.type==="openkey:close"){o(),this.popup?.close(),t({code:"USER_CANCELLED",message:"User cancelled the request"});return}(n.type==="openkey:auth:response"||n.type==="openkey:sign:response"||n.type==="openkey:signTypedData:response")&&(o(),this.popup?.close(),n.success?n.type==="openkey:auth:response"?i({address:n.address,keyId:n.keyId}):i({signature:n.signature,address:n.address}):t(n.error))};window.addEventListener("message",p)}openIframe(e,a,i,t){let r=document.createElement("div");r.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 d=document.createElement("div");d.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
|
+
`,d.appendChild(this.iframe),r.appendChild(d),document.body.appendChild(r);let o=()=>{window.removeEventListener("message",u),clearTimeout(g),r.remove(),this.iframe=null};r.addEventListener("click",p=>{p.target===r&&(o(),t({code:"USER_CANCELLED",message:"User cancelled the request"}))});let g=setTimeout(()=>{o(),t({code:"TIMEOUT",message:"Request timed out"})},3e5),u=p=>{if(p.origin!==this.host)return;let s=p.data;if(s.type==="openkey:ready"){this.iframe?.contentWindow?.postMessage(a,this.host);return}if(s.type==="openkey:close"){o(),t({code:"USER_CANCELLED",message:"User cancelled the request"});return}(s.type==="openkey:auth:response"||s.type==="openkey:sign:response"||s.type==="openkey:signTypedData:response")&&(o(),s.success?s.type==="openkey:auth:response"?i({address:s.address,keyId:s.keyId}):i({signature:s.signature,address:s.address}):t(s.error))};window.addEventListener("message",u)}disconnect(){this.popup?.close(),this.popup=null,this.iframe?.parentElement?.parentElement?.remove(),this.iframe=null}},m=c,l=null;function T(y){return(!l||y)&&(l=new c(y)),l}export{c as OpenKey,m as default,T as getOpenKey};
|
|
21
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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 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}\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}\n\nexport interface OpenKeyError {\n code: 'USER_CANCELLED' | 'POPUP_BLOCKED' | 'TIMEOUT' | 'NO_KEY' | 'UNAUTHORIZED' | 'UNKNOWN';\n message: string;\n}\n\ntype MessageType =\n | { type: 'openkey:auth:request'; appName: string }\n | { type: 'openkey:auth:response'; success: true; address: string; keyId: string }\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: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\n\nexport class OpenKey {\n private host: string;\n private appName: string;\n private usePopup: boolean;\n private popup: Window | null = null;\n private iframe: HTMLIFrameElement | null = null;\n\n constructor(config: OpenKeyConfig = {}) {\n this.host = config.host || DEFAULT_HOST;\n this.appName = config.appName || window.location.hostname;\n this.usePopup = config.usePopup ?? true;\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 return this.openFlow<AuthResult>('connect', {\n type: 'openkey:auth:request',\n appName: this.appName,\n });\n }\n\n /**\n * Sign a message with the user's OpenKey wallet\n */\n async signMessage(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 /**\n * Sign typed data (EIP-712) with the user's OpenKey wallet\n */\n async signTypedData(request: SignTypedDataRequest): Promise<SignResult> {\n return this.openFlow<SignResult>('sign-typed-data', {\n type: 'openkey:signTypedData:request',\n data: request,\n });\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 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 ) {\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 } 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 ) {\n cleanup();\n\n if (data.success) {\n if (data.type === 'openkey:auth: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// 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","OpenKey","config","DEFAULT_HOST","request","res","action","message","resolve","reject","url","left","top","cleanup","handleMessage","pollClosed","timeout","event","data","overlay","container","e","index_default","defaultInstance","getOpenKey"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@openkey/sdk",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"require": "./dist/index.cjs",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"dev": "tsup --watch",
|
|
17
|
+
"build": "tsup",
|
|
18
|
+
"typecheck": "tsc --noEmit"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"tsup": "^8.0.0",
|
|
23
|
+
"typescript": "^5.7.0"
|
|
24
|
+
}
|
|
25
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
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
ADDED