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.
Navigating to paths within a grain
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 ahref=
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.