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, RefreshToken och deviceId levereras 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