I have a project that I am prototyping on the Internet Computer, but for various reasons I do not plan to host the actual static assets of the project on ICP. I just want to use ICP as the computational and storage backend for the application data.
This doesn’t seem easily possible at the moment. I have been trying to import the @dfinity/agent library on my own, hosting and loading it from my own static file server. I keep running into issues, the most fundamental being that window.ic is not defined.
This has led me to believe that the ICP replica is actually adding some extra scripts into the http response for a frontend endpoint, and that I currently am not able to emulate that on my own.
This is a major limitation. I hope we’ll be able to host our frontends anywhere we want, including iOS, Android, existing servers, etc.
You can definitely do that. The Agent package on npm can help you, but it won’t setup a window.ic like you would expect. That’s the bootstrap code which is hosted on ic0.app.
We don’t have good documentation, but let me see if I can make a simple example for you when I get in front of a computer later.
Here’s code (typescript) that would create the agent and send an update message to a “greet” function. It creates a private key everytime, and will loop through status requests to wait for a reply. To use the Actor factory we use in “normal” DFX you should call didc yourself to create a did.js from a did file.
I didn’t test that code, it’s mostly from our own bootstrap and agent code base.
import {
Agent,
HttpAgent,
generateKeyPair,
makeAuthTransform,
makeExpiryTransform,
makeNonceTransform,
Principal,
RequestStatusResponseStatus
} from '@dfinity/agent';
const keyPair = generateKeyPair();
const principal = Principal.selfAuthenticating(keyPair.publicKey);
const agent = new HttpAgent({
host: "http://localhost:8000/",
principal,
});
agent.addTransform(makeNonceTransform());
agent.addTransform(makeExpiryTransform(5 * 60 * 1000));
agent.setAuthTransform(makeAuthTransform(keyPair));
async function makeCall(): Promise<Uint8Array> {
let { requestId, response } = await agent.call(CANISTER_ID_GOES_HERE, { methodName: "greet", arg: CANDID_ENCODED_ARGUMENT_GOES_HERE });
if (!response.ok) throw new Error('could not contact the replica. error: ' + response.statusText);
while (true) {
const status = await agent.requestStatus({ requestId });
switch (status) {
case RequestStatusResponseStatus.Replied: {
return new Uint8Array(status.reply.arg || []);
}
case RequestStatusResponseStatus.Unknown:
case RequestStatusResponseStatus.Received:
case RequestStatusResponseStatus.Processing:
continue;
case RequestStatusResponseStatus.Rejected:
throw new Error('call was rejected. reason: ' + status.reject_message);
case RequestStatusResponseStatus.Done:
throw new Error('call was done without a return value. this is an error');
}
}
}
}
Unhandled Rejection (Error): call was rejected. reason: IC0503: Canister 7kncf-oidaa-aaaaa-aaaaa-aaaaa-aaaaa-aaaaa-q trapped explicitly: IDL error: word read out of buffer
I’ve followed these steps but on NextJS 12.3 I have this error:
Uncaught (in promise) Error: Invalid character: "&"
at decodeChar (base32.js?cc45:64:1)
at decode (base32.js?cc45:83:1)
at Principal.fromText (index.js?3619:44:25)
at new CanisterActor (actor.js?3acd:111:23)
at Actor.createActor (actor.js?3acd:125:1)
I have the frontend not hosted in a canister ( gonna be hosted in Vercel ), while the BE is in a canister hosted locally. I don’t know what I’m doing wrong.
I’ve changed the host as you suggested. But still I get the same error.
I really cannot understand what steps to follow in order to deploy the Frontend and use only ICP as Backend.