From 3dc0bbb5e6d049bbee94f49e98e826b765f9bf86 Mon Sep 17 00:00:00 2001 From: Ryan Vandersmith Date: Fri, 9 Sep 2022 13:07:35 -0600 Subject: [PATCH 1/7] Add fetchCandid() helper function to @dfinity/agent --- packages/agent/src/fetch_candid.test.ts | 27 +++++++++++++++++++++++++ packages/agent/src/fetch_candid.ts | 21 +++++++++++++++++++ packages/agent/src/index.ts | 1 + packages/candid/tsconfig.json | 2 +- 4 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 packages/agent/src/fetch_candid.test.ts create mode 100644 packages/agent/src/fetch_candid.ts diff --git a/packages/agent/src/fetch_candid.test.ts b/packages/agent/src/fetch_candid.test.ts new file mode 100644 index 000000000..9b12f608e --- /dev/null +++ b/packages/agent/src/fetch_candid.test.ts @@ -0,0 +1,27 @@ +import { fetchCandid, HttpAgent } from '.'; +import { IDL } from '@dfinity/candid'; +import * as cbor from './cbor'; + +test('simulate fetching a Candid interface', async () => { + const mockFetch = jest.fn().mockImplementationOnce((/*resource, init*/) => { + return Promise.resolve( + new Response( + cbor.encode({ + status: 'replied', + reply: { + arg: IDL.encode([IDL.Text], ['service {}']), + }, + }), + { + status: 200, + }, + ), + ); + }); + + const agent = new HttpAgent({ fetch: mockFetch, host: 'http://localhost' }); + + const candid = await fetchCandid(agent, 'ryjl3-tyaaa-aaaaa-aaaba-cai'); + + expect(candid).toMatch(/service/); +}); diff --git a/packages/agent/src/fetch_candid.ts b/packages/agent/src/fetch_candid.ts new file mode 100644 index 000000000..5ed474326 --- /dev/null +++ b/packages/agent/src/fetch_candid.ts @@ -0,0 +1,21 @@ +import { Actor, ActorSubclass, Agent } from '.'; +import { IDL } from '@dfinity/candid'; + +// Common interface for the hidden `__get_candid_interface_tmp_hack` actor method +const tmpHackInterface: IDL.InterfaceFactory = ({ IDL }) => + IDL.Service({ + __get_candid_interface_tmp_hack: IDL.Func([], [IDL.Text], ['query']), + }); + +/** + * Retrieves the Candid interface for the specified canister.. + * + * @param agent The agent to use for the request (usually an `HttpAgent`) + * @param canisterId A string corresponding to the canister ID + * @returns Candid source code + */ +export async function fetchCandid(agent: Agent, canisterId: string): Promise { + const actor: ActorSubclass = Actor.createActor(tmpHackInterface, { agent, canisterId }); + const candidSource = (await actor.__get_candid_interface_tmp_hack()) as string; + return candidSource; +} diff --git a/packages/agent/src/index.ts b/packages/agent/src/index.ts index 35bfa4770..f784eecb0 100644 --- a/packages/agent/src/index.ts +++ b/packages/agent/src/index.ts @@ -8,6 +8,7 @@ export * from './agent/http/transforms'; export * from './agent/http/types'; export * from './canisters/asset'; export * from './canisters/management'; +export * from './fetch_candid'; export * from './request_id'; export * from './utils/bls'; export * from './utils/buffer'; diff --git a/packages/candid/tsconfig.json b/packages/candid/tsconfig.json index bd9067961..3e6fbcff2 100644 --- a/packages/candid/tsconfig.json +++ b/packages/candid/tsconfig.json @@ -18,6 +18,6 @@ "strict": true, "target": "es2017" }, - "include": ["types/*", "src/**/*", "vendor/bls"], + "include": ["types/*", "src/**/*", "vendor/bls", "../agent/src/fetch.ts"], "references": [] } From 4549111b67880aecc8c7286ad6fac74439159a7a Mon Sep 17 00:00:00 2001 From: Ryan Vandersmith Date: Fri, 9 Sep 2022 15:01:54 -0600 Subject: [PATCH 2/7] Update changelog --- docs/generated/changelog.html | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/generated/changelog.html b/docs/generated/changelog.html index 0be4a7707..7f054ef17 100644 --- a/docs/generated/changelog.html +++ b/docs/generated/changelog.html @@ -18,8 +18,14 @@

Version 0.13.3

