Skip to content

Instantly share code, notes, and snippets.

@philipcmonk
Created October 23, 2022 06:01
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save philipcmonk/9b6fc8f6f90f67abe9335c1a5338c5e9 to your computer and use it in GitHub Desktop.
Save philipcmonk/9b6fc8f6f90f67abe9335c1a5338c5e9 to your computer and use it in GitHub Desktop.

This is a proposal for a "lure service".

While this can serve as the foundation for countless features, the primary goal is to solve the "boot into an empty landscape" problem. In other words, it's trying to close the loop of "one person uses Urbit" -> "they invite another to their group on Urbit" -> "they get on Urbit and immediately know what to do with it" -> "they invite another".

Thus, we will first consider the case of inviting someone into an Urbit group who may not already use Urbit.

Invites should be regular links. These work on any device, can be transmitted over any transport (including publicly in a tweet or physically via qr code), and are very low-friction to click. When you click this link, you should expect to receive an invite to that group.

To make this happen, we must somehow establish an association between the person who clicked the link and their @p. We should make this work for as many cases as possible -- especially, it should work even if they don't yet have a @p.

Thus, the destination of the link should ask the user if they have a @p, and if so to enter it. Else, it should show the user options for getting on Urbit -- for example, by signing up with hosting, or running locally. If they sign up with hosting, the hosting provider should call back to that link supplying the assigned @p of the user.

Once this association between the user and their @p has been established, the group host will send them an invite. Thus, when they get on their ship, they're not faced with an empty landscape: they will see a notification to join that group.

Behind the scenes, there are many ways to implement this. We propose a generic "lure service". We resist the name "invite service" simply because it's much more general, and if we use that name, we will close our minds to its other possibilies.

This lure service should simply notify the inviter that the link was clicked, and what was the @p of the person who clicked it. The inviter can then perform arbitrary actions -- for example, invite that user to their group.

The inviter may wish to delegate the link-click actions to another ship, which we will call a Configuration Server. This is especially the case if the inviter is inconsistently online, or if there is a generally useful set of actions to be performed on click (for example, invite to several groups and apps useful for app developers).

The lure service could be hosted by the inviter, but not all ships should have a publicly-exposed DNS name and HTTP server. This is why we call it a service -- we expect there to be only a few of these that most people use.

Spec:

Roles

Some of these roles may be the same ship in practice. For example, the configuration server will often be either the inviter or a group host.

Inviter: ~inviter is an Urbit user who will generate a link to send to another user, who may or may not already be on Urbit.

Configuration Server: ~config is a ship which contains configuration templates. A template might be as simple as "invite the user to X group", or it might perform arbitrary actions.

Lure Service: ~lure-service is a ship with a public DNS which serves a landing page, which attempts to get the @p of the ship which clicked the link. When it does, it pokes the configuration server with that @p

Host: hosting providers will recognize referrals from the lure service and tell the lure service which @p it has assigned to the user.

URL structure

https://lure.service/category/~config/token

where:

  • lure.service is the hostname of the lure service
  • category is a space to allow multiple landing pages to be hosted at the same domain. For example, /urbit/ might be a standard landing page while /dev/ is a landing page that emphasizes self-hosting options.
  • ~config is the ship which knows what token means, and is the ship which will be poked on click.
  • token is an opaque token, known by ~config, and corresponding to a particular configuration

Lure Service API

The %lure-service landing page will be at the URL specified above. If you POST to that URL with ship=~alice where ~alice is the ship which clicked the link, then the Lure service will poke ~config's %lure app with cask [%bite-0 token ~alice]. This POST may come from a form on the landing page, or it could come from a hosting provider once they've assigned a @p to the user.

Links to hosting providers should append ?lure=~config_token.

Configuration Server API

The %lure app has the following state:

  • default-service=@t is the default %lure-service URL, including category.

It exposes this state over the scry path:

  • /gx/service produces the default lure-service URL as a @t which may be concatenated with ~config/token to produce an invite.

It exposes the following subscription path, for domestic subscribers only:

  • /bites, which produces facts of the form [%bite-0 token ~alice]

It responds to the following poke:

  • [%bite-0 token ~alice], which is immediately given as a fact on /bites

We expect subscribers to /bites to know how to fulfill the purpose of the token.

Examples:

Here's the detailed flow for a simple example where an Inviter is inviting Alice to join a group. In this example, the inviter is acting as the config server, and Alice is already on Urbit (but the Inviter doesn't know that).

sequenceDiagram
  autonumber
  participant Inviter
  participant groups as %groups (~inviter)
  participant lure as %lure (~inviter)
  participant Alice
  participant service as %lure-service (~lure-service)
  participant host groups as %groups (~group-host)
  participant alice groups as %groups (~alice)
  Inviter->>groups: Generate invite link
  groups->>lure: .^(@t %gx /=lure=/service/noun)
  lure->>groups: 'https://lure.service/lure'
  groups->>Inviter: 'https://lure.service/lure/~inviter/TOKEN'
  Inviter->>Alice: Hey, come join my group: https://lure.service/lure/~inviter/TOKEN
  Alice->>service: *click*
  service->>Alice: Enter your @p
  Alice->>service: ~alice
  service->>lure: [%bite-0 TOKEN ~alice]
  lure->>groups: [%give %fact /bites %bite-0 TOKEN ~alice]
  groups->>host groups: [%invite %the-group ~alice]
  host groups->>alice groups: [%invitation %the-group]
  alice groups->>Alice: You've been invited to ~group-host/the-graph

