Skip to content

Commit

Permalink
feat: Add fetchCandid() function to @dfinity/agent (#630)
Browse files Browse the repository at this point in the history
* Add fetchCandid() helper function to @dfinity/agent

* Update changelog

* Remove extraneous file from tsconfig

* Use canister metadata in fetchCandid()

* Add 'isLocal()' method to 'HttpAgent'
  • Loading branch information
rvanasa committed Sep 29, 2022
1 parent 2805ad8 commit 2dc2127
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 0 deletions.
4 changes: 4 additions & 0 deletions docs/generated/changelog.html
Expand Up @@ -16,6 +16,10 @@ <h2>Version 0.14.0</h2>
Adds retry logic to HttpAgent. By default, retries three times before throwing an error,
to offer a more cohesive workflow
</li>
<li>
Adds a top-level <code>fetchCandid()</code> function which retrieves the Candid interface
for a given canister id.
</li>
</ul>
<h2>Version 0.13.4</h2>
<ul>
Expand Down
5 changes: 5 additions & 0 deletions packages/agent/src/agent/http/index.ts
Expand Up @@ -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);
Expand Down
27 changes: 27 additions & 0 deletions 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().mockImplementation((/*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('ryjl3-tyaaa-aaaaa-aaaba-cai', agent);

expect(candid).toMatch(/service/);
});
39 changes: 39 additions & 0 deletions packages/agent/src/fetch_candid.ts
@@ -0,0 +1,39 @@
import { Actor, ActorSubclass, CanisterStatus, HttpAgent } from '.';
import { Principal } from '@dfinity/principal';
import { IDL } from '@dfinity/candid';

/**
* 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(canisterId: string, agent?: HttpAgent): Promise<string> {
if (!agent) {
// Create an anonymous `HttpAgent` (adapted from Candid UI)
agent = new HttpAgent();
if (agent.isLocal()) {
agent.fetchRootKey();
}
}

// 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 });
return (await actor.__get_candid_interface_tmp_hack()) as string;
}
1 change: 1 addition & 0 deletions packages/agent/src/index.ts
Expand Up @@ -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';
Expand Down

0 comments on commit 2dc2127

Please sign in to comment.