Permalink
Fetching contributors…
Cannot retrieve contributors at this time
187 lines (128 sloc) 8.34 KB

Notes on exposing IPFS API via window.ipfs

Disclaimer:

Want to help in shaping it? See #589 and issues with window.ipfs label.

Background

IPFS Companion is exposing a subset of IPFS APIs as window.ipfs on every webpage

This means websites can detect that window.ipfs already exists and use it instead of spawning own js-ipfs node, which saves resources, battery etc.

For more context, see original issue: Expose IPFS API as window.ipfs #330

Creating applications using window.ipfs

If a user has installed IPFS companion, window.ipfs will be available as soon as the first script runs on your web page, so you'll be able to detect it using a simple if statement:

if (window.ipfs) {  
  await ipfs.id()
} else {
  // Fallback
}

To add and get content, you could do something like this:

if (window.ipfs) {
  try {
    const [{ hash }] = await ipfs.add(Buffer.from('=^.^='))
    const data = await ipfs.cat(hash)
    console.log(data.toString()) // =^.^=
  } catch (err) {
    if (err.isIpfsProxyAclError) {
      // Fallback
      console.log('Unable to get ACL decision from user :(', err)
    } else {
      throw err
    }
  }
} else {
  // Fallback
}

Note that IPFS Companion also adds window.Buffer if it doesn't already exist.

See also: How do I fallback if window.ipfs is not available?

Error messages

If access was denied:

User denied access to ${permission}

If the user closes the dialog without making a decision:

Failed to obtain access response for ${permission} at ${scope}

If access to IPFS was disabled entirely:

User disabled access to IPFS

Note these might have been re-worded already. Please send a PR.

Q&A

What is a window.ipfs?

Depending how IPFS companion is configured, you may be talking directly to a js-ipfs node running in the companion, a go-ipfs daemon over js-ipfs-api or a js-ipfs daemon over js-ipfs-api and potentially others in the future.

Note that window.ipfs is not an instance of js-ipfs or js-ipfs-api but is a proxy to one of them, so don't expect to be able to detect either of them or be able to use any undocumented or instance specific functions.

See the js-ipfs/js-ipfs-api docs for available functions. If you find that some new functions are missing, the proxy might be out of date. Please check the current status and submit a PR.

How do I fallback if window.ipfs is not available?

See the example code (and live demo) for getting an IPFS instance with a fallback.

What about IPFS node configuration?

You can't make any assumptions about how the node is configured. For example, the user may not have enabled experimental features like pubsub.

Is there a Permission Control (ACL)?

Yes.

IPFS companion users are able to selectively control access to window.ipfs functions so calls may reject (or callback) with an error if a user decides to deny access.

The first time you call a window.ipfs function the user will be prompted to allow or deny the call and the decision will be remembered for subsequent calls.

It looks like this:

permission dialog in Firefox

Do I need to confirm every API call?

Not all function calls require a decision from the user. You will be able to call whitelisted IPFS functions and users will not be prompted to allow/deny access.

Functions that are not whitelisted need to be confirmed only once per scope.

Note that users can modify their permission decisions after the fact so you should not expect to always be allowed to call a function if it was successfully called previously.

Can I disable this for now?

Users can permanently deny access to all IPFS functions by disabling window.ipfs experiment on Preferences screen.

How are permissions scoped?

Permissions are scoped to the origin and path (and sub-paths) of the file from which the permission was requested.

Scoped permissions in window.ipfs work similarly to how they work for service worker registrations except that the user cannot control the scope, and it is set to the origin and path from which the permission was requested.

Scope based permissions allow applications running on an IPFS gateway to be granted different permissions. Consider the following two web sites running on the ipfs.io gateway:

With same-origin policy these two applications would be granted the same permissions. With scoped permissions, these applications will be given a different set of permissions.

e.g.

  • Allow files.add to https://domain.com/
    • ...will allow files.add to:
      • https://domain.com/file
      • https://domain.com/file2.html
      • https://domain.com/sub/paths
      • https://domain.com/sub/paths/files
      • etc.
  • Allow files.add to https://domain.com/feature
    • ...will allow files.add to:
      • https://domain.com/feature/file
      • https://domain.com/feature/file2.html
      • https://domain.com/feature/sub/paths
      • https://domain.com/feature/sub/paths/files
      • https://domain.com/featuresearch/sub/paths/files (note substring)
      • https://domain.com/features.html (note substring)
      • etc.
    • ...will cause additional prompt for files.add to:
      • https://domain.com/
      • https://domain.com/files
      • etc.

Are mutable file system (MFS) files sandboxed to a directory?

Yes. To avoid conflicts, each app gets it's own MFS directory where it can store files. When using MFS functions (see docs) this directory will be automatically added to paths you pass. Your app's MFS directory is based on the origin and path where your application is running.

e.g.

  • files.write to /myfile.txt on https://domain.com/
    • writes to /dapps/https/domain.com/myfile.txt
  • files.write to /path/to/myfile.txt on https://domain.com/feature
    • writes to /dapps/https/domain.com/feature/path/to/myfile.txt
  • files.read from /feature/path/to/myfile.txt on https://domain.com/
    • reads from /dapps/https/domain.com/feature/path/to/myfile.txt
  • files.stat to / on https://domain.com/feature
    • stats /dapps/https/domain.com/feature
  • files.read from /../myfile.txt on https://domain.com/feature
    • reads from /dapps/https/domain.com/feature/myfile.txt (no traverse above your app's root)