UNPKG

@dfinity/agent

Version:

JavaScript and TypeScript library to interact with the Internet Computer

301 lines 14 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.encodePath = exports.fetchNodeKeys = exports.request = exports.CustomPath = void 0; const principal_1 = require("@dfinity/principal"); const errors_ts_1 = require("../errors.js"); const certificate_ts_1 = require("../certificate.js"); const cbor = __importStar(require("../cbor.js")); const leb_ts_1 = require("../utils/leb.js"); const utils_1 = require("@noble/hashes/utils"); /** * Interface to define a custom path. Nested paths will be represented as individual buffers, and can be created from text using TextEncoder. * @param {string} key the key to use to access the returned value in the canisterStatus map * @param {Uint8Array[]} path the path to the desired value, represented as an array of buffers * @param {string} decodeStrategy the strategy to use to decode the returned value */ class CustomPath { constructor(key, path, decodeStrategy) { this.key = key; this.path = path; this.decodeStrategy = decodeStrategy; } } exports.CustomPath = CustomPath; /** * Requests information from a canister's `read_state` endpoint. * Can be used to request information about the canister's controllers, time, module hash, candid interface, and more. * @param {CanisterStatusOptions} options The configuration for the canister status request. * @see {@link CanisterStatusOptions} for detailed options. * @returns {Promise<StatusMap>} A map populated with data from the requested paths. Each path is a key in the map, and the value is the data obtained from the certificate for that path. * @example * const status = await canisterStatus({ * paths: ['controllers', 'candid'], * ...options * }); * * const controllers = status.get('controllers'); */ const request = async (options) => { const { agent, paths, disableCertificateTimeVerification = false } = options; const canisterId = principal_1.Principal.from(options.canisterId); const uniquePaths = [...new Set(paths)]; const status = new Map(); const promises = uniquePaths.map((path, index) => { const encodedPath = (0, exports.encodePath)(path, canisterId); return (async () => { try { if (agent.rootKey === null) { throw errors_ts_1.ExternalError.fromCode(new errors_ts_1.MissingRootKeyErrorCode()); } const rootKey = agent.rootKey; const response = await agent.readState(canisterId, { paths: [encodedPath], }); const certificate = await certificate_ts_1.Certificate.create({ certificate: response.certificate, rootKey, canisterId, disableTimeVerification: disableCertificateTimeVerification, agent, }); const lookup = (cert, path) => { if (path === 'subnet') { const data = (0, exports.fetchNodeKeys)(response.certificate, canisterId, rootKey); return { path, data, }; } else { return { path, data: (0, certificate_ts_1.lookupResultToBuffer)(cert.lookup_path(encodedPath)), }; } }; // must pass in the rootKey if we have no delegation const { path, data } = lookup(certificate, uniquePaths[index]); if (!data) { // Typically, the cert lookup will throw console.warn(`Expected to find result for path ${path}, but instead found nothing.`); if (typeof path === 'string') { status.set(path, null); } else { status.set(path.key, null); } } else { switch (path) { case 'time': { status.set(path, (0, leb_ts_1.decodeTime)(data)); break; } case 'controllers': { status.set(path, decodeControllers(data)); break; } case 'module_hash': { status.set(path, (0, utils_1.bytesToHex)(data)); break; } case 'subnet': { status.set(path, data); break; } case 'candid': { status.set(path, new TextDecoder().decode(data)); break; } default: { // Check for CustomPath signature if (typeof path !== 'string' && 'key' in path && 'path' in path) { switch (path.decodeStrategy) { case 'raw': status.set(path.key, data); break; case 'leb128': { status.set(path.key, (0, leb_ts_1.decodeLeb128)(data)); break; } case 'cbor': { status.set(path.key, cbor.decode(data)); break; } case 'hex': { status.set(path.key, (0, utils_1.bytesToHex)(data)); break; } case 'utf-8': { status.set(path.key, new TextDecoder().decode(data)); } } } } } } } catch (error) { // Throw on certificate errors if (error instanceof errors_ts_1.AgentError && (error.hasCode(errors_ts_1.CertificateVerificationErrorCode) || error.hasCode(errors_ts_1.CertificateTimeErrorCode))) { throw error; } if (typeof path !== 'string' && 'key' in path && 'path' in path) { status.set(path.key, null); } else { status.set(path, null); } console.group(); console.warn(`Expected to find result for path ${path}, but instead found nothing.`); console.warn(error); console.groupEnd(); } })(); }); // Fetch all values separately, as each option can fail await Promise.all(promises); return status; }; exports.request = request; const fetchNodeKeys = (certificate, canisterId, root_key) => { if (!canisterId._isPrincipal) { throw errors_ts_1.InputError.fromCode(new errors_ts_1.UnexpectedErrorCode('Invalid canisterId')); } const cert = cbor.decode(certificate); const tree = cert.tree; let delegation = cert.delegation; let subnetId; if (delegation && delegation.subnet_id) { subnetId = principal_1.Principal.fromUint8Array(new Uint8Array(delegation.subnet_id)); } // On local replica, with System type subnet, there is no delegation else if (!delegation && typeof root_key !== 'undefined') { subnetId = principal_1.Principal.selfAuthenticating(new Uint8Array(root_key)); delegation = { subnet_id: subnetId.toUint8Array(), certificate: new Uint8Array(0), }; } // otherwise use default NNS subnet id else { subnetId = principal_1.Principal.selfAuthenticating(principal_1.Principal.fromText('tdb26-jop6k-aogll-7ltgs-eruif-6kk7m-qpktf-gdiqx-mxtrf-vb5e6-eqe').toUint8Array()); delegation = { subnet_id: subnetId.toUint8Array(), certificate: new Uint8Array(0), }; } const canisterInRange = (0, certificate_ts_1.check_canister_ranges)({ canisterId, subnetId, tree }); if (!canisterInRange) { throw errors_ts_1.TrustError.fromCode(new errors_ts_1.CertificateNotAuthorizedErrorCode(canisterId, subnetId)); } const subnetLookupResult = (0, certificate_ts_1.lookup_subtree)(['subnet', delegation.subnet_id, 'node'], tree); if (subnetLookupResult.status !== certificate_ts_1.LookupSubtreeStatus.Found) { throw errors_ts_1.ProtocolError.fromCode(new errors_ts_1.LookupErrorCode('Node not found', subnetLookupResult.status)); } if (subnetLookupResult.value instanceof Uint8Array) { throw errors_ts_1.UnknownError.fromCode(new errors_ts_1.HashTreeDecodeErrorCode('Invalid node tree')); } // The forks are all labeled trees with the <node_id> label const nodeForks = (0, certificate_ts_1.flatten_forks)(subnetLookupResult.value); const nodeKeys = new Map(); nodeForks.forEach(fork => { const node_id = principal_1.Principal.from(fork[1]).toText(); const publicKeyLookupResult = (0, certificate_ts_1.lookup_path)(['public_key'], fork[2]); if (publicKeyLookupResult.status !== certificate_ts_1.LookupPathStatus.Found) { throw errors_ts_1.ProtocolError.fromCode(new errors_ts_1.LookupErrorCode('Public key not found', publicKeyLookupResult.status)); } const derEncodedPublicKey = publicKeyLookupResult.value; if (derEncodedPublicKey.byteLength !== 44) { throw errors_ts_1.ProtocolError.fromCode(new errors_ts_1.DerKeyLengthMismatchErrorCode(44, derEncodedPublicKey.byteLength)); } else { nodeKeys.set(node_id, derEncodedPublicKey); } }); return { subnetId: principal_1.Principal.fromUint8Array(new Uint8Array(delegation.subnet_id)).toText(), nodeKeys, }; }; exports.fetchNodeKeys = fetchNodeKeys; const encodePath = (path, canisterId) => { const canisterUint8Array = canisterId.toUint8Array(); switch (path) { case 'time': return [(0, utils_1.utf8ToBytes)('time')]; case 'controllers': return [(0, utils_1.utf8ToBytes)('canister'), canisterUint8Array, (0, utils_1.utf8ToBytes)('controllers')]; case 'module_hash': return [(0, utils_1.utf8ToBytes)('canister'), canisterUint8Array, (0, utils_1.utf8ToBytes)('module_hash')]; case 'subnet': return [(0, utils_1.utf8ToBytes)('subnet')]; case 'candid': return [ (0, utils_1.utf8ToBytes)('canister'), canisterUint8Array, (0, utils_1.utf8ToBytes)('metadata'), (0, utils_1.utf8ToBytes)('candid:service'), ]; default: { // Check for CustomPath signature if ('key' in path && 'path' in path) { // For simplified metadata queries if (typeof path['path'] === 'string' || path['path'] instanceof Uint8Array) { const metaPath = path.path; const encoded = typeof metaPath === 'string' ? (0, utils_1.utf8ToBytes)(metaPath) : metaPath; return [(0, utils_1.utf8ToBytes)('canister'), canisterUint8Array, (0, utils_1.utf8ToBytes)('metadata'), encoded]; // For non-metadata, return the provided custompath } else { return path['path']; } } } } throw errors_ts_1.UnknownError.fromCode(new errors_ts_1.UnexpectedErrorCode(`Error while encoding your path for canister status. Please ensure that your path ${path} was formatted correctly.`)); }; exports.encodePath = encodePath; // Controllers are CBOR-encoded buffers const decodeControllers = (buf) => { const controllersRaw = cbor.decode(buf); return controllersRaw.map(buf => { return principal_1.Principal.fromUint8Array(buf); }); }; //# sourceMappingURL=index.js.map