Skip to content

Commit

Permalink
Merge branch 'main' into fix-build
Browse files Browse the repository at this point in the history
  • Loading branch information
krpeacock authored Sep 11, 2024
2 parents 5122dab + 33f2e38 commit 46782cf
Show file tree
Hide file tree
Showing 13 changed files with 212 additions and 28 deletions.
5 changes: 5 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### Added

- feat: expose inner certificate in `Certificate` for inspection or use in raw calls. `Certificate.cert` is now a public property
- feat: allow creation of multiple Actors in `useAuthClient` by passing a record to `actorOptions` with the actor name as the key, and `CreateActorOptions` as the value
- feat: sync_call support in HttpAgent and Actor
- Skips polling if the sync call succeeds and provides a certificate
- Falls back to v2 api if the v3 endpoint 404's
Expand All @@ -14,6 +16,9 @@
- docs: documentation and metadata for use-auth-client
- feat: adds optional `rootKey` to `HttpAgentOptions` to allow for a custom root key to be used for verifying signatures from other networks
- chore: npm audit bumping micromatch
- feat: exports polling utilities from `@dfinity/agent` for use in other packages
- `pollForResponse` now uses the default strategy by default
- Updated the `bls-verify` jsdoc comment to accurately reflect that the default strategy now uses @noble/curves
- docs: clarifies meaning of `effectiveCanisterId` in `CallOptions`

