npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dooraccess/opendoor-web-sdk

v1.0.2

Published

JavaScript/TypeScript SDK for embedding door code access into web applications

Readme

OpenDOOR Web SDK

JavaScript/TypeScript SDK for embedding door code access into your web application. Your guests authenticate via email OTP, and the SDK fetches and displays their door codes.

Installation

npm install @dooraccess/opendoor-web-sdk

Or load via CDN:

<script src="https://unpkg.com/@dooraccess/opendoor-web-sdk"></script>

How It Works

The integration has two parts: your backend handles authentication (because it requires secrets), and the web SDK runs in the guest's browser and fetches door codes.

Guest's Browser                   Your Backend                      DOOR
───────────────                   ────────────                      ────

1. Guest enters email       ──>   POST /passwordless/start     ──>  DOOR sends OTP email
                                  (includes client_id + secret)

2. Guest enters OTP code    ──>   POST /oauth/token            ──>  DOOR returns JWT
                                  (includes client_id + secret)      + refresh_token
                            <──   Return JWT to browser         <──

3. Browser initializes SDK
   with the JWT

4. SDK calls getLocks()     ──>  Returns locks + door codes

5. JWT expires, SDK calls
   onTokenExpired()         ──>   POST /oauth/token            ──>  DOOR refresh_token grant
                                  (includes client_id + secret)
                            <──   Return new JWT                <──

Steps 1, 2, and 5 go through your backend because they require client_id and client_secret, which must never be exposed in browser code. The SDK's lock/device calls run in the browser and call the DOOR API directly with the JWT.

Step 1: Your Backend — Authentication

Your backend handles the passwordless OTP flow. The SDK does not manage authentication — it receives a JWT from your backend.

Obtain credentials

You'll receive a client_id and client_secret from DOOR during onboarding. These are scoped to your account.

Send the OTP email

When a guest wants to access their door codes, your backend triggers an OTP email:

POST https://auth.prod.latch.com/passwordless/start
Content-Type: application/json

{
  "client_id": "YOUR_CLIENT_ID",
  "client_secret": "YOUR_CLIENT_SECRET",
  "connection": "email",
  "send": "code",
  "email": "[email protected]"
}

This sends a 6-digit code to the guest's email.

Exchange the OTP for a JWT

After the guest enters the code, your backend exchanges it for tokens:

POST https://auth.prod.latch.com/oauth/token
Content-Type: application/json

{
  "grant_type": "http://auth0.com/oauth/grant-type/passwordless/otp",
  "client_id": "YOUR_CLIENT_ID",
  "client_secret": "YOUR_CLIENT_SECRET",
  "username": "[email protected]",
  "otp": "123456",
  "realm": "email",
  "scope": "openid profile email offline_access",
  "audience": "https://rest.latchaccess.com/access/sdk"
}

The audience parameter is required. Without it, the JWT will not have the correct permissions to call the DOOR API.

Response:

{
  "access_token": "eyJhbGciOi...",
  "refresh_token": "v1.MjY0OTFk...",
  "token_type": "Bearer",
  "expires_in": 86400
}
  • access_token: Pass this to the SDK. Valid for 24 hours.
  • refresh_token: Store this securely on your backend. Used to get a new access_token when the current one expires.

Return the JWT to the browser

Your backend returns the access_token to the browser (e.g., via a JSON response). Never return the refresh_token or client_secret to the browser.

Step 2: Browser — Initialize the SDK

Once the browser has the JWT, initialize the SDK:

import { OpenDOORClient } from '@dooraccess/opendoor-web-sdk';

const client = new OpenDOORClient({
  token: jwtFromYourBackend,
  onTokenExpired: async () => {
    // Call your backend to refresh the token (see Step 3)
    const res = await fetch('/api/auth/refresh', { method: 'POST' });
    const { token } = await res.json();
    return token;
  },
});

// Fetch all locks and door codes
const locks = await client.getLocks();

locks.forEach(lock => {
  console.log(`${lock.name}: ${lock.doorCode ?? 'No code — use app to unlock'}`);
});

// Clean up when done
client.destroy();

Via CDN / script tag:

<script src="https://unpkg.com/@dooraccess/opendoor-web-sdk"></script>
<script>
  var client = new OpenDOOR.OpenDOORClient({
    token: jwtFromYourBackend,
    onTokenExpired: function () {
      return fetch('/api/auth/refresh', { method: 'POST' })
        .then(function (res) { return res.json(); })
        .then(function (data) { return data.token; });
    },
  });

  client.getLocks().then(function (locks) {
    // Render locks in your UI
  });
</script>

Step 3: Your Backend — Token Refresh

JWTs expire after 24 hours. The SDK detects expiry and calls your onTokenExpired callback automatically. Your backend should use the stored refresh_token to get a new access_token:

POST https://auth.prod.latch.com/oauth/token
Content-Type: application/json

{
  "grant_type": "refresh_token",
  "client_id": "YOUR_CLIENT_ID",
  "client_secret": "YOUR_CLIENT_SECRET",
  "refresh_token": "STORED_REFRESH_TOKEN",
  "audience": "https://rest.latchaccess.com/access/sdk"
}

The audience parameter is required on refresh too. Without it, the new JWT won't have the correct permissions.

