When visitors interact with a Sandstorm grain, they typically see a URL like https://sandstorm.example.com/grain/{{grainId}}. This URL is automatically provisioned by Sandstorm, and Sandstorm routes requests to this URL to a specific grain. This document explains how that routing works, how an app author can control what URL gets displayed, and other ways to route requests to your app's content.

Overview: the grain URL (/grain/...) & ephemeral subdomains

When a visitor views a grain, the Sandstorm shell loads and checks if this user is allowed to view the grain. If the request is authorized, the shell creates an IFRAME that shows the grain's content to the user. Sandstorm adds authentication information to the HTTP request before sending it to the app.

The URL of the IFRAME uses a random per-session subdomain. This subdomain is generated according to this Sandstorm install's WILDCARD_HOST configuration option.

  • Example grain URL: https://sandstorm.example.com/grain/TPeYUde5rioE5keWM

  • Example ephemeral domain URL: https://ui-96cab9109791f1254002ac1f857ecee7.sandstorm.example.com/

Try it by creating a grain and using your browser's Inspect Element feature to look at the URL of the IFRAME element, or open the frame in a new tab.

If you need a stable domain name: the app can expose static HTML content to a stable domain name by using the static web publishing feature of Sandstorm. The app can also expose HTTP APIs on a fixed hostname.

Sharing links operate the same way, except they use a /shared/{{sharingToken}} URL pattern.

