UNPKG

herlina-kit

Version:

protocol untuk menghubungkan DApp Vexanium ke dompet Herlina

281 lines (249 loc) 7.66 kB
import {Base64u, IdentityProof, SigningRequest} from "@wharfkit/signing-request"; import { Action, Checksum512, Name, PermissionLevel, PublicKey, Serializer, Signature, SignedTransaction, Transaction } from "@wharfkit/antelope"; import {ABICache} from "@wharfkit/abicache"; import zlib from "pako"; import {loadSession, saveSession} from "./session-store.js"; export class WalletSession { static ChainID = "f9f432b1851b5c179d2091a96f593aaed50ec7466b74f89301f957a83e56ce1f"; #connection; #callbacks; #encodingOptions; /** * @type {PermissionLevel} */ #permissionLevel; #accountChangeListener; #closeListener; #errorListener; /** * * @param {import('peerjs').DataConnection} connection */ constructor(connection) { this.#connection = connection; this.#callbacks = new Map(); connection.on('data', this.#onDataReceived.bind(this)); connection.on('close', () => { if (this.#closeListener) this.#closeListener(); }); connection.on('error', (error) => { if (this.#errorListener) this.#errorListener(error); }); // keep alive setTimeout(this.#ping.bind(this), 15_000); } /** * * @param {ABICache} cache * @see transact */ setABICache(cache) { this.#encodingOptions = {zlib, abiProvider: cache}; } /** * listener dipanggil ketika ada perubahan akun * @param {(permission:PermissionLevel) => void} listener */ onAccountChange(listener) { this.#accountChangeListener = listener; } /** * listener dipanggil ketika terputus dengan wallet * @param {() => void} listener */ onClose(listener) { this.#closeListener = listener; } /** * listener dipanggil ketika terjadi error * @param {(error:Error) => void} listener */ onError(listener) { this.#errorListener = listener; } /** * apakah masih tersambung dengan wallet * @return {boolean} */ isOpen() { return this.#connection.open; } /** * putuskan sambungan dengan wallet */ close() { this.#connection.close(); } metadata() { return this.#connection.metadata; } /** * @return {PermissionLevel} */ get permissionLevel() { return this.#permissionLevel; } set permissionLevel(value) { this.#permissionLevel = value; } /** * @return {Name} */ get actor() { return this.#permissionLevel.actor; } /** * @return {Name} */ get permission() { return this.#permissionLevel.permission; } /** * @typedef TransactArguments * @property {Action} action transaksi dengan 1 action * @property {Array<Action>} actions transaksi dengan lebih dari 1 action * @property {Transaction} transaction transaksi */ /** * @typedef TransactOptions * @property {boolean} broadcast transaksi disiarkan ke blockchain atau hanya tanda tangan */ /** * membuat transaksi * @param {TransactArguments} args * @param {TransactOptions?} options * @return {Promise<SignedTransaction|SendTransactionResponse>} */ async transact(args, options) { args.chainId = WalletSession.ChainID; const willBroadcast = options && typeof options.broadcast !== 'undefined' ? options.broadcast : true; const request = await SigningRequest.create(args, this.#encodingOptions); request.setBroadcast(willBroadcast); const vsr = request.encode(true, false, "vsr:"); return this.signRequest(vsr); } /** * kirim signing request ke wallet. * siarkan transaksi ke blockchain secara langsung atau * hanya minta tanda tangan * * @param {string} vsr * @return {Promise<SendTransactionResponse|SignedTransaction>} */ signRequest(vsr) { const callback = window.crypto.randomUUID(); const data = { method: "signRequest", id: callback, params: {vsr: vsr} }; return new Promise((resolve, reject) => { const func = reply => { if (reply.code === 'SENT') { resolve(reply.result); } else if (reply.code === 'SIGNED') { resolve(SignedTransaction.from(reply.result)); } else { // ERROR | REJECT if (typeof reply.error === "string") { reject(new Error(reply.error)); } else { reject(reply.error); } } }; this.#callbacks.set(callback, func); this.#connection.send(data); }); } /** * minta tanda tangan * @param {string} message pesan yang ditanda tangani * @return {Promise<Signature>} */ signMessage(message) { const callback = window.crypto.randomUUID(); const data = { method: "signMessage", id: callback, params: {message} }; return new Promise((resolve, reject) => { const func = reply => { if (reply.code === 'SIGNED') { resolve(Signature.from(reply.result.signature)); } else { reject(new Error(reply.error.message)); } }; this.#callbacks.set(callback, func); this.#connection.send(data); }); } /** * membuat shared secret * @param {PublicKey} publicKey * @return {Promise<Checksum512>} */ sharedSecret(publicKey) { const callback = window.crypto.randomUUID(); const data = { method: "sharedSecret", id: callback, params: {key: publicKey.toString()} }; return new Promise((resolve, reject) => { const func = reply => { if (reply.code === 'CREATED') { resolve(Checksum512.from(reply.result.secret)); } else if (reply.code === 'ERROR') { reject(new Error(reply.error)); } }; this.#callbacks.set(callback, func); this.#connection.send(data); }); } #onChangeAccount(auth) { const proof = Serializer.decode({data: Base64u.decode(auth), type: IdentityProof}); this.permissionLevel = proof.signer; const session = loadSession(); if (session) { saveSession({ peerID: session.peerID, permission: proof.signer.toString(), expiration: proof.expiration, auth }); } if (this.#accountChangeListener) { this.#accountChangeListener(proof.signer); } } #ping() { const callback = window.crypto.randomUUID(); const data = { method: "ping", id: callback, params: {time: Date.now()} }; this.#connection.send(data); } /** * data diterima dari wallet * @param {Object} data */ #onDataReceived(data) { const callback = this.#callbacks.get(data.id); if (callback) { callback(data); this.#callbacks.delete(data.id); } if (data.code === "ACTIVE_ACCOUNT_CHANGED") { this.#onChangeAccount(data.result.auth); } if (data.code === "PONG") { setTimeout(this.#ping.bind(this), 15_000); } } }