UNPKG

@dfinity/agent

Version:

JavaScript and TypeScript library to interact with the Internet Computer

287 lines 13.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ACTOR_METHOD_WITH_CERTIFICATE = exports.ACTOR_METHOD_WITH_HTTP_DETAILS = exports.Actor = void 0; const index_ts_1 = require("./agent/index.js"); const errors_ts_1 = require("./errors.js"); const candid_1 = require("@dfinity/candid"); const index_ts_2 = require("./polling/index.js"); const principal_1 = require("@dfinity/principal"); const certificate_ts_1 = require("./certificate.js"); const index_ts_3 = require("./agent/http/index.js"); const utils_1 = require("@noble/hashes/utils"); const metadataSymbol = Symbol.for('ic-agent-metadata'); /** * An actor base class. An actor is an object containing only functions that will * return a promise. These functions are derived from the IDL definition. */ class Actor { /** * Get the Agent class this Actor would call, or undefined if the Actor would use * the default agent (global.ic.agent). * @param actor The actor to get the agent of. */ static agentOf(actor) { return actor[metadataSymbol].config.agent; } /** * Get the interface of an actor, in the form of an instance of a Service. * @param actor The actor to get the interface of. */ static interfaceOf(actor) { return actor[metadataSymbol].service; } static canisterIdOf(actor) { return principal_1.Principal.from(actor[metadataSymbol].config.canisterId); } static createActorClass(interfaceFactory, options) { const service = interfaceFactory({ IDL: candid_1.IDL }); class CanisterActor extends Actor { constructor(config) { if (!config.canisterId) { throw errors_ts_1.InputError.fromCode(new errors_ts_1.MissingCanisterIdErrorCode(config.canisterId)); } const canisterId = typeof config.canisterId === 'string' ? principal_1.Principal.fromText(config.canisterId) : config.canisterId; super({ config: { ...DEFAULT_ACTOR_CONFIG, ...config, canisterId, }, service, }); for (const [methodName, func] of service._fields) { if (options?.httpDetails) { func.annotations.push(exports.ACTOR_METHOD_WITH_HTTP_DETAILS); } if (options?.certificate) { func.annotations.push(exports.ACTOR_METHOD_WITH_CERTIFICATE); } this[methodName] = _createActorMethod(this, methodName, func, config.blsVerify); } } } return CanisterActor; } static createActor(interfaceFactory, configuration) { if (!configuration.canisterId) { throw errors_ts_1.InputError.fromCode(new errors_ts_1.MissingCanisterIdErrorCode(configuration.canisterId)); } return new (this.createActorClass(interfaceFactory))(configuration); } /** * Returns an actor with methods that return the http response details along with the result * @param interfaceFactory - the interface factory for the actor * @param configuration - the configuration for the actor * @deprecated - use createActor with actorClassOptions instead */ static createActorWithHttpDetails(interfaceFactory, configuration) { return new (this.createActorClass(interfaceFactory, { httpDetails: true }))(configuration); } /** * Returns an actor with methods that return the http response details along with the result * @param interfaceFactory - the interface factory for the actor * @param configuration - the configuration for the actor * @param actorClassOptions - options for the actor class extended details to return with the result */ static createActorWithExtendedDetails(interfaceFactory, configuration, actorClassOptions = { httpDetails: true, certificate: true, }) { return new (this.createActorClass(interfaceFactory, actorClassOptions))(configuration); } constructor(metadata) { this[metadataSymbol] = Object.freeze(metadata); } } exports.Actor = Actor; // IDL functions can have multiple return values, so decoding always // produces an array. Ensure that functions with single or zero return // values behave as expected. function decodeReturnValue(types, msg) { const returnValues = candid_1.IDL.decode(types, msg); switch (returnValues.length) { case 0: return undefined; case 1: return returnValues[0]; default: return returnValues; } } const DEFAULT_ACTOR_CONFIG = { pollingOptions: index_ts_2.DEFAULT_POLLING_OPTIONS, }; exports.ACTOR_METHOD_WITH_HTTP_DETAILS = 'http-details'; exports.ACTOR_METHOD_WITH_CERTIFICATE = 'certificate'; function _createActorMethod(actor, methodName, func, blsVerify) { let caller; if (func.annotations.includes('query') || func.annotations.includes('composite_query')) { caller = async (options, ...args) => { // First, if there's a config transformation, call it. options = { ...options, ...actor[metadataSymbol].config.queryTransform?.(methodName, args, { ...actor[metadataSymbol].config, ...options, }), }; const agent = options.agent || actor[metadataSymbol].config.agent || new index_ts_3.HttpAgent(); const cid = principal_1.Principal.from(options.canisterId || actor[metadataSymbol].config.canisterId); const arg = candid_1.IDL.encode(func.argTypes, args); const result = await agent.query(cid, { methodName, arg, effectiveCanisterId: options.effectiveCanisterId, }); const httpDetails = { ...result.httpDetails, requestDetails: result.requestDetails, }; switch (result.status) { case index_ts_1.QueryResponseStatus.Rejected: { const uncertifiedRejectErrorCode = new errors_ts_1.UncertifiedRejectErrorCode(result.requestId, result.reject_code, result.reject_message, result.error_code, result.signatures); uncertifiedRejectErrorCode.callContext = { canisterId: cid, methodName, httpDetails, }; throw errors_ts_1.RejectError.fromCode(uncertifiedRejectErrorCode); } case index_ts_1.QueryResponseStatus.Replied: return func.annotations.includes(exports.ACTOR_METHOD_WITH_HTTP_DETAILS) ? { httpDetails, result: decodeReturnValue(func.retTypes, result.reply.arg), } : decodeReturnValue(func.retTypes, result.reply.arg); } }; } else { caller = async (options, ...args) => { // First, if there's a config transformation, call it. options = { ...options, ...actor[metadataSymbol].config.callTransform?.(methodName, args, { ...actor[metadataSymbol].config, ...options, }), }; const agent = options.agent || actor[metadataSymbol].config.agent || index_ts_3.HttpAgent.createSync(); const { canisterId, effectiveCanisterId, pollingOptions } = { ...DEFAULT_ACTOR_CONFIG, ...actor[metadataSymbol].config, ...options, }; const cid = principal_1.Principal.from(canisterId); const ecid = effectiveCanisterId !== undefined ? principal_1.Principal.from(effectiveCanisterId) : cid; const arg = candid_1.IDL.encode(func.argTypes, args); const { requestId, response, requestDetails } = await agent.call(cid, { methodName, arg, effectiveCanisterId: ecid, nonce: options.nonce, }); let reply; let certificate; if ((0, index_ts_1.isV3ResponseBody)(response.body)) { if (agent.rootKey == null) { throw errors_ts_1.ExternalError.fromCode(new errors_ts_1.MissingRootKeyErrorCode()); } const cert = response.body.certificate; certificate = await certificate_ts_1.Certificate.create({ certificate: cert, rootKey: agent.rootKey, canisterId: ecid, blsVerify, agent, }); const path = [(0, utils_1.utf8ToBytes)('request_status'), requestId]; const status = new TextDecoder().decode((0, certificate_ts_1.lookupResultToBuffer)(certificate.lookup_path([...path, 'status']))); switch (status) { case 'replied': reply = (0, certificate_ts_1.lookupResultToBuffer)(certificate.lookup_path([...path, 'reply'])); break; case 'rejected': { // Find rejection details in the certificate const rejectCode = new Uint8Array((0, certificate_ts_1.lookupResultToBuffer)(certificate.lookup_path([...path, 'reject_code'])))[0]; const rejectMessage = new TextDecoder().decode((0, certificate_ts_1.lookupResultToBuffer)(certificate.lookup_path([...path, 'reject_message']))); const error_code_buf = (0, certificate_ts_1.lookupResultToBuffer)(certificate.lookup_path([...path, 'error_code'])); const error_code = error_code_buf ? new TextDecoder().decode(error_code_buf) : undefined; const certifiedRejectErrorCode = new errors_ts_1.CertifiedRejectErrorCode(requestId, rejectCode, rejectMessage, error_code); certifiedRejectErrorCode.callContext = { canisterId: cid, methodName, httpDetails: response, }; throw errors_ts_1.RejectError.fromCode(certifiedRejectErrorCode); } } } else if ((0, index_ts_1.isV2ResponseBody)(response.body)) { const { reject_code, reject_message, error_code } = response.body; const errorCode = new errors_ts_1.UncertifiedRejectUpdateErrorCode(requestId, reject_code, reject_message, error_code); errorCode.callContext = { canisterId: cid, methodName, httpDetails: response, }; throw errors_ts_1.RejectError.fromCode(errorCode); } // Fall back to polling if we receive an Accepted response code if (response.status === 202) { const pollOptions = { ...pollingOptions, blsVerify, }; // Contains the certificate and the reply from the boundary node const response = await (0, index_ts_2.pollForResponse)(agent, ecid, requestId, pollOptions); certificate = response.certificate; reply = response.reply; } const shouldIncludeHttpDetails = func.annotations.includes(exports.ACTOR_METHOD_WITH_HTTP_DETAILS); const shouldIncludeCertificate = func.annotations.includes(exports.ACTOR_METHOD_WITH_CERTIFICATE); const httpDetails = { ...response, requestDetails }; if (reply !== undefined) { if (shouldIncludeHttpDetails && shouldIncludeCertificate) { return { httpDetails, certificate, result: decodeReturnValue(func.retTypes, reply), }; } else if (shouldIncludeCertificate) { return { certificate, result: decodeReturnValue(func.retTypes, reply), }; } else if (shouldIncludeHttpDetails) { return { httpDetails, result: decodeReturnValue(func.retTypes, reply), }; } return decodeReturnValue(func.retTypes, reply); } else { const errorCode = new errors_ts_1.UnexpectedErrorCode(`Call was returned undefined. We cannot determine if the call was successful or not. Return types: [${func.retTypes.map(t => t.display()).join(',')}].`); errorCode.callContext = { canisterId: cid, methodName, httpDetails, }; throw errors_ts_1.UnknownError.fromCode(errorCode); } }; } const handler = (...args) => caller({}, ...args); handler.withOptions = (options) => (...args) => caller(options, ...args); return handler; } //# sourceMappingURL=actor.js.map