Team & Access

Smart-lock provider abstraction

← Back to Team & Access

All smart-lock operations (Nuki, 2N, future providers) route through one unified LockProvider facade. Operator-visible behaviour is unchanged; the abstraction lets new lock vendors plug in cleanly and makes error handling consistent.

V28 Smart Locks

What the operator sees (unchanged)

  • Smart Locks screen shows one card per property with a lock configured.
  • Codes editor + audit log + activity log per property.
  • Same buttons, same behaviour — Generate PIN, Revoke, Test Connection.

What changed underneath

  • One API — every lock call (generate code, revoke code, unlock, get activity) goes through lockOps.<operation> with the property's provider auto-resolved.
  • Retry policy consistent — 5 attempts on transient failures across ALL lock calls.
  • Never-silent-error — every lock error surfaces on the operator screen + writes an app_errors row; a transient Nuki 5xx is retried, never silently dropped.
  • Adds new providers cleanly — adding a new lock vendor requires implementing the LockProvider interface + registering in the provider registry. Four steps, documented in docs/lock-integration-api.md.

Why this matters for the operator

  • Error visibility — when a lock call fails, you see it: the failure surfaces on screen and a retry runs.
  • Consistent behaviour across providers — same retry, same error UX regardless of Nuki vs 2N vs a future provider.
  • New provider path — if a tenant wants to onboard a new lock brand (Salto, August, TTLock, …), the platform can add it without rewriting every callsite.

What's still per-provider

  • Provider capabilities — each provider declares what it supports (generateCode, revokeCode, unlock, getActivity). Not every provider supports every capability. The V28 Smart Locks screen shows disabled controls with a tooltip when the property's provider doesn't support the operation (mirrors the Channel sync capability-matrix pattern).
  • Provider-specific config — Nuki API token vs 2N intercom IP address vs whatever the new provider needs. Configured per-property in Settings.

2N lifecycle: dispose

2N sessions require an explicit dispose call to release resources on the intercom side. The abstraction handles this automatically — no operator step needed.

No-direct-import ratchet

A CI ratchet (server/tests/ratchets/noDirectLockClientImportRatchet.test.ts) fails PR if any file imports Nuki or 2N clients directly instead of going through lockOps. Prevents drift back to the prior implementations.

Source: the luxury.host operator manual.