@dfinity/agent
Advanced tools
Comparing version 2.0.0-beta.0 to 2.0.0-beta.1
@@ -27,3 +27,2 @@ import { JsonObject } from '@dfinity/candid'; | ||
export interface HttpAgentOptions { | ||
source?: HttpAgent; | ||
fetch?: typeof fetch; | ||
@@ -66,17 +65,30 @@ fetchOptions?: Record<string, unknown>; | ||
} | ||
interface V1HttpAgentInterface { | ||
_identity: Promise<Identity> | null; | ||
readonly _fetch: typeof fetch; | ||
readonly _fetchOptions?: Record<string, unknown>; | ||
readonly _callOptions?: Record<string, unknown>; | ||
readonly _host: URL; | ||
readonly _credentials: string | undefined; | ||
readonly _retryTimes: number; | ||
_isAgent: true; | ||
} | ||
export declare class HttpAgent implements Agent { | ||
#private; | ||
rootKey: ArrayBuffer; | ||
private _identity; | ||
private readonly _fetch; | ||
private readonly _fetchOptions?; | ||
private readonly _callOptions?; | ||
private _timeDiffMsecs; | ||
private readonly _host; | ||
private readonly _credentials; | ||
private _rootKeyFetched; | ||
readonly host: URL; | ||
readonly _isAgent = true; | ||
config: HttpAgentOptions; | ||
get waterMark(): number; | ||
log: ObservableLog; | ||
/** | ||
* @param options - Options for the HttpAgent | ||
* @deprecated Use `HttpAgent.create` or `HttpAgent.createSync` instead | ||
*/ | ||
constructor(options?: HttpAgentOptions); | ||
static createSync(options?: HttpAgentOptions): HttpAgent; | ||
static create(options?: HttpAgentOptions & { | ||
shouldFetchRootKey?: boolean; | ||
}): Promise<HttpAgent>; | ||
static from(agent: Pick<HttpAgent, 'config'> | V1HttpAgentInterface): Promise<HttpAgent>; | ||
isLocal(): boolean; | ||
@@ -83,0 +95,0 @@ addTransform(type: 'update' | 'query', fn: HttpAgentRequestTransformFn, priority?: number): void; |
@@ -39,3 +39,3 @@ "use strict"; | ||
}; | ||
var _HttpAgent_instances, _HttpAgent_retryTimes, _HttpAgent_backoffStrategy, _HttpAgent_waterMark, _HttpAgent_queryPipeline, _HttpAgent_updatePipeline, _HttpAgent_subnetKeys, _HttpAgent_verifyQuerySignatures, _HttpAgent_requestAndRetryQuery, _HttpAgent_requestAndRetry, _HttpAgent_verifyQueryResponse; | ||
var _HttpAgent_instances, _HttpAgent_identity, _HttpAgent_fetch, _HttpAgent_fetchOptions, _HttpAgent_callOptions, _HttpAgent_timeDiffMsecs, _HttpAgent_credentials, _HttpAgent_rootKeyFetched, _HttpAgent_retryTimes, _HttpAgent_backoffStrategy, _HttpAgent_waterMark, _HttpAgent_queryPipeline, _HttpAgent_updatePipeline, _HttpAgent_subnetKeys, _HttpAgent_verifyQuerySignatures, _HttpAgent_requestAndRetryQuery, _HttpAgent_requestAndRetry, _HttpAgent_verifyQueryResponse; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -130,2 +130,37 @@ exports.HttpAgent = exports.IdentityInvalidError = exports.MANAGEMENT_CANISTER_ID = exports.IC_ROOT_KEY = exports.RequestStatusResponseStatus = exports.makeNonce = void 0; | ||
} | ||
function determineHost(configuredHost) { | ||
let host; | ||
if (configuredHost !== undefined) { | ||
if (!configuredHost.match(/^[a-z]+:/) && typeof window !== 'undefined') { | ||
host = new URL(window.location.protocol + '//' + configuredHost); | ||
} | ||
else { | ||
host = new URL(configuredHost); | ||
} | ||
} | ||
else { | ||
// Mainnet, local, and remote environments will have the api route available | ||
const knownHosts = ['ic0.app', 'icp0.io', '127.0.0.1', 'localhost']; | ||
const remoteHosts = ['.github.dev', '.gitpod.io']; | ||
const location = typeof window !== 'undefined' ? window.location : undefined; | ||
const hostname = location === null || location === void 0 ? void 0 : location.hostname; | ||
let knownHost; | ||
if (hostname && typeof hostname === 'string') { | ||
if (remoteHosts.some(host => hostname.endsWith(host))) { | ||
knownHost = hostname; | ||
} | ||
else { | ||
knownHost = knownHosts.find(host => hostname.endsWith(host)); | ||
} | ||
} | ||
if (location && knownHost) { | ||
// If the user is on a boundary-node provided host, we can use the same host for the agent | ||
host = new URL(`${location.protocol}//${knownHost}${location.port ? ':' + location.port : ''}`); | ||
} | ||
else { | ||
host = new URL('https://icp-api.io'); | ||
} | ||
} | ||
return host.toString(); | ||
} | ||
// A HTTP agent allows users to interact with a client of the internet computer | ||
@@ -141,2 +176,6 @@ // using the available methods. It exposes an API that closely follows the | ||
class HttpAgent { | ||
/** | ||
* @param options - Options for the HttpAgent | ||
* @deprecated Use `HttpAgent.create` or `HttpAgent.createSync` instead | ||
*/ | ||
constructor(options = {}) { | ||
@@ -146,7 +185,14 @@ var _a; | ||
this.rootKey = (0, buffer_1.fromHex)(exports.IC_ROOT_KEY); | ||
this._timeDiffMsecs = 0; | ||
this._rootKeyFetched = false; | ||
_HttpAgent_identity.set(this, void 0); | ||
_HttpAgent_fetch.set(this, void 0); | ||
_HttpAgent_fetchOptions.set(this, void 0); | ||
_HttpAgent_callOptions.set(this, void 0); | ||
_HttpAgent_timeDiffMsecs.set(this, 0); | ||
_HttpAgent_credentials.set(this, void 0); | ||
_HttpAgent_rootKeyFetched.set(this, false); | ||
_HttpAgent_retryTimes.set(this, void 0); // Retry requests N times before erroring by default | ||
_HttpAgent_backoffStrategy.set(this, void 0); | ||
// Public signature to help with type checking. | ||
this._isAgent = true; | ||
this.config = {}; | ||
// The UTC time in milliseconds when the latest request was made | ||
@@ -219,56 +265,8 @@ _HttpAgent_waterMark.set(this, 0); | ||
}); | ||
if (options.source) { | ||
if (!(options.source instanceof HttpAgent)) { | ||
throw new Error("An Agent's source can only be another HttpAgent"); | ||
} | ||
this._identity = options.source._identity; | ||
this._fetch = options.source._fetch; | ||
this._host = options.source._host; | ||
this._credentials = options.source._credentials; | ||
} | ||
else { | ||
this._fetch = options.fetch || getDefaultFetch() || fetch.bind(global); | ||
this._fetchOptions = options.fetchOptions; | ||
this._callOptions = options.callOptions; | ||
} | ||
if (options.host !== undefined) { | ||
if (!options.host.match(/^[a-z]+:/) && typeof window !== 'undefined') { | ||
this._host = new URL(window.location.protocol + '//' + options.host); | ||
} | ||
else { | ||
this._host = new URL(options.host); | ||
} | ||
} | ||
else if (options.source !== undefined) { | ||
// Safe to ignore here. | ||
this._host = options.source._host; | ||
} | ||
else { | ||
const location = typeof window !== 'undefined' ? window.location : undefined; | ||
if (!location) { | ||
this._host = new URL('https://icp-api.io'); | ||
this.log.warn('Could not infer host from window.location, defaulting to mainnet gateway of https://icp-api.io. Please provide a host to the HttpAgent constructor to avoid this warning.'); | ||
} | ||
// Mainnet, local, and remote environments will have the api route available | ||
const knownHosts = ['ic0.app', 'icp0.io', '127.0.0.1', 'localhost']; | ||
const remoteHosts = ['.github.dev', '.gitpod.io']; | ||
const hostname = location === null || location === void 0 ? void 0 : location.hostname; | ||
let knownHost; | ||
if (hostname && typeof hostname === 'string') { | ||
if (remoteHosts.some(host => hostname.endsWith(host))) { | ||
knownHost = hostname; | ||
} | ||
else { | ||
knownHost = knownHosts.find(host => hostname.endsWith(host)); | ||
} | ||
} | ||
if (location && knownHost) { | ||
// If the user is on a boundary-node provided host, we can use the same host for the agent | ||
this._host = new URL(`${location.protocol}//${knownHost}${location.port ? ':' + location.port : ''}`); | ||
} | ||
else { | ||
this._host = new URL('https://icp-api.io'); | ||
this.log.warn('Could not infer host from window.location, defaulting to mainnet gateway of https://icp-api.io. Please provide a host to the HttpAgent constructor to avoid this warning.'); | ||
} | ||
} | ||
this.config = options; | ||
__classPrivateFieldSet(this, _HttpAgent_fetch, options.fetch || getDefaultFetch() || fetch.bind(global), "f"); | ||
__classPrivateFieldSet(this, _HttpAgent_fetchOptions, options.fetchOptions, "f"); | ||
__classPrivateFieldSet(this, _HttpAgent_callOptions, options.callOptions, "f"); | ||
const host = determineHost(options.host); | ||
this.host = new URL(host); | ||
if (options.verifyQuerySignatures !== undefined) { | ||
@@ -285,16 +283,16 @@ __classPrivateFieldSet(this, _HttpAgent_verifyQuerySignatures, options.verifyQuerySignatures, "f"); | ||
// Rewrite to avoid redirects | ||
if (this._host.hostname.endsWith(IC0_SUB_DOMAIN)) { | ||
this._host.hostname = IC0_DOMAIN; | ||
if (this.host.hostname.endsWith(IC0_SUB_DOMAIN)) { | ||
this.host.hostname = IC0_DOMAIN; | ||
} | ||
else if (this._host.hostname.endsWith(ICP0_SUB_DOMAIN)) { | ||
this._host.hostname = ICP0_DOMAIN; | ||
else if (this.host.hostname.endsWith(ICP0_SUB_DOMAIN)) { | ||
this.host.hostname = ICP0_DOMAIN; | ||
} | ||
else if (this._host.hostname.endsWith(ICP_API_SUB_DOMAIN)) { | ||
this._host.hostname = ICP_API_DOMAIN; | ||
else if (this.host.hostname.endsWith(ICP_API_SUB_DOMAIN)) { | ||
this.host.hostname = ICP_API_DOMAIN; | ||
} | ||
if (options.credentials) { | ||
const { name, password } = options.credentials; | ||
this._credentials = `${name}${password ? ':' + password : ''}`; | ||
__classPrivateFieldSet(this, _HttpAgent_credentials, `${name}${password ? ':' + password : ''}`, "f"); | ||
} | ||
this._identity = Promise.resolve(options.identity || new auth_1.AnonymousIdentity()); | ||
__classPrivateFieldSet(this, _HttpAgent_identity, Promise.resolve(options.identity || new auth_1.AnonymousIdentity()), "f"); | ||
// Add a nonce transform to ensure calls are unique | ||
@@ -322,4 +320,36 @@ this.addTransform('update', (0, transforms_1.makeNonceTransform)(types_1.makeNonce)); | ||
} | ||
static createSync(options = {}) { | ||
return new this(Object.assign({}, options)); | ||
} | ||
static async create(options = { | ||
shouldFetchRootKey: false, | ||
}) { | ||
const agent = HttpAgent.createSync(options); | ||
const initPromises = [agent.syncTime()]; | ||
if (agent.host.toString() !== 'https://icp-api.io' && options.shouldFetchRootKey) { | ||
initPromises.push(agent.fetchRootKey()); | ||
} | ||
await Promise.all(initPromises); | ||
return agent; | ||
} | ||
static async from(agent) { | ||
var _a; | ||
try { | ||
if ('config' in agent) { | ||
return await HttpAgent.create(agent.config); | ||
} | ||
return await HttpAgent.create({ | ||
fetch: agent._fetch, | ||
fetchOptions: agent._fetchOptions, | ||
callOptions: agent._callOptions, | ||
host: agent._host.toString(), | ||
identity: (_a = agent._identity) !== null && _a !== void 0 ? _a : undefined, | ||
}); | ||
} | ||
catch (error) { | ||
throw new errors_1.AgentError('Failed to create agent from provided agent'); | ||
} | ||
} | ||
isLocal() { | ||
const hostname = this._host.hostname; | ||
const hostname = this.host.hostname; | ||
return hostname === '127.0.0.1' || hostname.endsWith('127.0.0.1'); | ||
@@ -340,9 +370,9 @@ } | ||
async getPrincipal() { | ||
if (!this._identity) { | ||
if (!__classPrivateFieldGet(this, _HttpAgent_identity, "f")) { | ||
throw new IdentityInvalidError("This identity has expired due this application's security policy. Please refresh your authentication."); | ||
} | ||
return (await this._identity).getPrincipal(); | ||
return (await __classPrivateFieldGet(this, _HttpAgent_identity, "f")).getPrincipal(); | ||
} | ||
async call(canisterId, options, identity) { | ||
const id = await (identity !== undefined ? await identity : await this._identity); | ||
const id = await (identity !== undefined ? await identity : await __classPrivateFieldGet(this, _HttpAgent_identity, "f")); | ||
if (!id) { | ||
@@ -358,4 +388,4 @@ throw new IdentityInvalidError("This identity has expired due this application's security policy. Please refresh your authentication."); | ||
// If the value is off by more than 30 seconds, reconcile system time with the network | ||
if (Math.abs(this._timeDiffMsecs) > 1000 * 30) { | ||
ingress_expiry = new transforms_1.Expiry(DEFAULT_INGRESS_EXPIRY_DELTA_IN_MSECS + this._timeDiffMsecs); | ||
if (Math.abs(__classPrivateFieldGet(this, _HttpAgent_timeDiffMsecs, "f")) > 1000 * 30) { | ||
ingress_expiry = new transforms_1.Expiry(DEFAULT_INGRESS_EXPIRY_DELTA_IN_MSECS + __classPrivateFieldGet(this, _HttpAgent_timeDiffMsecs, "f")); | ||
} | ||
@@ -375,3 +405,3 @@ const submit = { | ||
method: 'POST', | ||
headers: Object.assign({ 'Content-Type': 'application/cbor' }, (this._credentials ? { Authorization: 'Basic ' + btoa(this._credentials) } : {})), | ||
headers: Object.assign({ 'Content-Type': 'application/cbor' }, (__classPrivateFieldGet(this, _HttpAgent_credentials, "f") ? { Authorization: 'Basic ' + btoa(__classPrivateFieldGet(this, _HttpAgent_credentials, "f")) } : {})), | ||
}, | ||
@@ -396,3 +426,3 @@ endpoint: "call" /* Endpoint.Call */, | ||
const request = __classPrivateFieldGet(this, _HttpAgent_instances, "m", _HttpAgent_requestAndRetry).call(this, { | ||
request: () => this._fetch('' + new URL(`/api/v2/canister/${ecid.toText()}/call`, this._host), Object.assign(Object.assign(Object.assign({}, this._callOptions), transformedRequest.request), { body })), | ||
request: () => __classPrivateFieldGet(this, _HttpAgent_fetch, "f").call(this, '' + new URL(`/api/v2/canister/${ecid.toText()}/call`, this.host), Object.assign(Object.assign(Object.assign({}, __classPrivateFieldGet(this, _HttpAgent_callOptions, "f")), transformedRequest.request), { body })), | ||
backoff, | ||
@@ -424,3 +454,3 @@ tries: 0, | ||
const makeQuery = async () => { | ||
const id = await (identity !== undefined ? await identity : await this._identity); | ||
const id = await (identity !== undefined ? await identity : await __classPrivateFieldGet(this, _HttpAgent_identity, "f")); | ||
if (!id) { | ||
@@ -445,3 +475,3 @@ throw new IdentityInvalidError("This identity has expired due this application's security policy. Please refresh your authentication."); | ||
method: 'POST', | ||
headers: Object.assign({ 'Content-Type': 'application/cbor' }, (this._credentials ? { Authorization: 'Basic ' + btoa(this._credentials) } : {})), | ||
headers: Object.assign({ 'Content-Type': 'application/cbor' }, (__classPrivateFieldGet(this, _HttpAgent_credentials, "f") ? { Authorization: 'Basic ' + btoa(__classPrivateFieldGet(this, _HttpAgent_credentials, "f")) } : {})), | ||
}, | ||
@@ -505,3 +535,3 @@ endpoint: "read" /* Endpoint.Query */, | ||
async createReadStateRequest(fields, identity) { | ||
const id = await (identity !== undefined ? await identity : await this._identity); | ||
const id = await (identity !== undefined ? await identity : await __classPrivateFieldGet(this, _HttpAgent_identity, "f")); | ||
if (!id) { | ||
@@ -516,3 +546,3 @@ throw new IdentityInvalidError("This identity has expired due this application's security policy. Please refresh your authentication."); | ||
method: 'POST', | ||
headers: Object.assign({ 'Content-Type': 'application/cbor' }, (this._credentials ? { Authorization: 'Basic ' + btoa(this._credentials) } : {})), | ||
headers: Object.assign({ 'Content-Type': 'application/cbor' }, (__classPrivateFieldGet(this, _HttpAgent_credentials, "f") ? { Authorization: 'Basic ' + btoa(__classPrivateFieldGet(this, _HttpAgent_credentials, "f")) } : {})), | ||
}, | ||
@@ -540,3 +570,3 @@ endpoint: "read_state" /* Endpoint.ReadState */, | ||
const response = await __classPrivateFieldGet(this, _HttpAgent_instances, "m", _HttpAgent_requestAndRetry).call(this, { | ||
request: () => this._fetch('' + new URL(`/api/v2/canister/${canister.toString()}/read_state`, this._host), Object.assign(Object.assign(Object.assign({}, this._fetchOptions), transformedRequest.request), { body })), | ||
request: () => __classPrivateFieldGet(this, _HttpAgent_fetch, "f").call(this, '' + new URL(`/api/v2/canister/${canister.toString()}/read_state`, this.host), Object.assign(Object.assign(Object.assign({}, __classPrivateFieldGet(this, _HttpAgent_fetchOptions, "f")), transformedRequest.request), { body })), | ||
backoff, | ||
@@ -605,3 +635,3 @@ tries: 0, | ||
if (replicaTime) { | ||
this._timeDiffMsecs = Number(replicaTime) - Number(callTime); | ||
__classPrivateFieldSet(this, _HttpAgent_timeDiffMsecs, Number(replicaTime) - Number(callTime), "f"); | ||
} | ||
@@ -614,5 +644,5 @@ } | ||
async status() { | ||
const headers = this._credentials | ||
const headers = __classPrivateFieldGet(this, _HttpAgent_credentials, "f") | ||
? { | ||
Authorization: 'Basic ' + btoa(this._credentials), | ||
Authorization: 'Basic ' + btoa(__classPrivateFieldGet(this, _HttpAgent_credentials, "f")), | ||
} | ||
@@ -624,3 +654,3 @@ : {}; | ||
backoff, | ||
request: () => this._fetch('' + new URL(`/api/v2/status`, this._host), Object.assign({ headers }, this._fetchOptions)), | ||
request: () => __classPrivateFieldGet(this, _HttpAgent_fetch, "f").call(this, '' + new URL(`/api/v2/status`, this.host), Object.assign({ headers }, __classPrivateFieldGet(this, _HttpAgent_fetchOptions, "f"))), | ||
tries: 0, | ||
@@ -631,6 +661,6 @@ }); | ||
async fetchRootKey() { | ||
if (!this._rootKeyFetched) { | ||
if (!__classPrivateFieldGet(this, _HttpAgent_rootKeyFetched, "f")) { | ||
// Hex-encoded version of the replica root key | ||
this.rootKey = (await this.status()).root_key; | ||
this._rootKeyFetched = true; | ||
__classPrivateFieldSet(this, _HttpAgent_rootKeyFetched, true, "f"); | ||
} | ||
@@ -640,6 +670,6 @@ return this.rootKey; | ||
invalidateIdentity() { | ||
this._identity = null; | ||
__classPrivateFieldSet(this, _HttpAgent_identity, null, "f"); | ||
} | ||
replaceIdentity(identity) { | ||
this._identity = Promise.resolve(identity); | ||
__classPrivateFieldSet(this, _HttpAgent_identity, Promise.resolve(identity), "f"); | ||
} | ||
@@ -677,3 +707,3 @@ async fetchSubnetKeys(canisterId) { | ||
exports.HttpAgent = HttpAgent; | ||
_HttpAgent_retryTimes = new WeakMap(), _HttpAgent_backoffStrategy = new WeakMap(), _HttpAgent_waterMark = new WeakMap(), _HttpAgent_queryPipeline = new WeakMap(), _HttpAgent_updatePipeline = new WeakMap(), _HttpAgent_subnetKeys = new WeakMap(), _HttpAgent_verifyQuerySignatures = new WeakMap(), _HttpAgent_verifyQueryResponse = new WeakMap(), _HttpAgent_instances = new WeakSet(), _HttpAgent_requestAndRetryQuery = async function _HttpAgent_requestAndRetryQuery(args) { | ||
_HttpAgent_identity = new WeakMap(), _HttpAgent_fetch = new WeakMap(), _HttpAgent_fetchOptions = new WeakMap(), _HttpAgent_callOptions = new WeakMap(), _HttpAgent_timeDiffMsecs = new WeakMap(), _HttpAgent_credentials = new WeakMap(), _HttpAgent_rootKeyFetched = new WeakMap(), _HttpAgent_retryTimes = new WeakMap(), _HttpAgent_backoffStrategy = new WeakMap(), _HttpAgent_waterMark = new WeakMap(), _HttpAgent_queryPipeline = new WeakMap(), _HttpAgent_updatePipeline = new WeakMap(), _HttpAgent_subnetKeys = new WeakMap(), _HttpAgent_verifyQuerySignatures = new WeakMap(), _HttpAgent_verifyQueryResponse = new WeakMap(), _HttpAgent_instances = new WeakSet(), _HttpAgent_requestAndRetryQuery = async function _HttpAgent_requestAndRetryQuery(args) { | ||
var _a, _b; | ||
@@ -698,3 +728,3 @@ const { ecid, transformedRequest, body, requestId, backoff, tries } = args; | ||
this.log.print(`fetching "/api/v2/canister/${ecid.toString()}/query" with request:`, transformedRequest); | ||
const fetchResponse = await this._fetch('' + new URL(`/api/v2/canister/${ecid.toString()}/query`, this._host), Object.assign(Object.assign(Object.assign({}, this._fetchOptions), transformedRequest.request), { body })); | ||
const fetchResponse = await __classPrivateFieldGet(this, _HttpAgent_fetch, "f").call(this, '' + new URL(`/api/v2/canister/${ecid.toString()}/query`, this.host), Object.assign(Object.assign(Object.assign({}, __classPrivateFieldGet(this, _HttpAgent_fetchOptions, "f")), transformedRequest.request), { body })); | ||
if (fetchResponse.status === 200) { | ||
@@ -701,0 +731,0 @@ const queryResponse = cbor.decode(await fetchResponse.arrayBuffer()); |
@@ -41,6 +41,3 @@ "use strict"; | ||
// Create an anonymous `HttpAgent` (adapted from Candid UI) | ||
agent = new http_1.HttpAgent(); | ||
if (agent.isLocal()) { | ||
agent.fetchRootKey(); | ||
} | ||
agent = await http_1.HttpAgent.create(); | ||
} | ||
@@ -47,0 +44,0 @@ // Attempt to use canister metadata |
@@ -27,3 +27,2 @@ import { JsonObject } from '@dfinity/candid'; | ||
export interface HttpAgentOptions { | ||
source?: HttpAgent; | ||
fetch?: typeof fetch; | ||
@@ -66,17 +65,30 @@ fetchOptions?: Record<string, unknown>; | ||
} | ||
interface V1HttpAgentInterface { | ||
_identity: Promise<Identity> | null; | ||
readonly _fetch: typeof fetch; | ||
readonly _fetchOptions?: Record<string, unknown>; | ||
readonly _callOptions?: Record<string, unknown>; | ||
readonly _host: URL; | ||
readonly _credentials: string | undefined; | ||
readonly _retryTimes: number; | ||
_isAgent: true; | ||
} | ||
export declare class HttpAgent implements Agent { | ||
#private; | ||
rootKey: ArrayBuffer; | ||
private _identity; | ||
private readonly _fetch; | ||
private readonly _fetchOptions?; | ||
private readonly _callOptions?; | ||
private _timeDiffMsecs; | ||
private readonly _host; | ||
private readonly _credentials; | ||
private _rootKeyFetched; | ||
readonly host: URL; | ||
readonly _isAgent = true; | ||
config: HttpAgentOptions; | ||
get waterMark(): number; | ||
log: ObservableLog; | ||
/** | ||
* @param options - Options for the HttpAgent | ||
* @deprecated Use `HttpAgent.create` or `HttpAgent.createSync` instead | ||
*/ | ||
constructor(options?: HttpAgentOptions); | ||
static createSync(options?: HttpAgentOptions): HttpAgent; | ||
static create(options?: HttpAgentOptions & { | ||
shouldFetchRootKey?: boolean; | ||
}): Promise<HttpAgent>; | ||
static from(agent: Pick<HttpAgent, 'config'> | V1HttpAgentInterface): Promise<HttpAgent>; | ||
isLocal(): boolean; | ||
@@ -83,0 +95,0 @@ addTransform(type: 'update' | 'query', fn: HttpAgentRequestTransformFn, priority?: number): void; |
@@ -12,3 +12,3 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { | ||
}; | ||
var _HttpAgent_instances, _HttpAgent_retryTimes, _HttpAgent_backoffStrategy, _HttpAgent_waterMark, _HttpAgent_queryPipeline, _HttpAgent_updatePipeline, _HttpAgent_subnetKeys, _HttpAgent_verifyQuerySignatures, _HttpAgent_requestAndRetryQuery, _HttpAgent_requestAndRetry, _HttpAgent_verifyQueryResponse; | ||
var _HttpAgent_instances, _HttpAgent_identity, _HttpAgent_fetch, _HttpAgent_fetchOptions, _HttpAgent_callOptions, _HttpAgent_timeDiffMsecs, _HttpAgent_credentials, _HttpAgent_rootKeyFetched, _HttpAgent_retryTimes, _HttpAgent_backoffStrategy, _HttpAgent_waterMark, _HttpAgent_queryPipeline, _HttpAgent_updatePipeline, _HttpAgent_subnetKeys, _HttpAgent_verifyQuerySignatures, _HttpAgent_requestAndRetryQuery, _HttpAgent_requestAndRetry, _HttpAgent_verifyQueryResponse; | ||
import { Principal } from '@dfinity/principal'; | ||
@@ -99,2 +99,37 @@ import { AgentError } from '../../errors'; | ||
} | ||
function determineHost(configuredHost) { | ||
let host; | ||
if (configuredHost !== undefined) { | ||
if (!configuredHost.match(/^[a-z]+:/) && typeof window !== 'undefined') { | ||
host = new URL(window.location.protocol + '//' + configuredHost); | ||
} | ||
else { | ||
host = new URL(configuredHost); | ||
} | ||
} | ||
else { | ||
// Mainnet, local, and remote environments will have the api route available | ||
const knownHosts = ['ic0.app', 'icp0.io', '127.0.0.1', 'localhost']; | ||
const remoteHosts = ['.github.dev', '.gitpod.io']; | ||
const location = typeof window !== 'undefined' ? window.location : undefined; | ||
const hostname = location === null || location === void 0 ? void 0 : location.hostname; | ||
let knownHost; | ||
if (hostname && typeof hostname === 'string') { | ||
if (remoteHosts.some(host => hostname.endsWith(host))) { | ||
knownHost = hostname; | ||
} | ||
else { | ||
knownHost = knownHosts.find(host => hostname.endsWith(host)); | ||
} | ||
} | ||
if (location && knownHost) { | ||
// If the user is on a boundary-node provided host, we can use the same host for the agent | ||
host = new URL(`${location.protocol}//${knownHost}${location.port ? ':' + location.port : ''}`); | ||
} | ||
else { | ||
host = new URL('https://icp-api.io'); | ||
} | ||
} | ||
return host.toString(); | ||
} | ||
// A HTTP agent allows users to interact with a client of the internet computer | ||
@@ -110,2 +145,6 @@ // using the available methods. It exposes an API that closely follows the | ||
export class HttpAgent { | ||
/** | ||
* @param options - Options for the HttpAgent | ||
* @deprecated Use `HttpAgent.create` or `HttpAgent.createSync` instead | ||
*/ | ||
constructor(options = {}) { | ||
@@ -115,7 +154,14 @@ var _a; | ||
this.rootKey = fromHex(IC_ROOT_KEY); | ||
this._timeDiffMsecs = 0; | ||
this._rootKeyFetched = false; | ||
_HttpAgent_identity.set(this, void 0); | ||
_HttpAgent_fetch.set(this, void 0); | ||
_HttpAgent_fetchOptions.set(this, void 0); | ||
_HttpAgent_callOptions.set(this, void 0); | ||
_HttpAgent_timeDiffMsecs.set(this, 0); | ||
_HttpAgent_credentials.set(this, void 0); | ||
_HttpAgent_rootKeyFetched.set(this, false); | ||
_HttpAgent_retryTimes.set(this, void 0); // Retry requests N times before erroring by default | ||
_HttpAgent_backoffStrategy.set(this, void 0); | ||
// Public signature to help with type checking. | ||
this._isAgent = true; | ||
this.config = {}; | ||
// The UTC time in milliseconds when the latest request was made | ||
@@ -188,56 +234,8 @@ _HttpAgent_waterMark.set(this, 0); | ||
}); | ||
if (options.source) { | ||
if (!(options.source instanceof HttpAgent)) { | ||
throw new Error("An Agent's source can only be another HttpAgent"); | ||
} | ||
this._identity = options.source._identity; | ||
this._fetch = options.source._fetch; | ||
this._host = options.source._host; | ||
this._credentials = options.source._credentials; | ||
} | ||
else { | ||
this._fetch = options.fetch || getDefaultFetch() || fetch.bind(global); | ||
this._fetchOptions = options.fetchOptions; | ||
this._callOptions = options.callOptions; | ||
} | ||
if (options.host !== undefined) { | ||
if (!options.host.match(/^[a-z]+:/) && typeof window !== 'undefined') { | ||
this._host = new URL(window.location.protocol + '//' + options.host); | ||
} | ||
else { | ||
this._host = new URL(options.host); | ||
} | ||
} | ||
else if (options.source !== undefined) { | ||
// Safe to ignore here. | ||
this._host = options.source._host; | ||
} | ||
else { | ||
const location = typeof window !== 'undefined' ? window.location : undefined; | ||
if (!location) { | ||
this._host = new URL('https://icp-api.io'); | ||
this.log.warn('Could not infer host from window.location, defaulting to mainnet gateway of https://icp-api.io. Please provide a host to the HttpAgent constructor to avoid this warning.'); | ||
} | ||
// Mainnet, local, and remote environments will have the api route available | ||
const knownHosts = ['ic0.app', 'icp0.io', '127.0.0.1', 'localhost']; | ||
const remoteHosts = ['.github.dev', '.gitpod.io']; | ||
const hostname = location === null || location === void 0 ? void 0 : location.hostname; | ||
let knownHost; | ||
if (hostname && typeof hostname === 'string') { | ||
if (remoteHosts.some(host => hostname.endsWith(host))) { | ||
knownHost = hostname; | ||
} | ||
else { | ||
knownHost = knownHosts.find(host => hostname.endsWith(host)); | ||
} | ||
} | ||
if (location && knownHost) { | ||
// If the user is on a boundary-node provided host, we can use the same host for the agent | ||
this._host = new URL(`${location.protocol}//${knownHost}${location.port ? ':' + location.port : ''}`); | ||
} | ||
else { | ||
this._host = new URL('https://icp-api.io'); | ||
this.log.warn('Could not infer host from window.location, defaulting to mainnet gateway of https://icp-api.io. Please provide a host to the HttpAgent constructor to avoid this warning.'); | ||
} | ||
} | ||
this.config = options; | ||
__classPrivateFieldSet(this, _HttpAgent_fetch, options.fetch || getDefaultFetch() || fetch.bind(global), "f"); | ||
__classPrivateFieldSet(this, _HttpAgent_fetchOptions, options.fetchOptions, "f"); | ||
__classPrivateFieldSet(this, _HttpAgent_callOptions, options.callOptions, "f"); | ||
const host = determineHost(options.host); | ||
this.host = new URL(host); | ||
if (options.verifyQuerySignatures !== undefined) { | ||
@@ -254,16 +252,16 @@ __classPrivateFieldSet(this, _HttpAgent_verifyQuerySignatures, options.verifyQuerySignatures, "f"); | ||
// Rewrite to avoid redirects | ||
if (this._host.hostname.endsWith(IC0_SUB_DOMAIN)) { | ||
this._host.hostname = IC0_DOMAIN; | ||
if (this.host.hostname.endsWith(IC0_SUB_DOMAIN)) { | ||
this.host.hostname = IC0_DOMAIN; | ||
} | ||
else if (this._host.hostname.endsWith(ICP0_SUB_DOMAIN)) { | ||
this._host.hostname = ICP0_DOMAIN; | ||
else if (this.host.hostname.endsWith(ICP0_SUB_DOMAIN)) { | ||
this.host.hostname = ICP0_DOMAIN; | ||
} | ||
else if (this._host.hostname.endsWith(ICP_API_SUB_DOMAIN)) { | ||
this._host.hostname = ICP_API_DOMAIN; | ||
else if (this.host.hostname.endsWith(ICP_API_SUB_DOMAIN)) { | ||
this.host.hostname = ICP_API_DOMAIN; | ||
} | ||
if (options.credentials) { | ||
const { name, password } = options.credentials; | ||
this._credentials = `${name}${password ? ':' + password : ''}`; | ||
__classPrivateFieldSet(this, _HttpAgent_credentials, `${name}${password ? ':' + password : ''}`, "f"); | ||
} | ||
this._identity = Promise.resolve(options.identity || new AnonymousIdentity()); | ||
__classPrivateFieldSet(this, _HttpAgent_identity, Promise.resolve(options.identity || new AnonymousIdentity()), "f"); | ||
// Add a nonce transform to ensure calls are unique | ||
@@ -291,4 +289,36 @@ this.addTransform('update', makeNonceTransform(makeNonce)); | ||
} | ||
static createSync(options = {}) { | ||
return new this(Object.assign({}, options)); | ||
} | ||
static async create(options = { | ||
shouldFetchRootKey: false, | ||
}) { | ||
const agent = HttpAgent.createSync(options); | ||
const initPromises = [agent.syncTime()]; | ||
if (agent.host.toString() !== 'https://icp-api.io' && options.shouldFetchRootKey) { | ||
initPromises.push(agent.fetchRootKey()); | ||
} | ||
await Promise.all(initPromises); | ||
return agent; | ||
} | ||
static async from(agent) { | ||
var _a; | ||
try { | ||
if ('config' in agent) { | ||
return await HttpAgent.create(agent.config); | ||
} | ||
return await HttpAgent.create({ | ||
fetch: agent._fetch, | ||
fetchOptions: agent._fetchOptions, | ||
callOptions: agent._callOptions, | ||
host: agent._host.toString(), | ||
identity: (_a = agent._identity) !== null && _a !== void 0 ? _a : undefined, | ||
}); | ||
} | ||
catch (error) { | ||
throw new AgentError('Failed to create agent from provided agent'); | ||
} | ||
} | ||
isLocal() { | ||
const hostname = this._host.hostname; | ||
const hostname = this.host.hostname; | ||
return hostname === '127.0.0.1' || hostname.endsWith('127.0.0.1'); | ||
@@ -309,9 +339,9 @@ } | ||
async getPrincipal() { | ||
if (!this._identity) { | ||
if (!__classPrivateFieldGet(this, _HttpAgent_identity, "f")) { | ||
throw new IdentityInvalidError("This identity has expired due this application's security policy. Please refresh your authentication."); | ||
} | ||
return (await this._identity).getPrincipal(); | ||
return (await __classPrivateFieldGet(this, _HttpAgent_identity, "f")).getPrincipal(); | ||
} | ||
async call(canisterId, options, identity) { | ||
const id = await (identity !== undefined ? await identity : await this._identity); | ||
const id = await (identity !== undefined ? await identity : await __classPrivateFieldGet(this, _HttpAgent_identity, "f")); | ||
if (!id) { | ||
@@ -327,4 +357,4 @@ throw new IdentityInvalidError("This identity has expired due this application's security policy. Please refresh your authentication."); | ||
// If the value is off by more than 30 seconds, reconcile system time with the network | ||
if (Math.abs(this._timeDiffMsecs) > 1000 * 30) { | ||
ingress_expiry = new Expiry(DEFAULT_INGRESS_EXPIRY_DELTA_IN_MSECS + this._timeDiffMsecs); | ||
if (Math.abs(__classPrivateFieldGet(this, _HttpAgent_timeDiffMsecs, "f")) > 1000 * 30) { | ||
ingress_expiry = new Expiry(DEFAULT_INGRESS_EXPIRY_DELTA_IN_MSECS + __classPrivateFieldGet(this, _HttpAgent_timeDiffMsecs, "f")); | ||
} | ||
@@ -344,3 +374,3 @@ const submit = { | ||
method: 'POST', | ||
headers: Object.assign({ 'Content-Type': 'application/cbor' }, (this._credentials ? { Authorization: 'Basic ' + btoa(this._credentials) } : {})), | ||
headers: Object.assign({ 'Content-Type': 'application/cbor' }, (__classPrivateFieldGet(this, _HttpAgent_credentials, "f") ? { Authorization: 'Basic ' + btoa(__classPrivateFieldGet(this, _HttpAgent_credentials, "f")) } : {})), | ||
}, | ||
@@ -365,3 +395,3 @@ endpoint: "call" /* Endpoint.Call */, | ||
const request = __classPrivateFieldGet(this, _HttpAgent_instances, "m", _HttpAgent_requestAndRetry).call(this, { | ||
request: () => this._fetch('' + new URL(`/api/v2/canister/${ecid.toText()}/call`, this._host), Object.assign(Object.assign(Object.assign({}, this._callOptions), transformedRequest.request), { body })), | ||
request: () => __classPrivateFieldGet(this, _HttpAgent_fetch, "f").call(this, '' + new URL(`/api/v2/canister/${ecid.toText()}/call`, this.host), Object.assign(Object.assign(Object.assign({}, __classPrivateFieldGet(this, _HttpAgent_callOptions, "f")), transformedRequest.request), { body })), | ||
backoff, | ||
@@ -393,3 +423,3 @@ tries: 0, | ||
const makeQuery = async () => { | ||
const id = await (identity !== undefined ? await identity : await this._identity); | ||
const id = await (identity !== undefined ? await identity : await __classPrivateFieldGet(this, _HttpAgent_identity, "f")); | ||
if (!id) { | ||
@@ -414,3 +444,3 @@ throw new IdentityInvalidError("This identity has expired due this application's security policy. Please refresh your authentication."); | ||
method: 'POST', | ||
headers: Object.assign({ 'Content-Type': 'application/cbor' }, (this._credentials ? { Authorization: 'Basic ' + btoa(this._credentials) } : {})), | ||
headers: Object.assign({ 'Content-Type': 'application/cbor' }, (__classPrivateFieldGet(this, _HttpAgent_credentials, "f") ? { Authorization: 'Basic ' + btoa(__classPrivateFieldGet(this, _HttpAgent_credentials, "f")) } : {})), | ||
}, | ||
@@ -474,3 +504,3 @@ endpoint: "read" /* Endpoint.Query */, | ||
async createReadStateRequest(fields, identity) { | ||
const id = await (identity !== undefined ? await identity : await this._identity); | ||
const id = await (identity !== undefined ? await identity : await __classPrivateFieldGet(this, _HttpAgent_identity, "f")); | ||
if (!id) { | ||
@@ -485,3 +515,3 @@ throw new IdentityInvalidError("This identity has expired due this application's security policy. Please refresh your authentication."); | ||
method: 'POST', | ||
headers: Object.assign({ 'Content-Type': 'application/cbor' }, (this._credentials ? { Authorization: 'Basic ' + btoa(this._credentials) } : {})), | ||
headers: Object.assign({ 'Content-Type': 'application/cbor' }, (__classPrivateFieldGet(this, _HttpAgent_credentials, "f") ? { Authorization: 'Basic ' + btoa(__classPrivateFieldGet(this, _HttpAgent_credentials, "f")) } : {})), | ||
}, | ||
@@ -509,3 +539,3 @@ endpoint: "read_state" /* Endpoint.ReadState */, | ||
const response = await __classPrivateFieldGet(this, _HttpAgent_instances, "m", _HttpAgent_requestAndRetry).call(this, { | ||
request: () => this._fetch('' + new URL(`/api/v2/canister/${canister.toString()}/read_state`, this._host), Object.assign(Object.assign(Object.assign({}, this._fetchOptions), transformedRequest.request), { body })), | ||
request: () => __classPrivateFieldGet(this, _HttpAgent_fetch, "f").call(this, '' + new URL(`/api/v2/canister/${canister.toString()}/read_state`, this.host), Object.assign(Object.assign(Object.assign({}, __classPrivateFieldGet(this, _HttpAgent_fetchOptions, "f")), transformedRequest.request), { body })), | ||
backoff, | ||
@@ -574,3 +604,3 @@ tries: 0, | ||
if (replicaTime) { | ||
this._timeDiffMsecs = Number(replicaTime) - Number(callTime); | ||
__classPrivateFieldSet(this, _HttpAgent_timeDiffMsecs, Number(replicaTime) - Number(callTime), "f"); | ||
} | ||
@@ -583,5 +613,5 @@ } | ||
async status() { | ||
const headers = this._credentials | ||
const headers = __classPrivateFieldGet(this, _HttpAgent_credentials, "f") | ||
? { | ||
Authorization: 'Basic ' + btoa(this._credentials), | ||
Authorization: 'Basic ' + btoa(__classPrivateFieldGet(this, _HttpAgent_credentials, "f")), | ||
} | ||
@@ -593,3 +623,3 @@ : {}; | ||
backoff, | ||
request: () => this._fetch('' + new URL(`/api/v2/status`, this._host), Object.assign({ headers }, this._fetchOptions)), | ||
request: () => __classPrivateFieldGet(this, _HttpAgent_fetch, "f").call(this, '' + new URL(`/api/v2/status`, this.host), Object.assign({ headers }, __classPrivateFieldGet(this, _HttpAgent_fetchOptions, "f"))), | ||
tries: 0, | ||
@@ -600,6 +630,6 @@ }); | ||
async fetchRootKey() { | ||
if (!this._rootKeyFetched) { | ||
if (!__classPrivateFieldGet(this, _HttpAgent_rootKeyFetched, "f")) { | ||
// Hex-encoded version of the replica root key | ||
this.rootKey = (await this.status()).root_key; | ||
this._rootKeyFetched = true; | ||
__classPrivateFieldSet(this, _HttpAgent_rootKeyFetched, true, "f"); | ||
} | ||
@@ -609,6 +639,6 @@ return this.rootKey; | ||
invalidateIdentity() { | ||
this._identity = null; | ||
__classPrivateFieldSet(this, _HttpAgent_identity, null, "f"); | ||
} | ||
replaceIdentity(identity) { | ||
this._identity = Promise.resolve(identity); | ||
__classPrivateFieldSet(this, _HttpAgent_identity, Promise.resolve(identity), "f"); | ||
} | ||
@@ -645,3 +675,3 @@ async fetchSubnetKeys(canisterId) { | ||
} | ||
_HttpAgent_retryTimes = new WeakMap(), _HttpAgent_backoffStrategy = new WeakMap(), _HttpAgent_waterMark = new WeakMap(), _HttpAgent_queryPipeline = new WeakMap(), _HttpAgent_updatePipeline = new WeakMap(), _HttpAgent_subnetKeys = new WeakMap(), _HttpAgent_verifyQuerySignatures = new WeakMap(), _HttpAgent_verifyQueryResponse = new WeakMap(), _HttpAgent_instances = new WeakSet(), _HttpAgent_requestAndRetryQuery = async function _HttpAgent_requestAndRetryQuery(args) { | ||
_HttpAgent_identity = new WeakMap(), _HttpAgent_fetch = new WeakMap(), _HttpAgent_fetchOptions = new WeakMap(), _HttpAgent_callOptions = new WeakMap(), _HttpAgent_timeDiffMsecs = new WeakMap(), _HttpAgent_credentials = new WeakMap(), _HttpAgent_rootKeyFetched = new WeakMap(), _HttpAgent_retryTimes = new WeakMap(), _HttpAgent_backoffStrategy = new WeakMap(), _HttpAgent_waterMark = new WeakMap(), _HttpAgent_queryPipeline = new WeakMap(), _HttpAgent_updatePipeline = new WeakMap(), _HttpAgent_subnetKeys = new WeakMap(), _HttpAgent_verifyQuerySignatures = new WeakMap(), _HttpAgent_verifyQueryResponse = new WeakMap(), _HttpAgent_instances = new WeakSet(), _HttpAgent_requestAndRetryQuery = async function _HttpAgent_requestAndRetryQuery(args) { | ||
var _a, _b; | ||
@@ -666,3 +696,3 @@ const { ecid, transformedRequest, body, requestId, backoff, tries } = args; | ||
this.log.print(`fetching "/api/v2/canister/${ecid.toString()}/query" with request:`, transformedRequest); | ||
const fetchResponse = await this._fetch('' + new URL(`/api/v2/canister/${ecid.toString()}/query`, this._host), Object.assign(Object.assign(Object.assign({}, this._fetchOptions), transformedRequest.request), { body })); | ||
const fetchResponse = await __classPrivateFieldGet(this, _HttpAgent_fetch, "f").call(this, '' + new URL(`/api/v2/canister/${ecid.toString()}/query`, this.host), Object.assign(Object.assign(Object.assign({}, __classPrivateFieldGet(this, _HttpAgent_fetchOptions, "f")), transformedRequest.request), { body })); | ||
if (fetchResponse.status === 200) { | ||
@@ -669,0 +699,0 @@ const queryResponse = cbor.decode(await fetchResponse.arrayBuffer()); |
@@ -15,6 +15,3 @@ import { Principal } from '@dfinity/principal'; | ||
// Create an anonymous `HttpAgent` (adapted from Candid UI) | ||
agent = new HttpAgent(); | ||
if (agent.isLocal()) { | ||
agent.fetchRootKey(); | ||
} | ||
agent = await HttpAgent.create(); | ||
} | ||
@@ -21,0 +18,0 @@ // Attempt to use canister metadata |
{ | ||
"name": "@dfinity/agent", | ||
"version": "2.0.0-beta.0", | ||
"version": "2.0.0-beta.1", | ||
"author": "DFINITY Stiftung <sdk@dfinity.org>", | ||
@@ -50,4 +50,4 @@ "license": "Apache-2.0", | ||
"peerDependencies": { | ||
"@dfinity/candid": "^2.0.0-beta.0", | ||
"@dfinity/principal": "^2.0.0-beta.0" | ||
"@dfinity/candid": "^2.0.0-beta.1", | ||
"@dfinity/principal": "^2.0.0-beta.1" | ||
}, | ||
@@ -54,0 +54,0 @@ "dependencies": { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1066947
11151