If Alice is not already on Urbit and chooses to sign up for hosting, replace 6-9 with:

sequenceDiagram
  autonumber
  participant Alice
  participant service as %lure-service (~lure-service)
  participant Hosting
  participant lure as %lure (~config)
  Alice->>service: GET https://lure.service/lure/~inviter/TOKEN
  service->>Alice: Sign up for hosting
  Alice->>Hosting: GET https://hosting.service/sign-up?lure=~inviter_TOKEN
  Hosting->>service: POST https://lure.service/lure/~inviter/TOKEN ship=~alice
  service->>lure: [%bite-0 TOKEN ~alice]

Suppose the Inviter wants to invite Alice not only to one group, but to many groups and apps that the Inviter already uses. The Inviter also wants to send a pals request to Alice, so that as soon as she gets on the network, she has access to the gossip network (eg Sphinx). Then the flow looks more like this:

sequenceDiagram
  autonumber
  participant Inviter
  participant groups as %groups (~inviter)
  participant lure as %lure (~inviter)
  participant treaty as %treaty (~inviter)
  participant pals as %pals (~inviter)
  participant Alice
  participant service as %lure-service (~lure-service)
  participant host groups as %groups (on various hosts)
  Inviter->>groups: Generate invite link
  groups->>lure: .^(@t %gx /=lure=/service/noun)
  lure->>groups: 'https://lure.service/lure'
  groups->>Inviter: 'https://lure.service/lure/~inviter/TOKEN'
  Inviter->>treaty: On TOKEN, invite to app %pals
  Inviter->>treaty: On TOKEN, invite to app %sphinx
  Inviter->>pals: On TOKEN, send pals request
  Inviter->>Alice: Hey, get on my level: https://lure.service/lure/~inviter/TOKEN
  Alice->>service: *click*
  Alice->service: (hosting flow happens)
  service->>lure: [%bite-0 TOKEN ~alice]
  lure->>groups: [%give %fact /bites %bite-0 TOKEN ~alice]
  groups->>host groups: [%invite %a-group ~alice]
  groups->>host groups: [%invite %another-group ~alice]
  groups->>host groups: [%invite %last-group ~alice]
  lure->>treaty: [%give %fact /bites %bite-0 TOKEN ~alice]
  treaty->>Alice: [%invite ~paldev/pals]
  treaty->>Alice: [%invite ~dister-nocsyx-lassul/sphinx]
  lure->>pals: [%give %fact /bites %bite-0 TOKEN ~alice]
  pals->>Alice : [%invite ~dister-nocsyx-lassul/sphinx]

Other flows are possible. For example, a group host may set up a token which invites to the group plus several apps used in the group. In this case, the group host is acting as the config server instead of the inviter.

A config server may be an entirely separate ship. For example, someone could host a config server with a token which sends invites to their 20 favorite apps and groups,for a batteries-included Urbit "distribution". It could even send a welcome DM with links to instructional videos.

Notes:

  • Many advanced configurations might be most easily created with an urbit-ifttt. In general, you want to be able to connect arbitrary actions on arbitrary apps to be triggered when a token is received. From this, the distance to urbit-ifttt is small -- you just need to generalize across triggers.

  • This proposal is an attempt to "close the loop", but there are many ways afterward to tighten the loop. For example, the landing page could have an entire sign-up flow for hosting embedded, including entering credit card numbers. It could recognize certain tokens and offer subsidized hosting, direct you to an "instant moon" for trial purposes, or let you "log in with metamask" and send/sell you an L2 planet.

  • Tlon will run a lure service and landing page, but anyone else can run one too. It's a simple app running on a moon at a publicly-accessible DNS. This means that if you're unsatisfied with the appearance or functionality of one lure service, you can use another or create your own. You could even create a specific one branded to match your group.

  • The lure service landing page should store the user's @p in a cookie, so that it can autofill it the next time the user clicks on such a link. This mechanism is not at all limited to new users, so it's reasonable to expect that you might click many public links, eg to join groups.

  • At some point, it may be worth adding a way for hosting providers to inject a token into a new ship, so that the invites can be automatically accepted.

  • There are countless other ways to use or expand the mechanism described in this proposal. The biggest execution risk is that we get bogged down in trying to support or future-proof against all of these. Each of these components can be upgraded in the future.

@lukebuehler
Copy link

Great name: ~lure and ~lure-service!

I would add another role, ~invitee, which would be Alice's ship or ~alice.

Since the ~lure-service can be hosted anywhere, might it not be better if hosting injects [token ~config] into ~invitee once the ship is booted? And then the ~invitee ship pokes ~lure with [%bite-0 token ~invitee]? The reason I'm bringing this up is because if the ~lure-service is hosted by a third party, then it would be more brittle for us trying to reach such a clear-web service without knowing for sure if it is up. Or could we just use our own Tlon ~lure-service for that even if the original invite came through a different ~lure-service? If the latter works (which I see why it would not) then your current spec will work well for a first production version.

@lukestiles
Copy link

Coming back to this one - for Lure Service, can this be on the clear web or is there something about it that requires it to be on Urbit?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment