Tabs & Spaces Developers

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:

ts
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:

FieldWhat it is
user.id, user.nameWho opened the tab
workspace.id, workspace.slug, workspace.nameThe workspace
space.id, space.nameThe 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.kindgeneral, personal (someone's My Space), normal, or dm (a direct message)
tab.idThis tab, in this space. The same app tab added to two spaces has two ids.
tab.appTabIdWhich of your manifest's tabs this is
app.idYour app's id, from your manifest
localeThe 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:

ts
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:

ts
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:

AllowedWhy
allow-scriptsYour page runs
allow-same-originYour page keeps its own cookies and storage, on your origin
allow-formsForms submit
allow-popups, allow-popups-to-escape-sandboxLinks can open new tabs, which aren't sandboxed
allow-downloadsFiles 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.