SDK guide: sign-in, data and sync
You build the app once. Each user points it at their own node and
signs in there; the node holds their account and the app’s data. This
guide is the path most apps take, using the Cloudoak class, which
looks after accounts and sync for you. The lower-level pieces
it is built from are in the reference.
The SDK is not on npm yet. It is developed in this repository,
cloudoakhq/cloudoak-sdk,
and ships as a tarball with each of its releases
(cloudoak-sdk-2.0.0.tgz, with the release’s SHA256SUMS). Download
it and install the file:
npm install ./cloudoak-sdk-2.0.0.tgzIt has no dependencies. React is an optional peer, needed only for
@cloudoak/sdk/ui.
The complete, runnable version of everything below is
examples/notes
in the node’s repository (about 100 lines).
examples/starter
is smaller still and is the one to copy.
1. Say where the node may send users back
Section titled “1. Say where the node may send users back”Sign-in happens on the node’s own page, and the node returns the user only to an address your manifest lists, exactly:
{ "app": { "id": "com.example.notes", "name": "Notes", "version": "1.0.0", "publisher": "example" }, "auth": { "redirect_uris": ["https://notes.example.dev/", "com.example.notes:/callback"], "origins": ["https://notes.example.dev"] }}redirect_uris are https addresses, http://localhost for
development, or a custom scheme in reverse-DNS form for a native app.
origins are the web origins allowed to call the node for this app;
a browser on any other origin is refused. A native app needs none.
2. Start
Section titled “2. Start”import { Cloudoak } from '@cloudoak/sdk';
const oak = await Cloudoak.init({ appId: 'com.example.notes', appVersion: '1.0.0', // this build; matches the manifest it was made for redirectUri: 'https://notes.example.dev/', // one of auth.redirect_uris storage: localStorage, // where accounts are remembered});Call it once, on every start. It loads the accounts this device
knows, finishes a sign-in if the node has just sent the user back, and
makes the account that was active last active again. After it,
oak.accounts.active is either an account or null.
storage is anything with getItem, setItem and removeItem.
Without it accounts live in memory and are gone on reload. On a phone,
pass a secure store (Keychain, Keystore).
3. Sign in
Section titled “3. Sign in”The user types the address of their node. You never ask for a password: the node does, on its own page, and it also draws the screen where the user approves your app.
await oak.accounts.add('cloud.example.com');On the web this leaves your page for the node’s and comes back to
redirectUri; Cloudoak.init finishes the job on the way back. In a
native app, pass openAuthSession to init to open the system
browser, and add resolves with the new account:
const oak = await Cloudoak.init({ appId, appVersion, storage, redirectUri: 'com.example.notes:/callback', openAuthSession: async (url, redirectUri) => { const result = await WebBrowser.openAuthSessionAsync(url, redirectUri); if (result.type !== 'success') throw new Error('sign-in was cancelled'); return result.url; },});Things that can come back instead of an account, as a CloudoakError
with a code:
pending_approval: this node has not seen your app before and its owner has to approve it. Tell the user to ask them.client_update_required,app_upgrade_required: this build and the version installed on the node do not fit.CloudoakVersionNoticerenders the right message for either.
A write can also fail with out_of_credits on a node that charges for
writes, when the user’s balance there is used up. Reading still works
and nothing is lost; tell the user to ask whoever runs the node.
A user can be signed in to several nodes, or as several people on one:
oak.accounts.list(); // every account on this device, most recent firstawait oak.accounts.switchTo(id); // local and instantawait oak.accounts.signOut(id); // revokes its tokens on its node, forgets it hereShow the user which account they are in: account.user.username and
account.nodeName. The SDK never gives you an email address.
4. Read and write
Section titled “4. Read and write”const notes = oak.tables('notes');await notes.create({ title: 'Hello' });const page = await notes.list(undefined, 50);
oak.documents('settings'); // JSON documentsoak.vectors('note_embeddings'); // vector searchoak.files('attachments'); // filesEvery call reads and writes the signed-in user’s own data. Nobody
else on the node reaches it through your app, and nobody reads it
without signing in. In the manifest a read rule is "owner" (or
"none"); "create": ["authenticated"] lets any signed-in user add
rows of their own.
Tokens are refreshed for you. Two tabs refreshing at once is fine.
5. Sync
Section titled “5. Sync”There is no push channel. The node keeps a change feed per app, and an app follows it by polling:
oak.on('sync', ({ changes, resync }) => { if (resync) reloadEverything(); // the first poll, or the node says start over else apply(changes); // [{ table, rowId, op, at }, …]});oak.startSync(5000); // poll every 5 seconds// oak.stopSync();What you can rely on:
- Nothing is skipped. A change appears in the feed once every write that began before it has finished, so two overlapping writes can never hide one another. This also means your own write may take a moment to show up in the feed; update the screen from the write’s own answer, not from the feed.
- Your place is remembered per account, across restarts. You never handle a cursor.
resync: truemeans “load in full”, and then carry on. You get it on the first poll, after the node was restored from a backup, and if the app has not synced for longer than the node keeps its feed (30 days by default). Treat it as ordinary: it is how the node says the short way is not available.- Poll while the app is visible, at an interval that suits it (5 to 30
seconds). Stop when it is hidden.
startSyncstops by itself on sign-out.
6. Signing out, and being signed out
Section titled “6. Signing out, and being signed out”oak.on('account:signed-out', ({ account, reason }) => { // 'user' they chose to // 'revoked' the node ended it: password changed, access removed, account closed // 'expired' 90 days since they signed in});For revoked and expired, say what happened and offer to sign in
again. Do not retry silently.
7. With React
Section titled “7. With React”@cloudoak/sdk/ui has a hook and two small components. The
main entry never loads React.
import { AccountMenu, CloudoakVersionNotice, useSession } from '@cloudoak/sdk/ui';
function App({ oak }) { const { account } = useSession(oak); // re-renders when accounts change return ( <> <AccountMenu oak={oak} /> {/* the gear: node, user, switch, add, sign out */} {account && <Notes oak={oak} />} </> );}There is no sign-in form and no approval sheet in the SDK. Both are the node’s.
What approving your app means
Section titled “What approving your app means”A user who approves your app gives it all of their data in your app on their node, and nothing else: not other apps, not their account. The scopes in your manifest describe what you use and are shown at approval; they are not enforced one by one today.
Checklist before you ship
Section titled “Checklist before you ship”- The manifest lists every address you return to, and every origin your web build runs on.
-
appVersionis this build’s version. - Accounts are kept in durable storage (a secure store on native).
- The screen always says which node and which user.
-
resyncreloads;account:signed-outtells the user something. - A failed write keeps what the user typed.
Built from spec 2.1.0, SDK 2.0.0 and the node's guide at v0.6.0-alpha.1.