Website JavaScript
Read the buyer's account and sell from your own code with window.Toolaby, in plain JavaScript, React or Next.js.
toolaby-web.js defines window.Toolaby: the buyer's account as state you can read and subscribe to, and the actions the website components perform, as methods.
This page is the reference for window.Toolaby, then React and Next.js. It assumes the script is on the page; see Your website.
window.Toolaby
window.Toolaby exists once the script has run. The toolaby:ready event on window fires when the first answer arrives; event.detail is Toolaby.state.
window.addEventListener('toolaby:ready', () => {
const { signedIn, email, tools } = Toolaby.state;
if (signedIn && tools['word-count']) showProFeatures();
});| Member | Returns | |
|---|---|---|
Toolaby.state | object | The last answer: ready, mode (cookie, proxy, dev or link), signedIn, email, name, image, and tools, where tools[id] is true for each tool the buyer holds. |
Toolaby.onChange(fn) | function | Calls fn(state) now and after every change. Returns a function that stops it. |
Toolaby.refresh() | Promise<state> | Asks the Wall again. |
Toolaby.entitlements() | Promise<object> | { signedIn, email, name, image, tools }, straight from the Wall. |
Toolaby.session() | Promise<object | null> | The signed-in account, or null. On your domain, the session as the sign-in library returns it ({ session, user }). Through a proxy or in development the page reaches only the site's endpoints, so it is { user: { email, name, image } }. |
Toolaby.signIn({ email, next }) | Promise | With email, on your domain, sends a sign-in link to it. Without, or through a proxy or in development, opens the sign-in page. next is where the buyer lands; the default is this page. |
Toolaby.signInUrl(next) | string | The sign-in page's address, returning to next or this page. |
Toolaby.accountUrl() | string | The account page's address. |
Toolaby.signOut() | Promise | Signs the buyer out, then refreshes. |
Toolaby.checkout({ tool, plan, email, ext, seats }) | Promise | Opens Stripe Checkout for the tool. seats buys a team licence or subscription on a plan sold per seat. A subscription signed out signs in first and continues. |
Toolaby.startTrial({ tool, ext }) | Promise | Starts the tool's free trial for the signed-in buyer, then opens the after-purchase page. The trial lasts as long as the longest trial a plan offers and unlocks that plan's features: see Website components. Signed out, signs in first and continues. Refused, it rejects with the reason: no trial, an unverified or a disposable address. |
Toolaby.portal({ subscriptionId }) | Promise | Opens the Stripe customer portal. |
Toolaby.purchase() | Promise<object> | On your after-purchase page: { status, tool, email, amount, inAccount, devices } for the purchase Stripe returned the buyer from. devices is how many devices the plan bought allows. |
Toolaby.origin is where the script sends its requests: your workspace's address, or your proxy's path on your own domain.
A method that fails rejects with an Error whose message is fit to show, and status is the HTTP status when there was one. With no connection (mode is link), the methods that need the session reject; checkout() and startTrial() open the checkout page, and portal() the account page, instead.
Selling from code
document.querySelector('#buy').addEventListener('click', async (event) => {
event.target.disabled = true;
try {
await Toolaby.checkout({ tool: 'word-count', plan: 'yearly' });
} catch (e) {
event.target.disabled = false;
showError(e.message);
}
});checkout() sends the page to Stripe Checkout. A failure rejects with the reason; a subscription asked for signed out goes to the sign-in page first and continues on return. After payment the buyer lands on the after-purchase page. The Wall's own signs the extension that sent them (ext) in to the account the purchase is on: at once, if the buyer paid signed in, or through its sign-in, if they paid signed out. On your own after-purchase page (Your own pages), the extension unlocks when the buyer signs in inside it.
React
Read the state with a hook:
'use client';
import { useEffect, useState } from 'react';
type ToolabyState = { ready: boolean; mode: 'cookie' | 'proxy' | 'dev' | 'link' | null; signedIn: boolean; email: string | null; name: string | null; image: string | null; tools: Record<string, true> };
declare global { interface Window { Toolaby?: { state: ToolabyState; onChange(fn: (s: ToolabyState) => void): () => void; checkout(o: { tool: string; plan?: string; ext?: string; seats?: number }): Promise<unknown>; startTrial(o: { tool: string; ext?: string }): Promise<unknown>; signInUrl(next?: string): string; accountUrl(): string } } }
const initial: ToolabyState = { ready: false, mode: null, signedIn: false, email: null, name: null, image: null, tools: {} };
export function useToolaby(): ToolabyState {
const [state, setState] = useState<ToolabyState>(initial);
useEffect(() => {
let stop = () => {};
const attach = () => { stop = window.Toolaby!.onChange(setState); };
if (window.Toolaby) attach(); else window.addEventListener('toolaby:ready', attach, { once: true });
return () => { stop(); window.removeEventListener('toolaby:ready', attach); };
}, []);
return state;
}Render nothing for the signed-in part until ready is true: before it, the state is unknown.
export function ProBadge() {
const { ready, tools } = useToolaby();
if (!ready || !tools['word-count']) return null;
return <span className="badge">Pro</span>;
}The account button works in JSX as it is. For TypeScript, declare it once:
declare module 'react' {
namespace JSX {
interface IntrinsicElements {
'toolaby-user': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> & { 'sign-in-url'?: string; 'account-url'?: string; 'after-sign-out-url'?: string; 'sign-in-label'?: string; theme?: 'light' | 'dark'; align?: 'start' | 'end' };
}
}
}Next.js
Load the script once, in the root layout:
import Script from 'next/script';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
<Script src="https://accounts.example.com/client/toolaby-web.js" strategy="afterInteractive" />
</body>
</html>
);
}The buyer's account is known in the browser, not on your server: useToolaby() belongs in client components. A server component renders the signed-out shape and the client fills it in. Through a proxy, add the route the dashboard shows as app/__toolaby/[...path]/route.ts and load the script from /__toolaby/client/toolaby-web.js.
To decide on your server who holds a tool — to render private content, or to answer an API of your own — ask the Wall from the server, with an API key: see Sync customers.