Golden path & rekommenderad arkitektur
Följer ni det här mönstret blir integrationen både snabbare och stabilare — och ni behöver nästan aldrig kontakta supporten.
Sekvensen#
- Vid uppstart / på schema: logga in server-side en gång. Spara access- och refresh-token i delad lagring.
- På schema: förnya token proaktivt var 36:e timme, inte när något gått sönder.
- På schema eller vid behov: hämta enhetslistan och skriv om mappningen. Ett dygn (24 h) är en rimlig utgångspunkt.
- I öppningsögonblicket: slå upp
{deviceId}i mappningen och gör ett unlock-anrop.
Ansvarsfördelningen#
| Lager | Ansvar | Får inte |
|---|---|---|
| Er klient / app | Visa dörrar, ta emot ett tryck | Aldrig hålla tokens eller anropa KiiOn direkt |
| Er backend | Behörighetsregler, tokenhantering, mappning, audit | Logga in per öppning |
| KiiOn-API:t | Utföra upplåsningen | Avgöra om användaren borde få öppna |
1. Tokenlagret#
En singleton per API-konto som äger inloggning, förnyelse och 401-hantering. All annan kod ber om en giltig token, aldrig om en inloggning. Detaljerna finns i Autentisering & tokens.
Node / TypeScript — getValidAccessToken
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
}2. Enhetsmappningen#
Enhetslistan ändras sällan; unlock-anropen sker ofta. Hämta listan på schema och spara minst Device.Id och Device.Name — gärna även plats och företag så att felsökning går att göra utan att slå i API:t.
Node / TypeScript — syncDevices
/** Runs on a schedule, not on the unlock path. */
export async function syncDevices() {
const res = await apiCall("/user/device");
const devices: Device[] = await res.json();
await db.deviceMapping.replaceAll(
devices.map((d) => ({
kiionDeviceId: d.id, // fills {deviceId} in the unlock call
kiionName: d.name,
locationId: d.locationId,
locationName: d.locationName,
syncedAt: Date.now(),
})),
);
}- Uppdatera mappningen var 24:e timme, samt när ni får
404på ett känt{deviceId}. - Låt mappningen vara nyckeln mellan er interna dörridentitet och KiiOns
id— hårdkoda aldrig id:t. - Spara
syncedAtså att ni kan se hur gammal mappningen är när något ser fel ut.
3. Öppningsögonblicket#
Här ska det bara finnas ett HTTP-anrop. Behörighetskontrollen är er, uppslaget är lokalt, token är redan giltig.
Node / TypeScript — the unlock path
/** Everything expensive already happened. This is one call. */
export async function openDoor(internalDoorId: string, actor: User) {
// 1. YOUR rules decide. KiiOn does not.
if (!(await mayOpen(actor, internalDoorId))) throw new Forbidden();
// 2. Cached mapping — no device-list call here.
const deviceId = await db.deviceMapping.kiionIdFor(internalDoorId);
if (!deviceId) throw new NotMapped(internalDoorId);
// 3. One call, with a token that is already valid.
const res = await apiCall(`/device/${deviceId}/unlock`, { method: "PUT" });
await audit.record({ actor, internalDoorId, deviceId, status: res.status });
return res.ok;
}Checklista#
- Inloggning sker server-side och återanvänds
- Proaktiv token-förnyelse med single-flight-lås
- 401-fallback som gör om anropet en gång
- Roterad refresh-token sparas atomiskt
- Enhetslistan cachelagras och hårdkodas inte
- Öppningsögonblicket innehåller exakt ett API-anrop
- Varje upplåsning skrivs till er egen audit-logg
Vidare
Kopiera sidan som Markdown: Visa som Markdown