UNPKG

@ar.io/sdk

Version:

[![codecov](https://codecov.io/gh/ar-io/ar-io-sdk/graph/badge.svg?token=7dXKcT7dJy)](https://codecov.io/gh/ar-io/ar-io-sdk)

221 lines (220 loc) 8.77 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AOProcess = void 0; /** * Copyright (C) 2022-2024 Permanent Data Solutions, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ const aoconnect_1 = require("@permaweb/aoconnect"); const base64_js_1 = require("../../utils/base64.js"); const index_js_1 = require("../../utils/index.js"); const json_js_1 = require("../../utils/json.js"); const version_js_1 = require("../../version.js"); const error_js_1 = require("../error.js"); const logger_js_1 = require("../logger.js"); class AOProcess { logger; ao; processId; constructor({ processId, ao = (0, aoconnect_1.connect)({ MODE: 'legacy', }), logger = logger_js_1.Logger.default, }) { this.processId = processId; this.logger = logger; this.ao = ao; } isMessageDataEmpty(messageData) { return (messageData === undefined || messageData === 'null' || // This is what the CU returns for 'nil' values that are json.encoded messageData === '' || messageData === null); } async read({ tags, retries = 3, fromAddress, }) { this.logger.debug(`Evaluating read interaction on process`, { tags, processId: this.processId, }); // map tags to inputs const dryRunInput = { process: this.processId, tags, }; if (fromAddress !== undefined) { dryRunInput['Owner'] = fromAddress; } let attempts = 0; let result = undefined; while (attempts < retries) { try { result = await this.ao.dryrun(dryRunInput); // break on successful return of result break; } catch (error) { attempts++; this.logger.debug(`Read attempt ${attempts} failed`, { error: error?.message, stack: error?.stack, tags, processId: this.processId, }); if (attempts >= retries) { this.logger.debug(`Maximum read attempts exceeded`, { error: error?.message, stack: error?.stack, tags, processId: this.processId, ao: JSON.stringify(this.ao), }); throw new Error(`Failed to evaluate a dry-run on process ${this.processId}.`); } // exponential backoff await new Promise((resolve) => setTimeout(resolve, 2 ** attempts * 1000)); } } if (result === undefined) { throw new Error('Unexpected error when evaluating read interaction'); } this.logger.debug(`Read interaction result`, { result, processId: this.processId, }); const error = (0, index_js_1.errorMessageFromOutput)(result); if (error !== undefined) { throw new Error(error); } if (result.Messages === undefined || result.Messages.length === 0) { this.logger.debug(`Empty result - process ${this.processId} does not support provided action.`, { result, tags, processId: this.processId, }); throw new Error(result.message || `Process ${this.processId} did not return a valid response. Response: ${JSON.stringify(result)}`); } const messageData = result.Messages?.[0]?.Data; // return undefined if no data is returned if (this.isMessageDataEmpty(messageData)) { return undefined; } const response = (0, json_js_1.safeDecode)(messageData); return response; } async send({ tags, data, signer, retries = 3, }) { let messageId; const anchor = (0, base64_js_1.getRandomText)(32); // anchor is a random text produce non-deterministic messages IDs when deterministic signers are provided (ETH) try { this.logger.debug(`Evaluating send interaction on contract`, { tags, data, processId: this.processId, }); /** * DO NOT retry messaging if a message was already sent. * This could result in a double entry-like condition when sending tokens for example. * If the message fails to send we will throw an error and the caller can retry. */ messageId = await this.ao.message({ process: this.processId, tags: [...tags, { name: 'AR-IO-SDK', value: version_js_1.version }], data, signer, anchor, }); this.logger.debug(`Sent message to process`, { messageId, processId: this.processId, anchor, }); } catch (error) { this.logger.debug('Error sending message to process', { error: error?.message, stack: error?.stack, processId: this.processId, tags, }); // throw the error so it can be handled by the caller throw error; } if (messageId === undefined) { throw new Error('Failed to send message to process.'); } // get the result of the message before returning, using retries to handle network errors/new process delays let result = undefined; let attempts = 0; while (attempts < retries) { try { result = await this.ao.result({ message: messageId, process: this.processId, }); this.logger.debug('Message result', { result, messageId, processId: this.processId, }); break; } catch (error) { attempts++; this.logger.debug('Retrying send interaction', { attempts, retries, error: error?.message, processId: this.processId, }); if (attempts >= retries) { this.logger.debug(`Message was sent to process ${this.processId} with id ${messageId} but result was not returned. Review transactions for more details.`, { error: error?.message, stack: error?.stack, tags, processId: this.processId, messageId, }); return { id: messageId }; } // exponential backoff await new Promise((resolve) => setTimeout(resolve, 2 ** attempts * 2000)); } } if (result === undefined) { this.logger.debug(`Message was sent to process ${this.processId} with id ${messageId} but the result was not returned. Review transactions for more details.`, { tags, processId: this.processId, messageId, }); return { id: messageId }; } const error = (0, index_js_1.errorMessageFromOutput)(result); if (error !== undefined) { throw new error_js_1.WriteInteractionError(error); } // check if there are any Messages in the output if (result.Messages?.length === 0 || result.Messages === undefined) { return { id: messageId }; } if (this.isMessageDataEmpty(result.Messages[0].Data)) { return { id: messageId }; } const resultData = (0, json_js_1.safeDecode)(result.Messages[0].Data); this.logger.debug('Message result data', { resultData, messageId, processId: this.processId, }); return { id: messageId, result: resultData }; } } exports.AOProcess = AOProcess;