Skip to content

Browser & server

@qratilabs/qrati-sdk has no separate /server or /browser entry — one package, one createQrati(), works unmodified wherever it runs. It detects its environment at the one point that actually differs (the S3 PUT transport) and otherwise doesn’t care.

import { createQrati } from '@qratilabs/qrati-sdk';
const qrati = createQrati(API_KEY, { uid, fname, lname });
await qrati.upload({
eventId,
file, // from <input type="file"> or a drop zone
onProgress: ({ percent }) => setProgress(percent),
});

The PUT to S3 goes out via XMLHttpRequest, so onProgress fires real, byte-level upload.onprogress events. Requests carry an Origin header (the browser adds it automatically) — the platform checks that origin against your organization’s domain allowlist, same as qrati-connect-ts. See API key security.

import { createQrati } from '@qratilabs/qrati-sdk';
import { readFile } from 'node:fs/promises';
const qrati = createQrati(process.env.QRATI_API_KEY!);
const buffer = await readFile('./photo.jpg');
const file = new File([buffer], 'photo.jpg', { type: 'image/jpeg' });
await qrati.upload({ eventId, file, identity: { uid } });

Identical call shape — createQrati, upload(), the same QratiIdentity. Two practical differences:

  • Progress: Node’s fetch (undici) doesn’t expose upload progress, so the SDK falls back to a streaming PUT and calls onProgress once, at 100%, when it finishes — not a series of incremental events like in the browser.
  • No domain check: a server-to-server call has no browser Origin header, so the domain allowlist (a browser-only concept — there’s no “origin” for a backend process) doesn’t apply. A request authenticated with a valid API key and no Origin header is treated as your own backend calling in, not a browser page to vet.

One client instance can safely serve many end-users this way — pass identity per call instead of binding one at construction:

const qrati = createQrati(process.env.QRATI_API_KEY!);
export async function uploadOnBehalfOf(uid: string, eventId: string, file: File) {
return qrati.upload({ eventId, file, identity: { uid } });
}

Requires Node 20+ (for global fetch, File, and Blob) — matches the SDK’s engines.node requirement.

There’s nothing to configure and no build-time branch to pick — bundlers (Vite, webpack, Next.js) ship the browser path to the client and Node resolves the same module server-side; createQrati() picks the right PUT transport at call time by checking whether XMLHttpRequest exists.