UNPKG

@memetic-block/ao-encrypted-messages

Version:

Send encrypted messages on AO. Like an encrypted voicemail.

246 lines (245 loc) 12.4 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.EncryptedMessages = void 0; const aoconnect_1 = require("@permaweb/aoconnect"); const tweetnacl_1 = __importDefault(require("tweetnacl")); const tweetnacl_util_1 = __importDefault(require("tweetnacl-util")); const arweave_1 = __importDefault(require("arweave")); const errors_1 = require("../errors"); class EncryptedMessages { static spawn(wallet, opts) { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c; const arweave = arweave_1.default.init({ host: ((_a = opts === null || opts === void 0 ? void 0 : opts.arweave) === null || _a === void 0 ? void 0 : _a.host) || 'arweave.net', port: ((_b = opts === null || opts === void 0 ? void 0 : opts.arweave) === null || _b === void 0 ? void 0 : _b.port) || 443, protocol: ((_c = opts === null || opts === void 0 ? void 0 : opts.arweave) === null || _c === void 0 ? void 0 : _c.protocol) || 'https' }); const signer = (0, aoconnect_1.createDataItemSigner)(wallet); const appName = (opts === null || opts === void 0 ? void 0 : opts.appName) || '@memeticblock/ao-encrypted-messages'; const luaSourceTxId = (opts === null || opts === void 0 ? void 0 : opts.luaSourceTxId) || EncryptedMessages.PUBLISHED_LUA_TX_ID; console.debug(`Fetching LUA Source code from tx id ${luaSourceTxId}`); const luaSource = yield arweave.transactions.getData(luaSourceTxId, { decode: true, string: true }); console.debug(`Spawning new AO process`); const processId = yield (0, aoconnect_1.spawn)({ module: (opts === null || opts === void 0 ? void 0 : opts.module) || EncryptedMessages.AOS_MODULE_ID, scheduler: (opts === null || opts === void 0 ? void 0 : opts.scheduler) || EncryptedMessages.SCHEDULER_ID, signer, tags: [ ...((opts === null || opts === void 0 ? void 0 : opts.tags) || []), { name: 'App-Name', value: appName } ] }); console.debug(`Sending Eval Action of LUA Source to process ${processId}`); yield EncryptedMessages.sendAosMessage({ processId, data: luaSource, signer, tags: [ { name: 'Action', value: 'Eval' }, { name: 'App-Name', value: appName }, { name: 'Source-Code-TX-ID', value: luaSourceTxId } ] }); return new EncryptedMessages(processId, wallet); }); } static sendAosMessage(_a) { return __awaiter(this, arguments, void 0, function* ({ processId, data, tags, signer }, retries = 3) { let attempts = 0; let lastError; while (attempts < retries) { try { console.debug(`Sending AO Message to process ${processId}`); const messageId = yield (0, aoconnect_1.message)({ process: processId, tags, data, signer }); console.debug(`Fetching AO Message result ${messageId} from process ${processId}`); const result = yield (0, aoconnect_1.result)({ message: messageId, process: processId }); console.debug(`Got AO Message result ${messageId} from process ${processId}`); console.dir(result, { depth: null }); return { messageId, result }; } catch (error) { console.error(`Error sending AO Message to process ${processId}`, error); if (error.message.includes('500')) { console.debug(`Retrying sending AO message to process ${processId}`, JSON.stringify({ attempts, retries, error: error.message }, undefined, 2)); // NB: Sleep between each attempt with exponential backoff yield new Promise(resolve => setTimeout(resolve, Math.pow(2, attempts) * 2000)); attempts++; lastError = error; } else throw error; } } throw lastError; }); } constructor(processId, wallet, arweave) { this.processId = processId; this.wallet = wallet; if (!arweave) { this.arweave = arweave_1.default.init({ host: 'arweave.net', port: 443, protocol: 'https' }); } } setWallet(wallet) { this.wallet = wallet; } getEncryptionPublicKey(tags) { return __awaiter(this, void 0, void 0, function* () { if (!this.wallet) { throw new errors_1.WalletNotSetError(); } const { messageId, result } = yield EncryptedMessages.sendAosMessage({ processId: this.processId, signer: (0, aoconnect_1.createDataItemSigner)(this.wallet), tags: [ { name: 'Action', value: 'Get-Encryption-Public-Key' }, ...(tags || []) ] }); const publicKey = result.Messages[0].Data; this.lastSeenEncryptionPublicKey = publicKey; return { messageId, result, publicKey }; }); } setEncryptionPublicKey(encryptionPublicKey, tags) { return __awaiter(this, void 0, void 0, function* () { if (!this.wallet) { throw new errors_1.WalletNotSetError(); } let publicKey = encryptionPublicKey; let secretKey; if (!publicKey) { const keyPair = tweetnacl_1.default.box.keyPair(); publicKey = tweetnacl_util_1.default.encodeBase64(keyPair.publicKey); secretKey = tweetnacl_util_1.default.encodeBase64(keyPair.secretKey); } const { messageId } = yield EncryptedMessages.sendAosMessage({ processId: this.processId, signer: (0, aoconnect_1.createDataItemSigner)(this.wallet), tags: [ { name: 'Action', value: 'Set-Encryption-Public-Key' }, { name: 'EncryptionPublicKey', value: publicKey }, ...(tags || []) ] }); this.lastSeenEncryptionPublicKey = publicKey; return { publicKey, secretKey, messageId }; }); } sendEncryptedMessage(message, opts, tags) { return __awaiter(this, void 0, void 0, function* () { if (!this.wallet) { throw new errors_1.WalletNotSetError(); } const { publicKey, secretKey } = (opts === null || opts === void 0 ? void 0 : opts.secretKey) ? tweetnacl_1.default.box.keyPair.fromSecretKey( // @ts-ignore typeof (opts === null || opts === void 0 ? void 0 : opts.secretKey) === 'string' ? Buffer.from(opts === null || opts === void 0 ? void 0 : opts.secretKey) : opts === null || opts === void 0 ? void 0 : opts.secretKey) : tweetnacl_1.default.box.keyPair(); const nonce = typeof (opts === null || opts === void 0 ? void 0 : opts.nonce) === 'string' ? Buffer.from(opts === null || opts === void 0 ? void 0 : opts.nonce) : tweetnacl_1.default.randomBytes(tweetnacl_1.default.box.nonceLength); if (!this.lastSeenEncryptionPublicKey) { yield this.getEncryptionPublicKey(); } if (!this.lastSeenEncryptionPublicKey) { throw new errors_1.EncryptionPublicKeyNotSetError(); } const encryptedMessage = tweetnacl_1.default.box(tweetnacl_util_1.default.decodeUTF8(message), // @ts-ignore nonce, tweetnacl_util_1.default.decodeBase64(this.lastSeenEncryptionPublicKey), secretKey); // @ts-ignore const nonceB64 = tweetnacl_util_1.default.encodeBase64(nonce); const encryptedMessageJson = JSON.stringify({ message: tweetnacl_util_1.default.encodeBase64(encryptedMessage), nonce: nonceB64, publicKey: tweetnacl_util_1.default.encodeBase64(publicKey), recipientPublicKey: this.lastSeenEncryptionPublicKey }); const { messageId, result } = yield EncryptedMessages.sendAosMessage({ processId: this.processId, signer: (0, aoconnect_1.createDataItemSigner)(this.wallet), tags: [ { name: 'Action', value: 'Send-Encrypted-Message' }, { name: 'Encrypted-Message-Nonce', value: nonceB64 }, ...(tags || []) ], data: encryptedMessageJson }); if (result.Error) { throw new errors_1.EncryptedMessagesLuaProcessError(result.Error); } return { messageId, nonce: nonceB64 }; }); } listEncryptedMessages(tags) { return __awaiter(this, void 0, void 0, function* () { if (!this.wallet) { throw new errors_1.WalletNotSetError(); } const { messageId, result } = yield EncryptedMessages.sendAosMessage({ processId: this.processId, signer: (0, aoconnect_1.createDataItemSigner)(this.wallet), tags: [ { name: 'Action', value: 'List-Encrypted-Messages' }, ...(tags || []) ] }); const messages = JSON.parse(result.Messages[0].Data); return { messageId, messages }; }); } getEncryptedMessage(messageId, secretKey) { return __awaiter(this, void 0, void 0, function* () { if (!messageId) { throw new errors_1.MessageIdRequiredError(); } const { data: encryptedMessage } = yield this.arweave.api.get(`/${messageId}`); if (!encryptedMessage) { throw new errors_1.EncryptedMessageNotFoundError(messageId); } if (secretKey) { const decryptedMessage = tweetnacl_1.default.box.open(tweetnacl_util_1.default.decodeBase64(encryptedMessage.message), tweetnacl_util_1.default.decodeBase64(encryptedMessage.nonce), tweetnacl_util_1.default.decodeBase64(encryptedMessage.publicKey), tweetnacl_util_1.default.decodeBase64(secretKey)); if (decryptedMessage) { encryptedMessage.message = Buffer.from(decryptedMessage).toString(); } } return encryptedMessage; }); } } exports.EncryptedMessages = EncryptedMessages; EncryptedMessages.SCHEDULER_ID = '_GQ33BkPtZrqxA84vM8Zk-N2aO0toNNu_C-l-rawrBA'; EncryptedMessages.AOS_MODULE_ID = 'cbn0KKrBZH7hdNkNokuXLtGryrWM--PjSTBqIzw9Kkk'; EncryptedMessages.PUBLISHED_LUA_TX_ID = 'p5zkcW3sysfkGrkN9oc_DfVNQ9PkI3hsb-8CyPeZZdg';