---
title: "@playstack/devices"
description: "Registered device identity, encrypted push targets, trust state, and atomic revocation."
tags: ["package","identity","devices","security","pro"]
---

{/* package-access:start */}

> **Pro.** Covered by the Playstack Pro License. Registry access is required; check preview availability before installing. See [package access](/docs/packages#access-policy).

{/* package-access:end */}

`@playstack/devices` gives authentication, MFA, and notification features one shared registered-device identity without making any of those packages own it.

## Install

```sh
npm install @playstack/devices @playstack/core @playstack/events
```

## Compose the service

```ts
import { createDevices } from '@playstack/devices'

export const devices = createDevices({
  persistence,
  cipher: applicationCrypto,
  events,
  clock,
  ids,
  lastSeenWriteIntervalMs: 60 * 60_000,
})

await devices.registerDevice({
  id: installationId,
  userId,
  appId: 'synth',
  platform: 'ios',
  appVersion: '2.4.0',
})
```

## Configuration reference

| `DevicesOptions` property | Required | Purpose |
| --- | --- | --- |
| `persistence` | Yes | Transactional devices, encrypted push records, and revocation state. |
| `cipher` | Yes | Encrypts and decrypts push bearer credentials. |
| `events` | Yes | Publishes registered, trusted, and revoked lifecycle events. |
| `clock`, `ids` | Yes | Application-owned time and identifiers. |
| `lastSeenWriteIntervalMs` | No | Non-negative write throttle; defaults to one hour. |

## Device and push properties

Device registration accepts an application installation `id`, `userId`, `appId`, platform (`android`, `ios`, `macos`, or `web`), and optional name, model, OS version, and app version. Trust always has an absolute expiry and never slides when the device is seen.

Push registration accepts a device/user pair, transport (`apns`, `expo`, `fcm`, or `webpush`), credential, and optional prior push-token ID for atomic rotation. Web Push uses `{ endpoint, keys: { auth, p256dh } }`; other transports use an opaque string.

## Auth session validation

`assertActive(userId, deviceId)` resolves only while the device exists, belongs to the user, and is not revoked, so the service satisfies the `DeviceSessionValidator` contract of [`@playstack/auth`](/docs/packages/identity/auth) without an adapter.

```ts
const sessions = databaseSessions({ persistence, crypto, events, clock, ids, deviceValidator: devices, requireDevice: true })
events.registerInternal('devices.device.revoked', sessions.deviceRevocationHandler())
```

In NestJS, provide the service under `NEST_DEVICE_SESSION_VALIDATOR` and [`@playstack/nest-auth`](/docs/packages/identity/nest-auth) injects it into the session factory.

## Bridge contracts

| Contract | Key members |
| --- | --- |
| `SecretCipher` | `encrypt(plaintext)` and `decryptText(ciphertext)`; compatible with `@playstack/crypto`. |
| `DevicesPersistence` | Transaction, device lookup/save/list, push lookup/save/list, and bulk invalidation. |

The persistence transaction handle is also used by synchronous auth and audit handlers. Revocation must invalidate push records and publish `devices.device.revoked` atomically so auth can revoke device-backed sessions in the same transaction.

```ts
import { prismaDevicesPersistence } from '@playstack/devices/prisma'

const persistence = prismaDevicesPersistence(prisma)
```

The Prisma adapter serializes registration and locks token rotation and revocation rows. The managed, non-ejectable schema remains application-migrated. Test adapters are available from `@playstack/devices/testing`.

## Boundary

The package does not fingerprint visitors, pair devices, deliver notifications, or store auth sessions. Auth and MFA depend on its explicit validator/trust methods and events; devices remains independent of both.

{/* package-reference:start */}

## API entry points and requirements

Reference snapshot: `@playstack/devices@0.1.0-beta.1`. Import only the entry point your runtime needs. Paths below are relative to the installed package; use **Go to Definition** in your editor to inspect exact parameters, return types and overloads. Do not import the declaration-file paths directly.

| Public entry point | Declaration file |
| --- | --- |
| `@playstack/devices` | `./dist/index.d.ts` |
| `@playstack/devices/errors` | `./dist/errors.d.ts` |
| `@playstack/devices/prisma` | `./dist/prisma.d.ts` |
| `@playstack/devices/testing` | `./dist/testing.d.ts` |
| `@playstack/devices/playstack.artifacts.json` | No TypeScript declaration (asset or metadata export). |
| `@playstack/devices/package.json` | No TypeScript declaration (asset or metadata export). |

Node.js engine requirement: `>=20`. This is not a claim that every entry point works in browsers or Workers.

### Peer dependencies

This package declares no peer dependencies. Its ordinary dependencies are resolved by the package manager.

For a complete first program, start with [Getting started](/docs/getting-started). For API lookup and partial-example conventions, see [Reading the reference](/docs/packages#reading-the-reference). Provider failures, lifecycle requirements and application responsibilities remain described in the guide above; types alone do not establish production safety.

{/* package-reference:end */}
