Tabs & Spaces Developers

Reference

The SDK

@tabs-and-spaces/sdk has no dependencies and two entry points:

  • @tabs-and-spaces/sdk, for your page: connect().
  • @tabs-and-spaces/sdk/server, for your server: verifyToken(), fetchKeys() and, for actions, dataClient().

connect()

ts
function connect(options?: ConnectOptions): Promise<Connection>;

Connects your page to the workspace showing it. It says hello, then resolves when the workspace answers with who's looking.

OptionDefaultWhat it does
timeoutMs10000How long to wait for the workspace before rejecting
windowwindowThe window your page runs in (for tests)

It rejects with an Error when:

  • the page isn't open in a workspace ("This page isn't open in a Tabs & Spaces workspace.");
  • the workspace doesn't answer in time ("The workspace didn't answer.").

Connection

MemberWhat it is
contextWho's looking, and where: a TabContext
theme"light" or "dark", now
token()Promise<string>: the current token, or a fresh one when less than a minute is left. Calls made together share one request. It rejects if the workspace refuses, for example if your app was removed.
values(scope)Free-form values in a scope (workspace, space, tab or user): get(key), set(key, value), delete(key), list(). See Keeping data.
collection(name)A collection your manifest declares: list(options), get(id), create(data), update(id, data), delete(id). See Keeping data.
data(request)Any data request, as values() and collection() send it
emit(name, data)Promise<void>: reports an event your manifest declares, for the person looking, in this space. See Events.
onTheme(listener)Calls listener(theme) when the reader switches theme. Returns a function that stops it.
close()Stops listening. Pending token() calls reject.

TabContext

ts
interface TabContext {
  user: { id: string; name: string };
  workspace: { id: string; slug: string; name: string };
  space: {
    id: string;
    kind: "general" | "personal" | "normal" | "dm";
    name: string; // as the reader sees it, in their language
  };
  tab: { id: string; appTabId: string };
  app: { id: string };
  locale: string; // "en", "fr", …
}

WorkspaceError

Data calls reject with a WorkspaceError when the workspace refuses. Its code says why:

CodeWhen
INVALIDThe data doesn't fit the collection, or the request is malformed
NOT_FOUNDNo such collection or record, here
FORBIDDENOnly people in the space can change its data
TOO_LARGEA value over 16 KB, a record over 32 KB, or a request over 64 KB
APP_DATA_FULLYour app keeps 100 MB here already
APP_NOT_INSTALLEDYour app was removed from the workspace

verifyToken()

ts
function verifyToken(
  token: string,
  options: { keys: PublicKey[]; audience: string; issuer?: string; now?: number },
): Promise<VerifiedToken>;

Checks a token and returns who's looking:

OptionWhat it is
keysThe deployment's public keys, from fetchKeys()
audienceYour app's origin, like https://crm.example.com
issuerThe deployment's address. Pass it; see Decide which workspaces you serve.
nowThe time, in milliseconds (for tests)

It resolves with { context: TabContext, issuer: string, expiresAt: Date, action: string | null }; action names the action an action's token is for. It throws a TokenError when the token:

  • isn't a token;
  • uses another algorithm;
  • names a key it doesn't know;
  • has a signature that doesn't match;
  • is meant for another app;
  • comes from another deployment;
  • has expired.

fetchKeys()

ts
function fetchKeys(deploymentUrl: string, fetcher?: typeof fetch): Promise<PublicKey[]>;

Fetches a deployment's public keys from <deploymentUrl>/api/apps/keys. It throws a TokenError if they can't be fetched. Cache the result. See Keys.

dataClient()

ts
function dataClient(
  request: { dataKey: string; dataUrl: string },
  fetcher?: typeof fetch,
): {
  values(scope): Values;
  collection(name): Collection;
  data(request): Promise<unknown>;
  emit(name: string, data?: Record<string, unknown>): Promise<void>;
};

Your app's data from your server, with the key an action's request carries (pass the whole ActionRequest). The same values() and collection() as in your page, for the action's place and person, for five minutes. Refusals throw a DataError with a code. See Your data, from your server. emit() reports an event with the same key.

ActionRequest<Input> is the type of an action request's body: action, input, context, agent, dataKey and dataUrl.

Messages

The SDK also exports the frame protocol's types and readers (AppMessage, HostMessage, DataRequest, DataRecord, readAppMessage(), readHostMessage() and PROTOCOL_VERSION), for anyone speaking it directly. See The frame protocol.