Response:

{
  "access_token": "eyJhbGciOi...",
  "token_type": "Bearer",
  "expires_in": 86400
}

Return the new access_token to the browser. If a new refresh_token is included in the response, update your stored copy.

How the SDK handles expiry

The SDK checks the JWT's exp claim before every API call. If expired (or within 30 seconds of expiry):

  1. If onTokenExpired is provided: The SDK calls it, waits for the new token, and retries the request automatically. The guest never sees an interruption.
  2. If onTokenExpired is not provided: The SDK throws an AuthError with a message explaining what happened.

If a request returns 401 (token rejected server-side), the same flow applies — the SDK calls onTokenExpired and retries once.

Avoiding Repeated OTP Verification

The OTP flow only needs to happen once. After the initial verification, your backend has a refresh_token that can mint new JWTs for the duration of the guest's stay — no additional OTP emails needed.

The recommended approach is to tie the DOOR refresh_token to your existing user session:

  1. Guest verifies OTP once — your backend receives the access_token and refresh_token from DOOR
  2. Store the refresh_token in your session — associate it with the guest's session in your app (e.g., in your session store, database, or server-side cache, keyed to your session cookie)
  3. On subsequent page loads — the guest is already logged into your app. Your backend checks the session, finds the stored DOOR refresh_token, mints a fresh access_token, and passes it to the SDK. No OTP required.
  4. The SDK's onTokenExpired callback — follows the same path. It calls your backend refresh endpoint, which uses the stored refresh_token to get a new JWT silently.
First Visit                        Subsequent Visits
───────────                        ─────────────────
Guest enters email                 Guest loads page (already logged in)
  ↓                                  ↓
OTP email sent                     Your backend checks session
  ↓                                  ↓
Guest enters code                  Finds stored DOOR refresh_token
  ↓                                  ↓
Backend gets JWT + refresh_token   Calls DOOR to mint fresh JWT
  ↓                                  ↓
Stores refresh_token in session    Returns JWT to browser
  ↓                                  ↓
Returns JWT to browser             SDK initialized — door codes load
  ↓
SDK initialized — door codes load

The guest only sees the OTP screen on their very first visit. Every visit after that, door codes load automatically as part of your normal page load.

API Reference

OpenDOORClient

| Option | Type | Required | Default | Description | |--------|------|----------|---------|-------------| | token | string | Yes | — | JWT access_token from the auth flow | | onTokenExpired | () => Promise<string> \| string | No | — | Called when the token expires. Return a fresh JWT. | | timeout | number | No | 15000 | Request timeout in milliseconds | | maxRetries | number | No | 2 | Max retries for 5xx, 429, and network errors | | includeAllDevices | boolean | No | false | When true, retrieves all credentials for doors the user can unlock. When false, retrieves only credentials generated from accesses granted by the Partner that minted the user's JWT. |

Methods

| Method | Description | |--------|-------------| | getLocks(): Promise<Lock[]> | Fetch all locks accessible to the authenticated user | | getLock(lockId: string): Promise<Lock> | Fetch a single lock by its device UUID | | updateToken(newToken: string): void | Manually replace the current JWT | | isAuthenticated(): boolean | Returns true if the current token has not expired | | destroy(): void | Clean up the client. All subsequent calls will throw. |

Types

interface Lock {
  id: string;              // Device UUID
  name: string;            // e.g. "Building Entrance", "Unit 4B"
  buildingId: string;      // Building UUID
  startTime: Date;         // Access window start
  endTime: Date | null;    // Access window end (null = no end date)
  doorCode: string | null; // Door code PIN, or null if not available
}

Error Types

All errors extend SDKError.

| Error | When | Properties | |-------|------|------------| | AuthError | Token expired with no onTokenExpired callback, or refresh returned an invalid token | statusCode | | APIError | DOOR API returned a non-success response (4xx, 5xx) | statusCode, responseBody | | NotFoundError | getLock(lockId) could not find a matching lock in the returned device list | — | | NetworkError | Network failure — DNS, timeout, connection refused | cause | | ConfigError | Invalid configuration (e.g., empty token) | — |

Error Handling Example

import { OpenDOORClient, AuthError, APIError, NetworkError } from '@dooraccess/opendoor-web-sdk';

try {
  const locks = await client.getLocks();
} catch (error) {
  if (error instanceof AuthError) {
    // Token expired and refresh failed — redirect to login
    redirectToLogin();
  } else if (error instanceof APIError) {
    // Server error — show message, maybe retry later
    console.error(`API error ${error.statusCode}:`, error.responseBody);
  } else if (error instanceof NetworkError) {
    // Offline or connectivity issue
    showOfflineMessage();
  }
}

Security Notes

  • Never expose client_id or client_secret in browser code. All auth calls must go through your backend.
  • Never send refresh_token to the browser. Store it server-side and expose a refresh endpoint that returns a new access_token.
  • The audience parameter is required on both the initial token exchange and refresh calls. Without it, the JWT will lack the necessary permissions.

Browser Support

The SDK works in all modern browsers:

  • Chrome 60+
  • Firefox 55+
  • Safari 11+
  • Edge 79+

Support

Contact your DOOR account representative for integration assistance.

License

MIT