### Changed
Expand Down
4 changes: 2 additions & 2 deletions packages/agent/src/certificate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export interface CreateCertificateOptions {
*/
canisterId: Principal;
/**
* BLS Verification strategy. Default strategy uses wasm for performance, but that may not be available in all contexts.
* BLS Verification strategy. Default strategy uses bls12_381 from @noble/curves
*/
blsVerify?: VerifyFunc;

Expand All @@ -149,7 +149,7 @@ export interface CreateCertificateOptions {
}

export class Certificate {
private readonly cert: Cert;
public cert: Cert;

/**
* Create a new instance of a certificate, automatically verifying it. Throws a
Expand Down
1 change: 1 addition & 0 deletions packages/agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { Agent, HttpAgent } from './agent';
import { IDL } from '@dfinity/candid';

export * as Cbor from './cbor';
export * from './polling';

export interface GlobalInternetComputer {
ic: {
Expand Down
3 changes: 2 additions & 1 deletion packages/agent/src/polling/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { RequestId } from '../request_id';
import { toHex } from '../utils/buffer';

export * as strategy from './strategy';
import { defaultStrategy } from './strategy';
export { defaultStrategy } from './strategy';
export type PollStrategy = (
canisterId: Principal,
Expand All @@ -27,7 +28,7 @@ export async function pollForResponse(
agent: Agent,
canisterId: Principal,
requestId: RequestId,
strategy: PollStrategy,
strategy: PollStrategy = defaultStrategy(),
// eslint-disable-next-line
request?: any,
blsVerify?: CreateCertificateOptions['blsVerify'],
Expand Down
22 changes: 22 additions & 0 deletions packages/use-auth-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,28 @@ const App = () => {
}
```

## Multiple Actors

If you have multiple actors, you can pass a record of `actorOptions` to the `useAuthClient` hook. The keys of the record will be the names of the actors, and the values will be the `actorOptions` for that actor. It will look something like this:

```ts
const { isAuthenticated, login, logout, actors } = useAuthClient({
actors: {
actor1: {
canisterId: canisterId1,
idlFactory: idlFactory1,
},
actor2: {
canisterId: canisterId2,
idlFactory: idlFactory2,
},
},
});

const { actor1, actor2 } = actors;

```

There is a live demo at https://5ibdo-haaaa-aaaab-qajia-cai.icp0.io/

Additional generated documentaion is available at https://agent-js.icp.xyz/use-auth-client/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
service : {
whoami: () -> (principal) query;
}
8 changes: 8 additions & 0 deletions packages/use-auth-client/examples/auth-demo/deps/init.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"canisters": {
"ivcos-eqaaa-aaaab-qablq-cai": {
"arg_str": null,
"arg_raw": null
}
}
}
13 changes: 13 additions & 0 deletions packages/use-auth-client/examples/auth-demo/deps/pulled.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"canisters": {
"ivcos-eqaaa-aaaab-qablq-cai": {
"name": "whoami",
"wasm_hash": "a5af74d01aec228c5a717dfb43f773917e1a9138e512431aafcd225ad0001a8b",
"wasm_hash_download": "88f0162fa446ad32e08e7ee513a85a82f145d16a9ebb6d2adfa9bc5ff5605f74",
"init_guide": "null",
"init_arg": null,
"candid_args": "()",
"gzip": false
}
}
}
6 changes: 5 additions & 1 deletion packages/use-auth-client/examples/auth-demo/dfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
"type": "assets",
"workspace": "auth-demo-frontend"
},
"whoami": {
"type": "pull",
"id": "ivcos-eqaaa-aaaab-qablq-cai"
},
"internet_identity": {
"candid": "https://github.com/dfinity/internet-identity/releases/latest/download/internet_identity.did",
"frontend": {},
Expand All @@ -34,4 +38,4 @@
},
"output_env_file": ".env",
"version": 1
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
import { useState } from 'react';
import { idlFactory, canisterId } from 'declarations/auth-demo-backend';
import { FormEvent, useState } from 'react';
import {
idlFactory as helloFactory,
canisterId as helloId,
} from '../../declarations/auth-demo-backend';
import { _SERVICE as HELLO_SERVICE } from '../../declarations/auth-demo-backend/auth-demo-backend.did';
import {
idlFactory as whoamiFactory,
canisterId as whoamiId,
} from '../../declarations/auth-demo-backend';
import { _SERVICE as WHOAMI_SERVICE } from '../../declarations/auth-demo-backend/auth-demo-backend.did';
import { useAuthClient } from '../../../../../src/index';
import type { ActorSubclass } from '@dfinity/agent';
import IILogo from './IILogo.svg';

export interface ProcessEnv {
[key: string]: string | undefined;
}

declare var process: {
env: ProcessEnv;
};

/**
*
*
* @returns app
*/
function App() {
Expand All @@ -15,23 +33,34 @@ function App() {
`http://${process.env.CANISTER_ID_INTERNET_IDENTITY}.localhost:4943`
: 'https://identity.ic0.app';

const { isAuthenticated, login, logout, actor } = useAuthClient({
const { isAuthenticated, login, logout, actors } = useAuthClient({
loginOptions: {
identityProvider,
},
actorOptions: {
canisterId,
idlFactory,
whoami_canister: {
canisterId: whoamiId,
idlFactory: whoamiFactory,
},
greet_canister: {
canisterId: helloId,
idlFactory: helloFactory,
},
},
});

const { whoami_canister, greet_canister } = actors as unknown as {
whoami_canister: ActorSubclass<WHOAMI_SERVICE>;
greet_canister: ActorSubclass<HELLO_SERVICE>;
};

const [greeting, setGreeting] = useState('');
const [whoamiText, setWhoamiText] = useState('');

function handleSubmit(event) {
function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const name = event.target.elements.name.value;
actor.greet(name).then(greeting => {
const name = (event.target as HTMLFormElement).querySelector('input')?.value || '';
greet_canister.greet(name).then(greeting => {
setGreeting(greeting);
});
return false;
Expand Down Expand Up @@ -61,8 +90,8 @@ function App() {
<p>{isAuthenticated ? 'You are logged in' : 'You are not logged in'}</p>
<button
onClick={async () => {
const whoami = await actor.whoami();
setWhoamiText(whoami);
const whoami = await whoami_canister.whoami();
setWhoamiText(whoami.toString());
}}
>
Whoami
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"types": ["vite/client"]
"types": ["vite/client", "./src/vite-env.d.ts"]
},
"include": ["src"]
}
63 changes: 54 additions & 9 deletions packages/use-auth-client/src/use-auth-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
type ActorConfig,
HttpAgent,
Actor,
ActorSubclass,
} from '@dfinity/agent';
import type { IDL } from '@dfinity/candid';
import { Principal } from '@dfinity/principal';
Expand Down Expand Up @@ -39,6 +40,7 @@ export interface CreateActorOptions {
* Options for the useAuthClient hook
*/
export type UseAuthClientOptions = {
createSync?: boolean;
/**
* Options passed during the creation of the auth client
*/
Expand All @@ -50,7 +52,7 @@ export type UseAuthClientOptions = {
/**
* Options to create an actor using the auth client identity
*/
actorOptions?: CreateActorOptions;
actorOptions?: CreateActorOptions | Record<string, CreateActorOptions>;
};

/**
Expand All @@ -63,7 +65,8 @@ export type UseAuthClientOptions = {
export function useAuthClient(options?: UseAuthClientOptions) {
const [authClient, setAuthClient] = React.useState<AuthClient | null>(null);
const [identity, setIdentity] = React.useState<Identity | null>(null);
const [actor, setActor] = React.useState<Actor | null>(null);
const [actor, setActor] = React.useState<ActorSubclass | null>();
const [actors, setActors] = React.useState<Record<string, ActorSubclass>>({});
const [isAuthenticated, setIsAuthenticated] = React.useState<boolean>(false);

// load the auth client on mount
Expand All @@ -87,12 +90,33 @@ export function useAuthClient(options?: UseAuthClientOptions) {

React.useEffect(() => {
if (identity && options?.actorOptions) {
createActor({
...options.actorOptions,
agentOptions: { ...options?.actorOptions?.agentOptions, identity },
}).then(actor => {
setActor(actor);
});
// if the options is for a single actor, it will have a canisterId
if ('canisterId' in options.actorOptions) {
const createActorOptions = options.actorOptions as CreateActorOptions;
createActor({
...createActorOptions,
agentOptions: { ...createActorOptions?.agentOptions, identity },
}).then(actor => {
// set the actor service

setActor(actor);
});
} else {
// if the options is for multiple actors, it will have a key value pair of an identifier and CreateActorOptions
const actorOptions = options.actorOptions as Record<string, CreateActorOptions>;
const actorPromises = Object.entries(actorOptions).map(
async ([canisterId, createActorOptions]) => {
const actor = await createActor({
...createActorOptions,
agentOptions: { ...createActorOptions?.agentOptions, identity },
});
return [canisterId, actor];
},
);
Promise.all(actorPromises).then(actors => {
setActors(Object.fromEntries(actors));
});
}
}
}, [identity]);

Expand Down Expand Up @@ -132,12 +156,32 @@ export function useAuthClient(options?: UseAuthClientOptions) {
setIsAuthenticated(false);
setIdentity(null);
await authClient.logout();
setActor(await createActor(options?.actorOptions));
if (options?.actorOptions) {
if ('canisterId' in options.actorOptions) {
setActor(await createActor(options.actorOptions as CreateActorOptions));
} else {
const actorOptions = options.actorOptions as Record<string, CreateActorOptions>;
const actorPromises = Object.entries(actorOptions).map(
async ([canisterId, createActorOptions]) => {
// Initialize with anonymous identity
const actor = await createActor(createActorOptions);
return [canisterId, actor];
},
);
Promise.all(actorPromises).then(actors => {
setActors(Object.fromEntries(actors));
});
}
} else {
setActor(null);
setActors({});
}
}
}

return {
actor,
actors,
authClient,
identity,
isAuthenticated,
Expand All @@ -147,6 +191,7 @@ export function useAuthClient(options?: UseAuthClientOptions) {
}

const createActor = async (options: CreateActorOptions) => {
options;
const agent = options.agent || (await HttpAgent.create({ ...options.agentOptions }));

if (options.agent && options.agentOptions) {
Expand Down
Loading

0 comments on commit 46782cf

Please sign in to comment.