the _key non-nullable. This fixes a regression with async window.open behavior in Safari
  • - HttpAgent now offers a method to sync time with the replica, provided a specific canister. - This can be used to set proper Expiry times when a device has fallen out of sync with the replica. + HttpAgent now offers a method to sync time with the replica, provided a specific canister. + This can be used to set proper Expiry times when a device has fallen out of sync with the + replica. +
  • +
  • + Adds a top-level fetchCandid() function which retrieves the Candid interface + for a given canister id. +
  • Version 0.13.2

      From a2e77fb33bab2479ae1ed527d57f9a0b9618c5e2 Mon Sep 17 00:00:00 2001 From: Ryan Vandersmith Date: Fri, 9 Sep 2022 15:54:45 -0600 Subject: [PATCH 3/7] Remove extraneous file from tsconfig --- packages/candid/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/candid/tsconfig.json b/packages/candid/tsconfig.json index 3e6fbcff2..bd9067961 100644 --- a/packages/candid/tsconfig.json +++ b/packages/candid/tsconfig.json @@ -18,6 +18,6 @@ "strict": true, "target": "es2017" }, - "include": ["types/*", "src/**/*", "vendor/bls", "../agent/src/fetch.ts"], + "include": ["types/*", "src/**/*", "vendor/bls"], "references": [] } From fc1b4696dd5ff1d7d46ffe80857d510f234aec38 Mon Sep 17 00:00:00 2001 From: Ryan Vandersmith Date: Fri, 9 Sep 2022 16:06:20 -0600 Subject: [PATCH 4/7] Use canister metadata in fetchCandid() --- packages/agent/src/fetch_candid.test.ts | 2 +- packages/agent/src/fetch_candid.ts | 32 ++++++++++++++++--------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/packages/agent/src/fetch_candid.test.ts b/packages/agent/src/fetch_candid.test.ts index 9b12f608e..79602421f 100644 --- a/packages/agent/src/fetch_candid.test.ts +++ b/packages/agent/src/fetch_candid.test.ts @@ -3,7 +3,7 @@ import { IDL } from '@dfinity/candid'; import * as cbor from './cbor'; test('simulate fetching a Candid interface', async () => { - const mockFetch = jest.fn().mockImplementationOnce((/*resource, init*/) => { + const mockFetch = jest.fn().mockImplementation((/*resource, init*/) => { return Promise.resolve( new Response( cbor.encode({ diff --git a/packages/agent/src/fetch_candid.ts b/packages/agent/src/fetch_candid.ts index 5ed474326..1c12502c6 100644 --- a/packages/agent/src/fetch_candid.ts +++ b/packages/agent/src/fetch_candid.ts @@ -1,21 +1,31 @@ -import { Actor, ActorSubclass, Agent } from '.'; +import { Actor, ActorSubclass, CanisterStatus, HttpAgent } from '.'; +import { Principal } from '@dfinity/principal'; import { IDL } from '@dfinity/candid'; -// Common interface for the hidden `__get_candid_interface_tmp_hack` actor method -const tmpHackInterface: IDL.InterfaceFactory = ({ IDL }) => - IDL.Service({ - __get_candid_interface_tmp_hack: IDL.Func([], [IDL.Text], ['query']), - }); - /** - * Retrieves the Candid interface for the specified canister.. + * Retrieves the Candid interface for the specified canister. * * @param agent The agent to use for the request (usually an `HttpAgent`) * @param canisterId A string corresponding to the canister ID * @returns Candid source code */ -export async function fetchCandid(agent: Agent, canisterId: string): Promise { +export async function fetchCandid(agent: HttpAgent, canisterId: string): Promise { + // Attempt to use canister metadata + const status = await CanisterStatus.request({ + agent, + canisterId: Principal.fromText(canisterId), + paths: ['candid'], + }); + const candid = status.get('candid') as string | undefined; + if (candid) { + return candid; + } + + // Use `__get_candid_interface_tmp_hack` for canisters without Candid metadata + const tmpHackInterface: IDL.InterfaceFactory = ({ IDL }) => + IDL.Service({ + __get_candid_interface_tmp_hack: IDL.Func([], [IDL.Text], ['query']), + }); const actor: ActorSubclass = Actor.createActor(tmpHackInterface, { agent, canisterId }); - const candidSource = (await actor.__get_candid_interface_tmp_hack()) as string; - return candidSource; + return (await actor.__get_candid_interface_tmp_hack()) as string; } From 4209fb27f227e9e52c4a2b8770ca84a2870db0aa Mon Sep 17 00:00:00 2001 From: Ryan Vandersmith Date: Fri, 9 Sep 2022 17:25:08 -0600 Subject: [PATCH 5/7] Move changelog entry to v0.13.4 --- docs/generated/changelog.html | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/generated/changelog.html b/docs/generated/changelog.html index ece8b5ee4..41eca3c9d 100644 --- a/docs/generated/changelog.html +++ b/docs/generated/changelog.html @@ -10,6 +10,14 @@

      Agent-JS Changelog

      +

      Version 0.13.4

      +
        +
      • + Adds a top-level fetchCandid() function which retrieves the Candid interface + for a given canister id. +
      • +
      +

      Version 0.13.3

      • @@ -28,10 +36,6 @@

        Version 0.13.3

        This can be used to set proper Expiry times when a device has fallen out of sync with the replica.
      • -
      • - Adds a top-level fetchCandid() function which retrieves the Candid interface - for a given canister id. -

      Version 0.13.2

      From aa080868a0677ced1a20d637eee296ad2bba9527 Mon Sep 17 00:00:00 2001 From: Ryan Vandersmith Date: Thu, 29 Sep 2022 11:02:27 -0600 Subject: [PATCH 6/7] Swap fetchCandid() arguments and allow default HttpAgent --- packages/agent/src/fetch_candid.test.ts | 2 +- packages/agent/src/fetch_candid.ts | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/agent/src/fetch_candid.test.ts b/packages/agent/src/fetch_candid.test.ts index 79602421f..5f6ae1335 100644 --- a/packages/agent/src/fetch_candid.test.ts +++ b/packages/agent/src/fetch_candid.test.ts @@ -21,7 +21,7 @@ test('simulate fetching a Candid interface', async () => { const agent = new HttpAgent({ fetch: mockFetch, host: 'http://localhost' }); - const candid = await fetchCandid(agent, 'ryjl3-tyaaa-aaaaa-aaaba-cai'); + const candid = await fetchCandid('ryjl3-tyaaa-aaaaa-aaaba-cai', agent); expect(candid).toMatch(/service/); }); diff --git a/packages/agent/src/fetch_candid.ts b/packages/agent/src/fetch_candid.ts index 1c12502c6..eada771fc 100644 --- a/packages/agent/src/fetch_candid.ts +++ b/packages/agent/src/fetch_candid.ts @@ -9,7 +9,16 @@ import { IDL } from '@dfinity/candid'; * @param canisterId A string corresponding to the canister ID * @returns Candid source code */ -export async function fetchCandid(agent: HttpAgent, canisterId: string): Promise { +export async function fetchCandid(canisterId: string, agent?: HttpAgent): Promise { + if (!agent) { + // Create an anonymous `HttpAgent` (adapted from Candid UI) + agent = new HttpAgent(); + const hostname = agent['_host'].hostname; + if (hostname === '127.0.0.1' || hostname.endsWith('localhost')) { + agent.fetchRootKey(); + } + } + // Attempt to use canister metadata const status = await CanisterStatus.request({ agent, From 5f4a2b7a45d03b5720b163293226c14cad3e933d Mon Sep 17 00:00:00 2001 From: Ryan Vandersmith Date: Thu, 29 Sep 2022 11:15:25 -0600 Subject: [PATCH 7/7] Add 'isLocal()' method to 'HttpAgent' --- packages/agent/src/agent/http/index.ts | 5 +++++ packages/agent/src/fetch_candid.ts | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/agent/src/agent/http/index.ts b/packages/agent/src/agent/http/index.ts index 839c72c80..399a9a888 100644 --- a/packages/agent/src/agent/http/index.ts +++ b/packages/agent/src/agent/http/index.ts @@ -219,6 +219,11 @@ export class HttpAgent implements Agent { } } + public isLocal(): boolean { + const hostname = this._host.hostname; + return hostname === '127.0.0.1' || hostname.endsWith('localhost'); + } + public addTransform(fn: HttpAgentRequestTransformFn, priority = fn.priority || 0): void { // Keep the pipeline sorted at all time, by priority. const i = this._pipeline.findIndex(x => (x.priority || 0) < priority); diff --git a/packages/agent/src/fetch_candid.ts b/packages/agent/src/fetch_candid.ts index eada771fc..a6db5bc36 100644 --- a/packages/agent/src/fetch_candid.ts +++ b/packages/agent/src/fetch_candid.ts @@ -13,8 +13,7 @@ export async function fetchCandid(canisterId: string, agent?: HttpAgent): Promis if (!agent) { // Create an anonymous `HttpAgent` (adapted from Candid UI) agent = new HttpAgent(); - const hostname = agent['_host'].hostname; - if (hostname === '127.0.0.1' || hostname.endsWith('localhost')) { + if (agent.isLocal()) { agent.fetchRootKey(); } }