@dfinity/agent
Version:
JavaScript and TypeScript library to interact with the Internet Computer
124 lines • 5.09 kB
JavaScript
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