Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document interaction with (shared) service workers #26

Open
dokterbob opened this issue Nov 1, 2020 · 5 comments
Open

Document interaction with (shared) service workers #26

dokterbob opened this issue Nov 1, 2020 · 5 comments
Assignees

Comments

@dokterbob
Copy link

It would be great to know whether and how (shared) service workers would be supported.

Shared service workers: https://github.com/ipfs/js-ipfs/tree/master/examples/browser-sharing-node-across-tabs

@autonome
Copy link

autonome commented Nov 2, 2020

cc @Gozala

@Gozala Gozala self-assigned this Nov 2, 2020
@Gozala
Copy link

Gozala commented Nov 3, 2020

@dokterbob creating an example demonstrating how to use IPFS in the shared worker from the service worker is on my TODO list. Unfortunately that is a non-trivial task because SharedWorker API is not available to ServiceWorkers (see w3c/ServiceWorker#678), although it is possible to talk to one if corresponding MessagePort is obtained. Which would would involve quit a bit of the ceremony like:

  • on fetch event ServiceWorker will need to find a client to obtain a SharedWorker message port from (unless we find a better option that would correspond to the window from which request originated).
  • SharedWorker will send a message to that client with a SharedWorker port request.
  • Client will create a new SharedWorker instance and send response back to the ServiceWorker with worker.port.
  • ServiceWorker then will be able to create a ipfs-message-port-client and use that in response to the fetch request.

There's some research to be done to see if a better alternatives like the one below is viable:

const [client] = await self.clients.matchAll({ includeUncontrolled: true, type: "sharedworker" })
const ipfs = client && IPFSClient.from(client)

As things stand today that does not work because shared worker isn't getting messages send using client.postMessage, but maybe there a a way.

But from what I can tell there is not way for shared worker to receive message from

@dokterbob
Copy link
Author

Thanks for the feedback @Gozala!

I'm not sure whether I fully understand - but it does seem there are two distinct things:
a) Using a shared worker from the origin (e.g. sharing a web worker amongst tabs from the same domain);
b) Using an 'untrusted' shared web worker on an external domain (e.g. js.ipfs.io).

Right now, it does seem that scenario a) is 'good enough for now'. It might imply that users have multiple JS IPFS nodes running in the background (which, I do think should kill themselves within a given timeout, e.g. 2 or 5 minutes or so). Would implementing this be somehow simpler?

Then, for b), I've read something somewhere (sorry, no reference...) about using an iframe in an iframe. Although, once a web worker is running for js.ipfs.io, wouldn't it be possible for it to intercept any requests for this domain, such that another site could simply do a fetch for that domain and such interact with the JS-IPFS and/or built-in gateway?

Lastly, I consider this only a bridge, I think we should try and get more enthusiastic users to either run JS IPFS in an extension or to run a native IPFS daemon. It's more about offloading the dependency on a single gateway, and also to prevent potential censorship of said gateway.

Curious what your thoughts are.

@Gozala
Copy link

Gozala commented Nov 7, 2020

Submitted a pull request that demonstrates use of the shared ipfs node from service worker
ipfs/js-ipfs#3374

@Gozala
Copy link

Gozala commented Nov 7, 2020

I'm not sure whether I fully understand - but it does seem there are two distinct things:
a) Using a shared worker from the origin (e.g. sharing a web worker amongst tabs from the same domain);
b) Using an 'untrusted' shared web worker on an external domain (e.g. js.ipfs.io).

Implementation of ipfs-message-port-server and client are designed to abstract between those two use cases. I'll try to explain how:

  • server basically wraps around IPFS node and can be run in arbitrary contexts shared worker, dedidaced worker, tab, etc...
  • You can call server.connect(port) (where port is an instance of MessagePort)
  • You can create a client (in another arbitrary context) via IPFSClient.from(port) (where port is the other end of the port used on the server).
  • This enables multiple client loaded in different contexts to consume (shared) IPFS node loaded and wrapped in a server in some other context.

This enables a) because you can load server in the SharedWorker and create clients to it in tabs.

This also enables b) because origin A can operate server and pass MessagePort to a page on origin B that will create client make use of it that way.

Now that said there are some challenges we need to address before we can have good story for b). Namely we need to ensure that B cant abuse A by enforcing origin separation, otherwise we may enable user tracking and other ill behaviors.

Right now, it does seem that scenario a) is 'good enough for now'. It might imply that users have multiple JS IPFS nodes running in the background (which, I do think should kill themselves within a given timeout, e.g. 2 or 5 minutes or so). Would implementing this be somehow simpler?

I'm not sure I fully understand this question. Implementing a) now should be possible as demonstrated by
https://github.com/ipfs/js-ipfs/tree/master/examples/browser-sharing-node-across-tabs. In that setup only one node (per origin) will exists no matter how many tabs you'll have open and node will only shut down if you close all the tabs holding reference to the shared worker.

If you use js-ipfs as is (without message-port-server / client) you'll likely end up with n nodes for n tabs and there are some problems one would have to overcome e.g. all nodes will end up with same peerID and only one would be really connected because peers on the other devices will end up talking to one of those tabs and not others.

Then, for b), I've read something somewhere (sorry, no reference...) about using an iframe in an iframe. Although, once a web worker is running for js.ipfs.io, wouldn't it be possible for it to intercept any requests for this domain, such that another site could simply do a fetch for that domain and such interact with the JS-IPFS and/or built-in gateway?

Are you by chance referring to https://hackmd.io/QYzm5P3bRQ6f85MJ4qsGWg or a https://github.com/gozala/lunet ? If so those are explorations that lead to message-port-server / client work.

And yes once we address origin separation challenges (described above) I hope we will be able to get something along the lines what you are describing. More specifically if you look at ipfs/js-ipfs#3374 you can see it does something very similar. On fetch event service worker obtains message port for the shared worker from the iframe parent, and uses message-port client to get IPFS content. Thing that will have to change (once we have all the pieces) is parent page will obtain port for message port from other origin like mentioned js.ipfs.io and to do that it will have to embed an iframe with it.

I wish it was more simple or obvious, but many web APIs have server client architecture so ingrained in the design that doing anything different requires orchestration of all the moving pieces to make it work. If you have more questions or need clarification please don't hesitate to follow up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants