Auth, Sync & HTTP
Install @absolutejs/auth and your app signs users in. Add @absolutejs/sync and it keeps their data encrypted on the device and works offline. Your page code stays exactly as it is on the web.
#Sign-in
When @absolutejs/auth is in your package.json, the app becomes a signed-in client of your server. There is nothing to add to absolute.config.ts.
import { createAuthClient } from '@absolutejs/auth/client';
const authClient = createAuthClient();
// In the app this opens your sign-in page in the system browser,
// with the email filled in, and returns once the user is signed in.
await authClient.signIn.email({ email, password });
const { data } = await authClient.status();
const signedIn = Boolean(data?.user);
await authClient.signOut();AbsoluteJS registers the app with your server for you:
#Server setup
The app signs in through Auth’s OpenID Connect provider, so turn on oidc in your auth config. Give it a socketTicketStore when you use Sync: the app opens its socket with a short-lived, single-use ticket instead of putting a token in the URL.
import { auth } from '@absolutejs/auth';
const authApplication = await auth({
getUser,
providersConfiguration,
oidc: {
// ...your issuer configuration
socketTicketStore // lets Sync open authenticated sockets from the app
}
});@absolutejs/auth is installed but its OIDC provider is not mounted, so you find out before a release, not from users.#API calls
For plain calls to your own API, use @absolutejs/http. It works the same in the browser and the app, and it is how the signed-in user’s credentials reach your routes from the app.
import { AbsoluteHttpError, http } from '@absolutejs/http';
type Order = { id: string; total: number };
try {
const orders = await http.get<Order[]>('/api/orders');
render(orders);
} catch (error) {
if (error instanceof AbsoluteHttpError && error.status === 401) showSignIn();
else throw error;
}#Offline data
Your app’s screens start offline, but each page asks your server for its data. Data that must be there without a connection belongs in @absolutejs/sync. With Auth and Sync both installed, every Sync client in your pages gets a durable, encrypted store on the device. You write the same code as on the web, or use the @absolutejs/sync/react, /svelte, /vue and /angular bindings.
import { createSyncCollection } from '@absolutejs/sync/client';
const orders = createSyncCollection({
url: 'wss://shop.example.com/sync/ws',
collection: 'orders',
params: { status: 'open' }
});
orders.subscribe((state) => render(state.data));
// Applied on screen immediately, queued while offline,
// and reconciled with the server when the app reconnects.
await orders.mutate({
name: 'createOrder',
args: { total: 42 },
optimistic: (draft) => draft.set({ id: tempId, total: 42, status: 'open' })
});#What is stored
Describe your offline data under absolutejs.sync.localSchema in package.json. Packages you install can declare their own, and the app combines them. Without one, your app’s data is version 1 with no rules.
{
"absolutejs": {
"sync": {
"localSchema": {
"version": 2,
"migrations": [
{
"toVersion": 2,
"operations": [
{ "type": "rename-field", "collection": "orders", "from": "sum", "to": "total" }
]
}
],
"localData": {
"collections": [
{
"match": "orders",
"sensitivity": "private",
"protection": "required",
"onProtectionUnavailable": "memory-only",
"evictionPriority": "critical"
},
{
"match": "catalog*",
"sensitivity": "public",
"protection": "none",
"evictionPriority": "disposable",
"maxAgeMs": 604800000
}
],
"mutations": [
{
"match": "createOrder",
"sensitivity": "private",
"protection": "required",
"conflict": { "strategy": "client-wins", "maxAttempts": 5 }
}
],
"maxBytesPerNamespace": 52428800
}
}
}
}
}| Rule | Meaning |
|---|---|
match | A collection or mutation name, or a pattern with *. |
sensitivity | public, private or secret. Private and secret data must be protected or memory-only. |
protection | required encrypts it on the device; none stores it as is. |
onProtectionUnavailable | error, or memory-only to keep it only in memory where the device cannot encrypt, such as some browsers. |
persistence | durable (the default) or memory-only. |
evictionPriority | critical, normal or disposable: what is removed first when the store is full. Queued writes are never removed. |
maxAgeMs | Drop a cached collection older than this. |
conflict | For mutations: manual (keep it for you to resolve, the default), server-wins, or client-wins with maxAttempts. |
maxBytesPerNamespace | A size limit for each account’s store. |
#Schema changes
Raise version when the shape of stored data changes, and add a migration whose toVersion is the new version. The device runs every step from the version it has to the new one, once, before your pages read the store, so each version needs its migration. A release upgrades data stored by up to two earlier versions; set minimumCompatibleVersion to reach further back. Data older than that reports SCHEMA_TOO_OLD through absolute:sync-schema.
| Operation | Fields |
|---|---|
rename-field | collection, from, to |
remove-field | collection, field |
set-default | collection, field, value |
delete-collection | collection |
#Status and failures
Two window events report what Sync is doing: absolute:sync-status for the connection and queue, and absolute:sync-schema for the stored data’s version.
addEventListener('absolute:sync-status', (event) => {
const { connection, pending, deadLetters } = event.detail;
showSyncBadge({ connection, pending, failed: deadLetters });
});
addEventListener('absolute:sync-schema', (event) => {
if (event.detail.state === 'failed') showUpdateRequired(event.detail.code);
});A write the server rejected is kept rather than lost. Inspect it, retry it, send it again with new arguments, or discard it:
import { getAbsoluteMobileSyncRemediation } from '@absolutejs/absolute/mobile';
const remediation = getAbsoluteMobileSyncRemediation();
if (remediation) {
const { deadLetters } = await remediation.inspect();
for (const failed of deadLetters) {
if (failed.kind === 'retryable') await remediation.retry(failed.operationId);
else if (failed.kind === 'conflict') showConflict(failed);
}
}#Packages
The build installs the native packages for you. Push notifications use the same sign-in; see Push notifications.
@absolutejs/authSign-in, sessions and the OIDC provider your app signs in through.
@absolutejs/syncLive collections, optimistic writes and the offline queue.
@absolutejs/httpHTTP to your own server with the user’s credentials handled for you.
@absolutejs/sync-capacitorThe encrypted SQLite store and background sync for Capacitor apps.
@absolutejs/auth-expoSign-in for Expo apps.
@absolutejs/sync-expoThe encrypted store and background sync for Expo apps.