Frontend hosted elsewhere

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.

1 Like

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.

2 Likes

I would appreciate that!

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.

I highly suggest that you use the Actor class, which can be found here (interface); https://unpkg.com/@dfinity/[email protected]/src/actor.d.ts (see function makeActorFactory).

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');
    }
  }
}

}

Cheers!

5 Likes

Thanks a lot for putting the time in!

1 Like