Gozala / lunet

Exploration: P2P Network access through the service worker
https://lunet.link
36 stars 2 forks source link

Load apps / sites like `https://lunet.link/myapp.io/` #2

Closed Gozala closed 5 years ago

Gozala commented 5 years ago

At the moment each app is hosted by an author on some domain which on first load imports lunet client which will setup a service worker to load app and generally make it available across sessions online or offline without having to interact with servers.

@jimpick and up implementing a simple server which generates bootstrapping doc / sw from subdomain. Specifically when loading https://cid.lunet.link/ it would serve html which mounts corresponding cid and lunet.js SW script.

@autonome in our conversation made a good point that user in this case needs to trust lunet.link because all the apps do more or less go through it.

This got me thinking about alternative way to architect lunet that would address both things in a really neat way.

https://lunet.link/ could be just a static site with a service worker setup such that when say https://lunet.link/peerdium.gozala.io/doc is loaded it will redirect to https://lunet.link/?redirect=peerdium.gozala.io/doc which will install SW and redirects back to https://lunet.link/peerdium.gozala.io/doc causing a SW to serve it.

Note: Redirect will only happen only if https://lunet.link/* never had being loaded in the past, in all other instances it SW will just serve it.

SW on request to https://lunet.link/peerdium.gozala.io/doc will do following:

  1. Will lookup dnslink TXT record to determine CID, let's say it's a $cid1.

    Note: navigating to https://lunet.link/$cid1/doc will be equivalent, except we won't need to do DNS TXT lookups.

  2. Will respond with a following markup:

    <html>
    <body>
       <iframe sandbox src="://${cid1}.lunet.link/doc` />
    </body>
    </html>
  3. lunet will serve same thing under all subdomains meaning that cid1.lunet.link will just register SW which will route requests (similar to how it's done today) to the corresponding client document in this case cid1.lunet.link that will route them back to the parent doc https://lunet.link/peerdium.gozala.io/doc which will take care of fetching cid1 from the IPFS and serving it.

Why / How is this better

  1. In this setup lunet.link becomes commodity, you don't have to trust it. You can deploy it anywhere (in fact fork github repo & enabling gh-pages probably will do fine). So in a way it addresses point made by @autonome you don't have to trust it just fork & deploy, best part being you'll need to load it once per device.

  2. We remove whole bootstrapping out of the equation. Knowing CID is just enough, better yet on first load we could allow user to choose to "install" it in local namespace meaning map it to say https://lunet.link/peerdium/. App could provide name suggestions, lunet could pick on that isn't taken and allow user to use that or override in place.

  3. Unlike current setup which requires app / site to cooperate (by serving bootstrap code which embeds lunet client from that domain) it always doc from own subdomain which will then serve corresponding app / site. In other words no cooperation is required.

  4. In a way we are removing dependency on DNS, well not really because we made it far more loosely coupled because

    1. User can just use CID
    2. User can map to whatever name desired.
    3. In a future we could offer alternative name resolution from DNS (web of trust anyone ?)
  5. We put browser in your browser! However maybe in a future we could remove top browser layer.

Gozala commented 5 years ago

I need to do some exploration to be certain but I think <iframe sandbox> might be able to offer alternative to subdomains & greatly simplify proposed architecture. Specifically following quote from MDN

allow-same-origin: If this token is not used, the resource is treated as being from a special origin that always fails the same-origin policy.

Seems to suggest that sandboxed iframes might be given some unique origin, in which case we don't need subdomains to separate origin and might be able to just use same lunen.link SW across them.

If that doesn't work out there is also some hope with blob URLs.

In other words if we can somehow isolate apps / sites from each other we won't need to deal with subdomains which I think would be really amazing & as it simplifies everything greatly!

Gozala commented 5 years ago

Sadly without allow-same-origin load does not goes through service worker & however setting allow-same-origin doesn't prevent document in iframe from accessing storage which is also not good because that would allow things to mess with lunet itself.

Gozala commented 5 years ago

I have gave up on making using <iframe sandbox /> to avoid need for sub-origins, because with allow-same-origin iframe is pretty pointless and without out SW API isn't available nor top document SW is being triggered.

Instead I've set up https://celestial.link/ static site on S3 which serves same content across all the subdomains like https://mycid.celestial.link/. Content served right now does not really do much but I plan to make it bootstrap SW proxy to lunet. Lunet will be able to infer CID based of off origin that is going to be subdomain & serve content that corresponds to it.

In this setup visiting https://lunet.link/peerdium.gozala.io/${dataCID} will do following:

  1. Resolve appCID that corresponds to peerdium.gozala.io via dnslink.
  2. And serve document like:
    <iframe
     src=`://${appCID}.celestial.link/`
     data-src=`/ipfs/${dataCID}`
     sandbox="allow-same-origin allow-scripts allow-presentation allow-popups allow-pointer-lock allow-orientation-lock allow-modals allow-forms"
     style="width: 100%; height: 100%; left: 0px; top: 0px; position: absolute;"
     />
  3. ${appCID}.celestial.link will do register proxy service worker that on request messages corresponding document (meaning ${appCID}.celestial.link).
  4. ${appCID}.celestial.link listens to those messages and routes them to window.top in this instance https://lunet.link/peerdium.gozala.io/${dataCID} that captures event.origin and data-src routing request to the SharedWorker.
  5. SharedWorker takes care of handling requests & imposing restrictions:
    • Allow reads as long as they fall under ${appCID} or ${dataCID}.
    • Allow writes as long as they fall under ${dataCID} in which case it will produce response containing ${dataCID2} which https://lunet.link/* uses to update data-src attribute.

This has interesting consequences

  1. Application hosted on p2p network is disconnected from all the servers and there for can't really aggregate data.
  2. Application get's to read data that is being provided to it without:
    • Knowing it's public address
    • Knowing whether encryption / decryption occurs during read / write operation (Yes that will be a case)
  3. Lunet is fully aware what app is loaded and what document / data it's operating on & there for can provide save / share functionality.
  4. App can initiate share which would be handled by lunet instead.
  5. I think there is an opportunity to use navigator.registerProtocolHandler to allow use your own "lunet" instance. e.g https://lunet.link could register protocol like:

    navigator.registerProtocolHandler(
     "web+lunet",
     "https://lunet.link/?redirect=%s",
     "Peer-to-Peer Web");

    In such case we could embrace web+lunet://${appCID}/${dataCID}/ URLs enabling anyone to deploy own copy of lunet and use it instead. This also implies that such links may load different lunet deployment based on recipient, which I think is great.

Gozala commented 5 years ago

It just occurred to me that this can be untied from specific protocol & embrace diversity by registering web+dat://, web+ipfs://, web+ssb://, web+torrent:// etc... to resolve to https://lunet.link/ipfs/${appCID}/${data} and web+dat://dat/appKey/ etc... not only that, it could in fact allow you to have app from IPFS operating on data in Dat protocol.

Gozala commented 5 years ago
  • Application hosted on p2p network is disconnected from all the servers and there for can't really aggregate data.

This might have being little too bold of a statement as page could .unregister() service worker and there for overcome all the limitations.

Gozala commented 5 years ago
  • Application hosted on p2p network is disconnected from all the servers and there for can't really aggregate data.

This might have being little too bold of a statement as page could .unregister() service worker and there for overcome all the limitations.

However, according to the spec

Note: The unregister() method unregisters the service worker registration. It is important to note that the currently controlled service worker client's active service worker’s containing service worker registration is effective until all the service worker clients (including itself) using this service worker registration unload. That is, the unregister() method only affects subsequent navigations.

So, if unregister() only affects next load that means that means that all requests will still be handled by SW in the given load & next load will register SW before loading app anyhow, so maybe we can prevent apps loaded this way to talk with remote origins.

Gozala commented 5 years ago

Prototype is live! Now you can go to

https://lunet.link/peerdium.gozala.io/

And it will load peerdium fork. This time it no longer needs any bootstrap just a DNS TXT record for gozala.io:

_dnslink.peerdium TXT dnslink=/ipfs/QmYjtd61SyXU4aVSKWBrtDiXjHtpJVFCbvR7RgJ57BPZro/

Following also works https://lunet.link/webui.lunet.link/

In this instance through following record for lunet.link:

_dnslink.webui TXT dnslink=/ipfs/QmSDgpiHco5yXdyVTfhKxr3aiJ82ynz8V14QcGKicM3rVh/

Finally you can load anything from IPFS in same way just via CID, for example following loads same peerdium fork:

https://lunet.link/QmYjtd61SyXU4aVSKWBrtDiXjHtpJVFCbvR7RgJ57BPZro/