# node-playready

A lightweight NodeJS library for working with **Microsoft PlayReady**

- Loading and provisioning device certificates (bgroupcert + zgpriv - PRD v2/v3)
- Generating PlayReady license acquisition challenges (LA_URL / WRMHEADER)
- Parsing and decrypting PlayReady license responses (XMR)
- Unpacking PlayReady V3 PRD device files
- Inspecting PSSHs for PlayReady WRMHeader structures

---

- [Installation](#installation)
- [Usage](#usage)
- [Usage with PRD](#usage-with-prd-files)
- [unpackV3PRD](#unpackv3prd)
- [containsWRMHeader](#containswrmheader)

## Installation

npm:

```bash
npm install node-playready
```

pnpm:

```bash
pnpm install node-playready
```

bun:

```bun
bun add node-playready
```

yarn:

```bash
yarn add node-playready
```

## Usage

```typescript
import { readFileSync } from "fs";
import Playready from "node-playready";

// Read bgroupcert file
const bgroupcert = readFileSync("./bgroupcert.dat");
// Read zgpriv file
const zgpriv = readFileSync("./zgpriv.dat");

// Initialize Playready client
const device = Playready.init(bgroupcert, zgpriv)

// Get Device info
console.log(device.info)

// PSSH found in the MPD manifest
const pssh = Buffer.from(
"AAADfHBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAA1xcAwAAAQABAFIDPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4A
cwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AM
gAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMA
AuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAA
vAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBD
AFQASQBOAEYATwA+ADwASwBJAEQAPgA0AFIAcABsAGIAKwBUAGIATgBFAFMAOAB0AEcAawBOAEYAVwBUAEUASABBAD0APQA8A
C8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AEsATABqADMAUQB6AFEAUAAvAE4AQQA9ADwALwBDAEgARQBDAEsAUwBVAE
0APgA8AEwAQQBfAFUAUgBMAD4AaAB0AHQAcABzADoALwAvAHAAcgBvAGYAZgBpAGMAaQBhAGwAcwBpAHQAZQAuAGsAZQB5AGQ
AZQBsAGkAdgBlAHIAeQAuAG0AZQBkAGkAYQBzAGUAcgB2AGkAYwBlAHMALgB3AGkAbgBkAG8AdwBzAC4AbgBlAHQALwBQAGwA
YQB5AFIAZQBhAGQAeQAvADwALwBMAEEAXwBVAFIATAA+ADwAQwBVAFMAVABPAE0AQQBUAFQAUgBJAEIAVQBUAEUAUwA+ADwAS
QBJAFMAXwBEAFIATQBfAFYARQBSAFMASQBPAE4APgA4AC4AMQAuADIAMwAwADQALgAzADEAPAAvAEkASQBTAF8ARABSAE0AXw
BWAEUAUgBTAEkATwBOAD4APAAvAEMAVQBTAFQATwBNAEEAVABUAFIASQBCAFUAVABFAFMAPgA8AC8ARABBAFQAQQA+ADwALwB
XAFIATQBIAEUAQQBEAEUAUgA+AA==",
  "base64"
);

// Generate Challenge
const challenge = device.generateChallenge(pssh);

// License Server URL
const licenseUrl = "https://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(persist:false,sl:2000)";

// License Request
const response = await fetch(licenseUrl, {
  method: "POST",
  headers: {
    'Content-Type': 'text/xml; charset=UTF-8'
  },
  body: challenge
});

// Check if request was successful
if (response.ok) {
  // Parse license
  const successful = device.parseLicense(Buffer.from(await response.text(), 'utf-8')).length > 0
  console.log(`successful? ${successful ? 'yes' : 'no'}`)
} else {
  console.error('Request failed!')
  console.log(await response.text())
}
```

## Usage with PRD files

**WARNING:** This function does not reprovision the PRD file, if you have a V3 PRD device please use the [unpackV3PRD](#unpackv3prd) function and init the client with `Playready.init()`

```typescript
import { readFileSync } from "fs";
import Playready from "node-playready";

// Read PRD file
const prd = readFileSync("./device.prd");

// Initialize Playready client
const device = Playready.initPRD(prd)

// Get Device info
console.log(device.info)

// PSSH found in the MPD manifest
const pssh = Buffer.from(
"AAADfHBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAA1xcAwAAAQABAFIDPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4A
cwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AM
gAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMA
AuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAA
vAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBD
AFQASQBOAEYATwA+ADwASwBJAEQAPgA0AFIAcABsAGIAKwBUAGIATgBFAFMAOAB0AEcAawBOAEYAVwBUAEUASABBAD0APQA8A
C8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AEsATABqADMAUQB6AFEAUAAvAE4AQQA9ADwALwBDAEgARQBDAEsAUwBVAE
0APgA8AEwAQQBfAFUAUgBMAD4AaAB0AHQAcABzADoALwAvAHAAcgBvAGYAZgBpAGMAaQBhAGwAcwBpAHQAZQAuAGsAZQB5AGQ
AZQBsAGkAdgBlAHIAeQAuAG0AZQBkAGkAYQBzAGUAcgB2AGkAYwBlAHMALgB3AGkAbgBkAG8AdwBzAC4AbgBlAHQALwBQAGwA
YQB5AFIAZQBhAGQAeQAvADwALwBMAEEAXwBVAFIATAA+ADwAQwBVAFMAVABPAE0AQQBUAFQAUgBJAEIAVQBUAEUAUwA+ADwAS
QBJAFMAXwBEAFIATQBfAFYARQBSAFMASQBPAE4APgA4AC4AMQAuADIAMwAwADQALgAzADEAPAAvAEkASQBTAF8ARABSAE0AXw
BWAEUAUgBTAEkATwBOAD4APAAvAEMAVQBTAFQATwBNAEEAVABUAFIASQBCAFUAVABFAFMAPgA8AC8ARABBAFQAQQA+ADwALwB
XAFIATQBIAEUAQQBEAEUAUgA+AA==",
  "base64"
);

// Generate Challenge
const challenge = device.generateChallenge(pssh);

// License Server URL
const licenseUrl = "https://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(persist:false,sl:2000)";

// License Request
const response = await fetch(licenseUrl, {
  method: "POST",
  headers: {
    'Content-Type': 'text/xml; charset=UTF-8'
  },
  body: challenge
});

// Check if request was successful
if (response.ok) {
  // Parse license
  const successful = device.parseLicense(Buffer.from(await response.text(), 'utf-8')).length > 0
  console.log(`successful? ${successful ? 'yes' : 'no'}`)
} else {
  console.error('Request failed!')
  console.log(await response.text())
}
```

## unpackV3PRD

Unpacks a **PlayReady V3 PRD device file**.

This function returns the group certificate and group key needed for `Playready.init()`

```typescript
import { readFileSync } from 'fs'
import Playready from 'node-playready'

const prd = readFileSync('./device.prd')

const { bgroupcert, zgpriv } = Playready.unpackV3PRD(prd)

console.log('bgroupcert length:', bgroupcert.length)
console.log('zgpriv length:', zgpriv.length)

// Initialize from PRD
const device = Playready.init(bgroupcert, zgpriv)
console.log(device.info)
```

## containsWRMHeader

Checks if a given Base64 string or raw buffer contains a **valid PlayReady WRMHeader**.

```typescript
import Playready from "node-playready";

const pssh = Buffer.from(
"AAADfHBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAA1xcAwAAAQABAFIDPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4A
cwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AM
gAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMA
AuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAA
vAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBD
AFQASQBOAEYATwA+ADwASwBJAEQAPgA0AFIAcABsAGIAKwBUAGIATgBFAFMAOAB0AEcAawBOAEYAVwBUAEUASABBAD0APQA8A
C8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AEsATABqADMAUQB6AFEAUAAvAE4AQQA9ADwALwBDAEgARQBDAEsAUwBVAE
0APgA8AEwAQQBfAFUAUgBMAD4AaAB0AHQAcABzADoALwAvAHAAcgBvAGYAZgBpAGMAaQBhAGwAcwBpAHQAZQAuAGsAZQB5AGQ
AZQBsAGkAdgBlAHIAeQAuAG0AZQBkAGkAYQBzAGUAcgB2AGkAYwBlAHMALgB3AGkAbgBkAG8AdwBzAC4AbgBlAHQALwBQAGwA
YQB5AFIAZQBhAGQAeQAvADwALwBMAEEAXwBVAFIATAA+ADwAQwBVAFMAVABPAE0AQQBUAFQAUgBJAEIAVQBUAEUAUwA+ADwAS
QBJAFMAXwBEAFIATQBfAFYARQBSAFMASQBPAE4APgA4AC4AMQAuADIAMwAwADQALgAzADEAPAAvAEkASQBTAF8ARABSAE0AXw
BWAEUAUgBTAEkATwBOAD4APAAvAEMAVQBTAFQATwBNAEEAVABUAFIASQBCAFUAVABFAFMAPgA8AC8ARABBAFQAQQA+ADwALwB
XAFIATQBIAEUAQQBEAEUAUgA+AA==",
  "base64"
);

if (Playready.containsWRMHeader(pssh)) {
  console.log("Valid PlayReady WRMHeader detected!");
} else {
  console.log("No WRMHeader found.");
}
```
