UNPKG

@slide-computer/signer-agent

Version:

Initiate transactions with signers on the Internet Computer

250 lines 16.3 kB
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var _StoicConnection_instances, _a, _StoicConnection_isInternalConstructing, _StoicConnection_options, _StoicConnection_delegationChain, _StoicConnection_accounts, _StoicConnection_disconnectListeners, _StoicConnection_disconnectMonitorInterval, _StoicConnection_monitorDisconnect; import { StoicTransportError } from "./agentTransport"; import { Delegation, DelegationChain, ECDSAKeyIdentity, Ed25519KeyIdentity, isDelegationValid, } from "@dfinity/identity"; import { getDelegationChain, getIdentity, IdbStorage, removeDelegationChain, setDelegationChain, setIdentity, } from "@slide-computer/signer-storage"; import { fromHex, requestIdOf, SignIdentity, toHex } from "@dfinity/agent"; import { PartialIdentity } from "@dfinity/identity/lib/cjs/identity/partial"; import { Principal } from "@dfinity/principal"; const ECDSA_KEY_LABEL = "ECDSA"; const ED25519_KEY_LABEL = "Ed25519"; const IDENTITY_STORAGE_KEY = "stoic-base-identity"; const DELEGATION_STORAGE_KEY = "stoic-delegation-chain"; const ACCOUNTS_STORAGE_KEY = "stoic-account-count"; const STOIC_ORIGIN = "https://www.stoicwallet.com"; const STOIC_WINDOW = "stoic"; export class StoicConnection { constructor(options, delegationChain, accounts) { _StoicConnection_instances.add(this); _StoicConnection_options.set(this, void 0); _StoicConnection_delegationChain.set(this, void 0); _StoicConnection_accounts.set(this, void 0); _StoicConnection_disconnectListeners.set(this, new Set()); _StoicConnection_disconnectMonitorInterval.set(this, void 0); const throwError = !__classPrivateFieldGet(_a, _a, "f", _StoicConnection_isInternalConstructing); __classPrivateFieldSet(_a, _a, false, "f", _StoicConnection_isInternalConstructing); if (throwError) { throw new StoicTransportError("StoicTransport is not constructable"); } __classPrivateFieldSet(this, _StoicConnection_options, options, "f"); __classPrivateFieldSet(this, _StoicConnection_delegationChain, delegationChain, "f"); __classPrivateFieldSet(this, _StoicConnection_accounts, accounts, "f"); if (this.connected) { __classPrivateFieldGet(this, _StoicConnection_instances, "m", _StoicConnection_monitorDisconnect).call(this); } } get connected() { if (!__classPrivateFieldGet(this, _StoicConnection_delegationChain, "f")) { return false; } return isDelegationValid(__classPrivateFieldGet(this, _StoicConnection_delegationChain, "f")); } get identity() { return __classPrivateFieldGet(this, _StoicConnection_options, "f").identity; } get delegationChain() { return __classPrivateFieldGet(this, _StoicConnection_delegationChain, "f"); } get accounts() { return __classPrivateFieldGet(this, _StoicConnection_accounts, "f"); } static async create(options) { var _b, _c, _d, _e, _f, _g; const maxTimeToLive = (_b = options === null || options === void 0 ? void 0 : options.maxTimeToLive) !== null && _b !== void 0 ? _b : BigInt(8) * BigInt(3600000000000); const keyType = (_c = options === null || options === void 0 ? void 0 : options.keyType) !== null && _c !== void 0 ? _c : ECDSA_KEY_LABEL; const storage = (_d = options === null || options === void 0 ? void 0 : options.storage) !== null && _d !== void 0 ? _d : new IdbStorage(); const crypto = (_e = options === null || options === void 0 ? void 0 : options.crypto) !== null && _e !== void 0 ? _e : globalThis.crypto; const disconnectMonitoringInterval = (_f = options === null || options === void 0 ? void 0 : options.disconnectMonitoringInterval) !== null && _f !== void 0 ? _f : 3000; let identity = (_g = options === null || options === void 0 ? void 0 : options.identity) !== null && _g !== void 0 ? _g : (await getIdentity(IDENTITY_STORAGE_KEY, storage)); if (!identity) { const createdIdentity = await (keyType === "Ed25519" ? Ed25519KeyIdentity.generate(crypto.getRandomValues(new Uint8Array(32))) : ECDSAKeyIdentity.generate()); await setIdentity(IDENTITY_STORAGE_KEY, createdIdentity, storage); identity = createdIdentity; } const delegationChain = await getDelegationChain(DELEGATION_STORAGE_KEY, storage); const accounts = await storage.get(ACCOUNTS_STORAGE_KEY); __classPrivateFieldSet(_a, _a, true, "f", _StoicConnection_isInternalConstructing); return new _a({ maxTimeToLive, keyType, identity, storage, crypto, disconnectMonitoringInterval, }, delegationChain, accounts ? Number(accounts) : undefined); } async connect() { return new Promise(async (resolve, reject) => { __classPrivateFieldSet(this, _StoicConnection_delegationChain, undefined, "f"); const keypair = { current: await __classPrivateFieldGet(this, _StoicConnection_options, "f").crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-384", }, false, ["sign", "verify"]), }; const apikey = toHex(await __classPrivateFieldGet(this, _StoicConnection_options, "f").crypto.subtle.exportKey("spki", keypair.current.publicKey)); const tunnel = document.createElement("iframe"); tunnel.width = "0"; tunnel.height = "0"; tunnel.style.borderWidth = "0"; const delegation = new Delegation(__classPrivateFieldGet(this, _StoicConnection_options, "f").identity.getPublicKey().toDer(), BigInt(Date.now()) * BigInt(1000000) + __classPrivateFieldGet(this, _StoicConnection_options, "f").maxTimeToLive); let publicKey; const complete = async () => { window.removeEventListener("message", listener); document.body.removeChild(tunnel); await setDelegationChain(DELEGATION_STORAGE_KEY, __classPrivateFieldGet(this, _StoicConnection_delegationChain, "f"), __classPrivateFieldGet(this, _StoicConnection_options, "f").storage); await __classPrivateFieldGet(this, _StoicConnection_options, "f").storage.set(ACCOUNTS_STORAGE_KEY, `${__classPrivateFieldGet(this, _StoicConnection_accounts, "f")}`); __classPrivateFieldGet(this, _StoicConnection_instances, "m", _StoicConnection_monitorDisconnect).call(this); resolve(); }; const listener = (event) => { var _b; if (!stoicWindow || event.origin !== STOIC_ORIGIN) { return; } if (event.source === tunnel.contentWindow && event.data.target === "STOIC-EXT") { if (!event.data.success) { window.removeEventListener("message", listener); document.body.removeChild(tunnel); reject(new StoicTransportError(event.data.data)); return; } switch (event.data.action) { case "accounts": __classPrivateFieldSet(this, _StoicConnection_accounts, JSON.parse(event.data.data).length, "f"); if (__classPrivateFieldGet(this, _StoicConnection_delegationChain, "f")) { complete(); } break; case "sign": const data = JSON.parse(event.data.data); const signature = fromHex(data.signed); const previousDelegationChain = data.chain && DelegationChain.fromJSON(data.chain); __classPrivateFieldSet(this, _StoicConnection_delegationChain, DelegationChain.fromDelegations([ ...((_b = previousDelegationChain === null || previousDelegationChain === void 0 ? void 0 : previousDelegationChain.delegations) !== null && _b !== void 0 ? _b : []), { delegation, signature }, ], publicKey), "f"); if (__classPrivateFieldGet(this, _StoicConnection_accounts, "f")) { complete(); } break; } return; } if (event.source !== stoicWindow) { // All events below are expected to be received from stoicWindow return; } switch (event.data.action) { case "initiateStoicConnect": // Request connection when window indicates its ready stoicWindow.postMessage({ action: "requestAuthorization", apikey }, STOIC_ORIGIN); break; case "rejectAuthorization": // If the connection is rejected, throw an error stoicWindow.close(); window.removeEventListener("message", listener); reject(new StoicTransportError("Connection is rejected")); break; case "confirmAuthorization": // Get public key from event publicKey = new Uint8Array(Object.values(event.data.key)).buffer; const principal = Principal.selfAuthenticating(new Uint8Array(publicKey)).toText(); // Once the connection has been approved, close window // and create iframe to get accounts and a delegation. stoicWindow.close(); document.body.appendChild(tunnel); tunnel.onload = async () => { if (!tunnel.contentWindow) { reject(new StoicTransportError("Tunnel could not be established")); return; } // Request accounts tunnel.contentWindow.postMessage({ target: "STOIC-IFRAME", action: "accounts", payload: "accounts", principal, apikey, sig: toHex(await window.crypto.subtle.sign({ name: "ECDSA", hash: { name: "SHA-384" }, }, keypair.current.privateKey, new TextEncoder().encode("accounts"))), }, STOIC_ORIGIN); // Request delegation signature const challenge = toHex(new Uint8Array([ ...new TextEncoder().encode("\x1Aic-request-auth-delegation"), ...new Uint8Array(requestIdOf(Object.assign({}, delegation))), ]).buffer); tunnel.contentWindow.postMessage({ target: "STOIC-IFRAME", action: "sign", payload: challenge, principal, apikey, sig: toHex(await window.crypto.subtle.sign({ name: "ECDSA", hash: { name: "SHA-384" }, }, keypair.current.privateKey, new TextEncoder().encode(challenge))), }, STOIC_ORIGIN); // Delete key pair after usage since its considered an unacceptable risk // to keep a key around that gives full signing access to Stoic Wallet. // // It could be abused for example to create a delegation with an (in practice) // indefinite expiration. By creating a delegation with a definite expiration // and deleting the key pair, the risk is limited to a definite timeframe. // // Keep in mind other dapps could still abuse their own key pair for various malicious // purposes, Stoic Wallet should be considered insecure for holding high value assets. delete keypair.current; }; tunnel.src = new URL("?stoicTunnel", STOIC_ORIGIN).href; break; } }; window.addEventListener("message", listener); // Open window to request connection const stoicWindow = window.open(new URL("?authorizeApp", STOIC_ORIGIN), STOIC_WINDOW); }); } async disconnect() { clearInterval(__classPrivateFieldGet(this, _StoicConnection_disconnectMonitorInterval, "f")); await removeDelegationChain(DELEGATION_STORAGE_KEY, __classPrivateFieldGet(this, _StoicConnection_options, "f").storage); await __classPrivateFieldGet(this, _StoicConnection_options, "f").storage.remove(ACCOUNTS_STORAGE_KEY); __classPrivateFieldSet(this, _StoicConnection_delegationChain, undefined, "f"); __classPrivateFieldSet(this, _StoicConnection_accounts, undefined, "f"); __classPrivateFieldGet(this, _StoicConnection_disconnectListeners, "f").forEach((listener) => listener()); } addEventListener(event, listener) { switch (event) { case "disconnect": __classPrivateFieldGet(this, _StoicConnection_disconnectListeners, "f").add(listener); return () => { __classPrivateFieldGet(this, _StoicConnection_disconnectListeners, "f").delete(listener); }; } } } _a = StoicConnection, _StoicConnection_options = new WeakMap(), _StoicConnection_delegationChain = new WeakMap(), _StoicConnection_accounts = new WeakMap(), _StoicConnection_disconnectListeners = new WeakMap(), _StoicConnection_disconnectMonitorInterval = new WeakMap(), _StoicConnection_instances = new WeakSet(), _StoicConnection_monitorDisconnect = function _StoicConnection_monitorDisconnect() { __classPrivateFieldSet(this, _StoicConnection_disconnectMonitorInterval, setInterval(() => { if (!this.connected) { __classPrivateFieldGet(this, _StoicConnection_disconnectListeners, "f").forEach((listener) => listener()); clearInterval(__classPrivateFieldGet(this, _StoicConnection_disconnectMonitorInterval, "f")); } }, __classPrivateFieldGet(this, _StoicConnection_options, "f").disconnectMonitoringInterval), "f"); }; _StoicConnection_isInternalConstructing = { value: false }; //# sourceMappingURL=stoicConnection.js.map