UNPKG

@dfinity/agent

Version:

JavaScript and TypeScript library to interact with the Internet Computer

124 lines 5.09 kB
import { lebEncode } from '@dfinity/candid'; import { Endpoint, makeNonce, } from "./types.js"; import { ExpiryJsonDeserializeErrorCode, InputError } from "../../errors.js"; export const JSON_KEY_EXPIRY = '__expiry__'; const SECONDS_TO_MILLISECONDS = BigInt(1_000); const MILLISECONDS_TO_NANOSECONDS = BigInt(1_000_000); const MINUTES_TO_SECONDS = BigInt(60); const EXPIRY_DELTA_THRESHOLD_MILLISECONDS = BigInt(90) * SECONDS_TO_MILLISECONDS; function roundMillisToSeconds(millis) { return millis / SECONDS_TO_MILLISECONDS; } function roundMillisToMinutes(millis) { return roundMillisToSeconds(millis) / MINUTES_TO_SECONDS; } export class Expiry { constructor(__expiry__) { this.__expiry__ = __expiry__; this._isExpiry = true; } /** * Creates an Expiry object from a delta in milliseconds. * If the delta is less than 90 seconds, the expiry is rounded down to the nearest second. * Otherwise, the expiry is rounded down to the nearest minute. * @param deltaInMs The milliseconds to add to the current time. * @param clockDriftMs The milliseconds to add to the current time, typically the clock drift between IC network clock and the client's clock. Defaults to `0` if not provided. * @returns {Expiry} The constructed Expiry object. */ static fromDeltaInMilliseconds(deltaInMs, clockDriftMs = 0) { const deltaMs = BigInt(deltaInMs); const expiryMs = BigInt(Date.now()) + deltaMs + BigInt(clockDriftMs); let roundedExpirySeconds; if (deltaMs < EXPIRY_DELTA_THRESHOLD_MILLISECONDS) { roundedExpirySeconds = roundMillisToSeconds(expiryMs); } else { const roundedExpiryMinutes = roundMillisToMinutes(expiryMs); roundedExpirySeconds = roundedExpiryMinutes * MINUTES_TO_SECONDS; } return new Expiry(roundedExpirySeconds * SECONDS_TO_MILLISECONDS * MILLISECONDS_TO_NANOSECONDS); } toBigInt() { return this.__expiry__; } toHash() { return lebEncode(this.__expiry__); } toString() { return this.__expiry__.toString(); } /** * Serializes to JSON * @returns {JsonnableExpiry} a JSON object with a single key, {@link JSON_KEY_EXPIRY}, whose value is the expiry as a string */ toJSON() { return { [JSON_KEY_EXPIRY]: this.toString() }; } /** * Deserializes a {@link JsonnableExpiry} object from a JSON string. * @param input The JSON string to deserialize. * @returns {Expiry} The deserialized Expiry object. */ static fromJSON(input) { const obj = JSON.parse(input); if (obj[JSON_KEY_EXPIRY]) { try { const expiry = BigInt(obj[JSON_KEY_EXPIRY]); return new Expiry(expiry); } catch (error) { throw new InputError(new ExpiryJsonDeserializeErrorCode(`Not a valid BigInt: ${error}`)); } } throw new InputError(new ExpiryJsonDeserializeErrorCode(`The input does not contain the key ${JSON_KEY_EXPIRY}`)); } static isExpiry(other) { return (other instanceof Expiry || (typeof other === 'object' && other !== null && '_isExpiry' in other && other['_isExpiry'] === true && '__expiry__' in other && typeof other['__expiry__'] === 'bigint')); } } /** * Create a Nonce transform, which takes a function that returns a Buffer, and adds it * as the nonce to every call requests. * @param nonceFn A function that returns a buffer. By default uses a semi-random method. */ export function makeNonceTransform(nonceFn = makeNonce) { return async (request) => { // Nonce needs to be inserted into the header for all requests, to enable logs to be correlated with requests. const headers = request.request.headers; // TODO: uncomment this when the http proxy supports it. // headers.set('X-IC-Request-ID', toHex(new Uint8Array(nonce))); request.request.headers = headers; // Nonce only needs to be inserted into the body for async calls, to prevent replay attacks. if (request.endpoint === Endpoint.Call) { request.body.nonce = nonceFn(); } }; } /** * Create a transform that adds a delay (by default 5 minutes) to the expiry. * @param delayInMilliseconds The delay to add to the call time, in milliseconds. */ export function makeExpiryTransform(delayInMilliseconds) { return async (request) => { request.body.ingress_expiry = Expiry.fromDeltaInMilliseconds(delayInMilliseconds); }; } /** * Maps the default fetch headers field to the serializable HttpHeaderField. * @param headers Fetch definition of the headers type * @returns array of header fields */ export function httpHeadersTransform(headers) { const headerFields = []; headers.forEach((value, key) => { headerFields.push([key, value]); }); return headerFields; } //# sourceMappingURL=transforms.js.map