Sandstorm respects paths within grains. If a user visits a grain URL with a path appended, or a fragment (e.g. #foo, also known as location.hash) appended, Sandstorm passes this through to the grain. To be specific:

  • Example URL: https://sandstorm.example.com/grain/TPeYUde5rioE5keWM/awesomeinfo#section3

  • Example ephemeral domain URL: https://ui-96cab9109791f1254002ac1f857ecee7.sandstorm.example.com/awesomeinfo#section3

The grain user's web browser will show the grain URL (with the path and fragment) in the address bar. By default, this URL will not change, even as the user clicks around within the app. To address that, the app can update this URL.

Updating the URL & page title from your app

By default, when someone interacts with a grain, the URL and page title stay fixed at the grain URL and default grain title. This is because Sandstorm apps runs in an IFRAME, so the top-level URL and title are not automatically synchronized as the user navigates within your app. You can postMessage to the Sandstorm shell to ask it to update the URL in the address bar with the following code snippet:

window.parent.postMessage({'setPath': location.pathname + location.hash}, '*');

This will copy the path and fragment from the grain into the browser's address bar.

  • Example ephemeral domain URL: https://ui-96cab9109791f1254002ac1f857ecee7.sandstorm.example.com/foo#bar

  • URL: https://sandstorm.example.com/grain/TPeYUde5rioE5keWM/foo#bar

The IFRAME also prevents the page title from propagating up into the web browser. You can push the current page's title into the browser's TITLE with the following Javascript code:

window.parent.postMessage({'setTitle': document.title}, '*');

If you're using Meteor or another client-side routing framework, consider reactively watching the current route and pushing a postMessage event on every navigation. See this reactive code sample and per-page-load code sample.

Using the grain's title in your app

If you wish to make use of the grain's title inside your app, you can obtain this information by making a postMessage request:

var getGrainTitleRpcId = 0;

// Sandstorm will reply via postMessage, so we need to set up a handler:
window.addEventListener('message', function(event) {
  if(event.source !== window.parent) {
    // SECURITY: ignore messages not from the parent.
    return;
  }
  if(event.data.rpcId === getGrainTitleRpcId) {
    console.log("The grain's title is: ", event.data.grainTitle);
  }
})

// Now make the request:
window.parent.postMessage({
  getGrainTitle: {},
  rpcId: getGrainTitleRpcId,
  // If subscribe is true, sandstorm will push future updates to the
  // grain's title. If it is false or absent, the app will not be
  // notified of updates.
  subscribe: true,
}, '*')

Helping the user share access

If your app wants to create a link to itself that anyone can use, you can trigger Sandstorm's "Share..." dialog with this Javascript code:

window.parent.postMessage({'startSharing': {}}, '*');

This shares at the app's default permission level. In the future, we may extend this API to permit the app to choose a permission level. Additionally, the app may add a pathname or hash to the shared URL by including those as arguments:

window.parent.postMessage({
  startSharing: {
    pathname: 'download/123',
    hash: 'linux',
  }
}, '*');

If your app wants Sandstorm to display a list of users who have access to the grain, you can ask Sandstorm to show who has access with this Javascript code:

window.parent.postMessage({'showConnectionGraph': {}}, '*');

In the future, as the Powerbox matures, this dialog will likely also show connections between grains.

Embedding references to in-app resources, despite the ephemeral domain name

Your app might need to use its current domain name (also known as base URL) for:

  • Redirects, for example after a user POSTs some data.

  • Static assets like CSS, images, Javascript.

  • Links within the app: If a user is on the home page (/) of an app, and the app wants to create a href= link to some other page, it needs to know what string to place into the <a href> tag.

However, the Sandstorm ephemeral domain only applies to one particular user session of one particular grain of the app. Given that, your can either use the empty string as the base URL, or it can generate these URLs as needed, on each request.

Recommendation: Use the empty string as your base URL

The easiest way to handle Sandstorm's dynamic base URL is to use the empty string ('') as your app's base URL. This way, the app needs to make no decisions at runtime. Many web frameworks support this.

Detecting the base URL at runtime with X-Sandstorm-Base-Path

If your app can't use the empty string as a base URL, you can detect the base URL at runtime during every request by looking at a HTTP header.

sandstorm-http-bridge provides the base URL for this particular request into the app as an HTTP header: X-Sandstorm-Base-Path.

For example, if the user requests the page http://7575abdec6caa44bb83df0e00d7d8605.me.sandcats.io:6080/party, the app will receive a header of:

X-Sandstorm-Base-Path: http://7575abdec6caa44bb83df0e00d7d8605.me.sandcats.io:6080

Details

  • No trailing slash. This way it is ready for you to add your own path e.g. /party.

  • Includes the URI scheme. Therefore, if you need to check if the request is coming in over HTTP vs. HTTPS, you can use this header.

  • Can change with every request. Recall that this value is unsafe to cache in a global settings object, since the next request to your grain might use a different value.

  • Not sent for API requests. All app API requests share the same base URL, and this can't be used for HTML sent to web browsers, so Sandstorm does not send this header on API requests. Additionally, for sandboxing reasons, the API token is kept secret from the app.

Other headers available in Sandstorm

Sandstorm sends a Host: header and an X-Forwarded-Proto for convenience when porting apps. A request to http://7575abdec6caa44bb83df0e00d7d8605.me.sandcats.io:6080/party would also cause an app to receive the following HTTP headers.

  • Host: 7575abdec6caa44bb83df0e00d7d8605.me.sandcats.io:6080
  • X-Forwarded-Proto: http

It is therefore OK to look for X-Forwarded-Proto: https to detect HTTPS if being used.

For API requests: sandstorm-http-bridge does send a Host value of sandbox since some apps crash in the absence of a host header. It does not send a X-Forwarded-Proto however.

Apps operating without sandstorm-http-bridge

X-Sandstorm-Base-Path is created from the WebSession attribute called basePath. Read the current implementation for its Cap'n Proto documentation. Consider also reading the source of sandstorm-http-bridge.

Impact on caching

The fact that Sandstorm apps must send their static assets (such as CSS, Javascript, and images) on different URLs per session means that a web browser can't make good use of its cache.

This can have a negative impact on app load time in Sandstorm and mobile data use when compared to other hosting options. The Cap'n Proto definition of WebSession attribute indicates some possible future work in creating a shared space in Sandstorm that apps can push these assets to.