@slide-computer/signer-agent
Version:
Initiate transactions with signers on the Internet Computer
250 lines • 16.3 kB
JavaScript
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