@ar.io/sdk
Version:
[](https://codecov.io/gh/ar-io/ar-io-sdk)
221 lines (220 loc) • 8.77 kB
JavaScript
"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;