Build
Building a Custom tab
A Custom tab is a web page you host. The workspace shows it in a sandboxed frame that fills the tab, and tells it who's looking.
Connect
With the SDK:
import { connect } from "@tabs-and-spaces/sdk";
const tands = await connect();
document.querySelector("h1")!.textContent = `Hello, ${tands.context.user.name}`;connect() says hello to the workspace and resolves once it answers. It rejects when the page isn't open in a workspace (someone opened its address directly), or when the workspace doesn't answer within ten seconds. Show something useful in that case, such as "Open this page from a Tabs & Spaces workspace".
Without the SDK, the conversation is a few postMessage calls. See The frame protocol.
What you're told
tands.context holds:
| Field | What it is |
|---|---|
user.id, user.name | Who opened the tab |
workspace.id, workspace.slug, workspace.name | The workspace |
space.id, space.name | The space, and its name as the reader sees it: General and My Space in their language, a direct message by its other people's names |
space.kind | general, personal (someone's My Space), normal, or dm (a direct message) |
tab.id | This tab, in this space. The same app tab added to two spaces has two ids. |
tab.appTabId | Which of your manifest's tabs this is |
app.id | Your app's id, from your manifest |
locale | The reader's language, like en or fr |
That's everything. Your app isn't told about messages, files, tasks or anyone else in the space.
Follow the theme
tands.theme is "light" or "dark", and onTheme() tells you when the reader switches:
document.documentElement.dataset.theme = tands.theme;
tands.onTheme((theme) => (document.documentElement.dataset.theme = theme));The frame's color-scheme matches the workspace's. A page that leaves its background transparent sits on the tab's own background.
Call your server
token() gives you a token to send with your requests. It returns the current one, or fetches a fresh one when less than a minute is left:
const response = await fetch("/api/contacts", {
headers: { authorization: `Bearer ${await tands.token()}` },
});Your server checks the token, then uses context from it, not from the request. See Checking who's looking.
The sandbox
Your page runs in an iframe with these permissions:
| Allowed | Why |
|---|---|
allow-scripts | Your page runs |
allow-same-origin | Your page keeps its own cookies and storage, on your origin |
allow-forms | Forms submit |
allow-popups, allow-popups-to-escape-sandbox | Links can open new tabs, which aren't sandboxed |
allow-downloads | Files can be downloaded |
Not allowed:
- navigating the workspace's own window;
- device features such as the camera, microphone or location;
- full screen.
The frame's referrer policy is origin. Your page fills the tab's width and height, so design for any size, down to a phone's.
Under the frame, the workspace shows where it comes from ("From crm.example.com") and a Reload button. Reloading loads your page again, and it gets a fresh token.
Pages on your origin only
Every page your manifest lists must be on the same origin as the manifest itself. Inside the frame, your page can link and navigate anywhere on that origin. The workspace only talks to your origin: messages from any other origin, or from any window but your frame, are ignored.