Kodexempel
Fyra funktioner räcker för en komplett integration. Alla värden nedan är platshållare — byt ut dem mot era egna via miljövariabler, aldrig i koden.
Konfiguration
BASE_URL=https://staging.zesec.com/api
KIION_PHONE_NUMBER={{phoneNumber}}
KIION_PASSWORD={{password}}1. login#
Körs en gång vid uppstart, och som fallback när en refresh misslyckats. Aldrig per öppning.
curl -X POST https://staging.zesec.com/api/auth/login-by-phone \
-H "Content-Type: application/json" \
-d '{"phoneNumber": "{{phoneNumber}}", "password": "{{password}}"}'2. getValidAccessToken#
Proaktiv förnyelse var 36:e timme, single-flight-lås och fallback till ominloggning. All annan kod anropar den här, aldrig login direkt.
const PROACTIVE_REFRESH_MS = 36 * 60 * 60 * 1000;
let inFlight: Promise<string> | null = null;
/** Single active session per account: one process, one token pair, one refresh at a time. */
export async function getValidAccessToken(): Promise<string> {
const current = await session.read();
if (current && Date.now() - current.rotatedAt < PROACTIVE_REFRESH_MS) {
return current.accessToken;
}
// Single-flight guard: concurrent callers must not each burn a single-use refresh token.
inFlight ??= (async () => {
try {
return current ? await rotate(current.refreshToken) : await login();
} catch (err) {
if (err instanceof RefreshFailed) return await login(); // refresh token spent or expired
throw err;
} finally {
inFlight = null;
}
})();
return inFlight;
}
/** Wrap every API call: one refresh, one replay, then surface the failure. */
export async function apiCall(path: string, init: RequestInit = {}) {
let token = await getValidAccessToken();
let res = await fetch(`${BASE_URL}${path}`, withAuth(init, token));
if (res.status === 401) {
await session.invalidateAccessToken();
token = await getValidAccessToken();
res = await fetch(`${BASE_URL}${path}`, withAuth(init, token));
}
return res; // still 401 -> stop and alert an operator; do not loop
}Rotationen som sparar båda tokens
async function rotate(currentRefreshToken: string) {
const res = await fetch(`${BASE_URL}/auth/refresh`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ refreshToken: currentRefreshToken }),
});
if (!res.ok) throw new RefreshFailed(res.status);
const { AccessToken, RefreshToken } = await res.json();
// One write. Never persist the access token without the refresh token that came with it:
// the token you sent is already dead, so a partial save loses the session for good.
await session.replace({ accessToken: AccessToken, refreshToken: RefreshToken, rotatedAt: Date.now() });
return AccessToken;
}Refresh-anropet i cURL
curl -X POST https://staging.zesec.com/api/auth/refresh \
-H "Content-Type: application/json" \
-d '{"refreshToken": "<RefreshToken>"}'3. syncDevices#
Hämtar enhetslistan och skriver om mappningen. Körs på schema — inte i öppningsögonblicket.
curl https://staging.zesec.com/api/user/device \
-H "Authorization: Bearer <AccessToken>"4. unlockDoor#
Ett anrop, med ett {deviceId} från den cachelagrade mappningen.
curl -X PUT https://staging.zesec.com/api/device/{deviceId}/unlock \
-H "Authorization: Bearer <AccessToken>"Postman-samling#
Ladda ner KiiOn-samlingen för Postman. Den innehåller de fyra anropen, en BASE_URL-variabel och ett testskript som sparar tokenparet efter login och refresh.
- Samlingen är skriven av KiiOn för den publika API-ytan — den innehåller inga partner- eller kundspecifika anrop.
- Variablerna
phoneNumber,password,AccessToken,RefreshTokenochdeviceIdlevereras tomma. - Lägg era uppgifter i en Postman-miljö med tomt *initial value*, så följer de inte med vid export.
Vidare
Kopiera sidan som Markdown: Visa som Markdown