Skip to content

Commit 87045d4

Browse files
committed
feat: add Access Manager (PAM) support, light theme demo, bump to v1.1.2
- Implement PubNub Access Manager v3 integration in SyncManager - Add grantToken(), setAuthToken(), parseToken() methods - Add automatic token refresh via onTokenExpired callback - Add accessdenied event for 403 Forbidden handling - Include -pnpres presence channel in default token grants - Add PAM Admin/Follower mode UI to demo - Switch demo to light theme with improved readability - Add architecture diagram to README - Use absolute GitHub raw URLs for images (NPM compatibility) - Add 21 new unit tests for Access Manager features - Bump version to 1.1.2
1 parent 4d38df7 commit 87045d4

12 files changed

Lines changed: 1446 additions & 45 deletions

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "1.1.1"
2+
".": "1.1.2"
33
}

CHANGELOG.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
---
99

10+
## [Unreleased]
11+
12+
### Added - Access Manager (PAM v3) Support
13+
14+
#### New API Methods
15+
- `setAuthToken(token)` — Set or refresh the Access Manager auth token at runtime
16+
- `grantToken(options)` — Grant an Access Manager token (requires `secretKey`)
17+
- `parseToken(token)` — Decode a token to inspect permissions and TTL
18+
- `SyncManager.parseToken(token, PubNub)` — Static token parser (no connection needed)
19+
20+
#### New Configuration Options
21+
- `authToken` — Pre-set an Access Manager v3 token
22+
- `secretKey` — PubNub Secret Key for server-side/demo token grants
23+
- `onTokenExpired` — Async callback for automatic token refresh on 403
24+
25+
#### New Events
26+
- `accessdenied` — Emitted when a 403 Forbidden response is received from PubNub
27+
28+
#### Improvements
29+
- `connect()` now conditionally includes `secretKey` and applies `authToken` via `setToken()`
30+
- `broadcastCommand()` and `broadcastMasterClaim()` detect 403 errors and auto-retry after token refresh
31+
- `onPubNubStatus()` detects `PNAccessDeniedCategory` and triggers token refresh
32+
- Demo updated with full Access Manager UI (Admin/Follower modes, token granting, copy/paste workflow)
33+
- PubNub SDK updated to v10.2.7 for Access Manager v3 `grantToken`/`setToken` support
34+
35+
---
36+
1037
## [1.1.1](https://github.com/PubNubDevelopers/Shaka-Player-Sync/compare/v1.1.0...v1.1.1) (2026-02-02)
1138

1239

README.md

Lines changed: 171 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Looking for the original? See [shaka-player on npm](https://www.npmjs.com/packag
2929
| **Master/Follower Control** | - | **Y** |
3030
| **Automatic Drift Correction** | - | **Y** |
3131
| **Presence Events** | - | **Y** |
32+
| **Access Manager (Token Security)** | - | **Y** |
3233

3334
---
3435

@@ -89,6 +90,10 @@ syncManager.becomeMaster();
8990
| `isConnected()` | Returns connection status |
9091
| `getRoomId()` | Returns current room ID |
9192
| `getUserId()` | Returns this client's user ID |
93+
| `setAuthToken(token)` | Set or refresh the Access Manager auth token at runtime |
94+
| `grantToken(options)` | Grant an Access Manager token (requires `secretKey`) |
95+
| `parseToken(token)` | Decode a token to inspect permissions and TTL |
96+
| `SyncManager.parseToken(token, PubNub)` | Static token parser (no connection needed) |
9297
| `destroy()` | Clean up resources |
9398

9499
### Events
@@ -97,13 +102,177 @@ syncManager.becomeMaster();
97102
syncManager.addEventListener('masterchanged', (event) => {
98103
console.log('New master:', event.newMasterId);
99104
});
105+
106+
syncManager.addEventListener('accessdenied', (event) => {
107+
console.error('Access denied:', event.reason);
108+
});
109+
```
110+
111+
| Event | Data | Description |
112+
|-------|------|-------------|
113+
| `masterchanged` | `{ newMasterId, previousRole }` | Another user claimed master |
114+
| `userjoined` | `{ userId, occupancy }` | A user joined the room |
115+
| `userleft` | `{ userId, occupancy }` | A user left the room |
116+
| `connected` | `{ roomId }` | Successfully connected to a room |
117+
| `disconnected` | `{ roomId }` | Disconnected from a room |
118+
| `accessdenied` | `{ reason }` | Access Manager denied a request (403) |
119+
120+
---
121+
122+
## Access Manager (Optional Security)
123+
124+
PubNub Access Manager lets you secure Watch Party rooms with time-limited, per-user tokens. Access Manager is **completely optional** — if you don't need it, the library works without any token configuration.
125+
126+
### Why Use Access Manager?
127+
128+
- Prevent unauthorized users from joining or controlling Watch Party rooms
129+
- Restrict **Master** (publish) access to specific users
130+
- Issue time-limited tokens that automatically expire
131+
- Revoke access at any time
132+
133+
### Server-Side Token Granting (Recommended)
134+
135+
In production, your server authenticates users and grants scoped tokens. The client never sees the Secret Key.
136+
137+
```
138+
Client → Your Server → PubNub grantToken() → token → Client → SyncManager
139+
```
140+
141+
**Server-side (Node.js):**
142+
143+
```javascript
144+
import PubNub from 'pubnub';
145+
146+
const pubnub = new PubNub({
147+
publishKey: 'pub-c-xxx',
148+
subscribeKey: 'sub-c-xxx',
149+
secretKey: 'sec-c-xxx', // Never expose this to clients
150+
userId: 'server',
151+
});
152+
153+
// Grant a token for a specific user and room
154+
const token = await pubnub.grantToken({
155+
ttl: 60, // 60 minutes
156+
authorized_uuid: 'user-123',
157+
resources: {
158+
channels: {
159+
'shaka-sync-friday-movie': { read: true, write: true },
160+
'shaka-sync-friday-movie-pnpres': { read: true }, // Required for presence events
161+
},
162+
},
163+
});
164+
165+
// Send `token` to the authenticated client
166+
```
167+
168+
**Client-side:**
169+
170+
```javascript
171+
const syncManager = new SyncManager(player, {
172+
publishKey: 'pub-c-xxx',
173+
subscribeKey: 'sub-c-xxx',
174+
PubNub: PubNub,
175+
authToken: token, // Token from your server
176+
});
177+
178+
syncManager.connect('friday-movie');
100179
```
101180

181+
### Automatic Token Refresh
182+
183+
Tokens expire. Provide an `onTokenExpired` callback to seamlessly refresh tokens without interrupting the session:
184+
185+
```javascript
186+
const syncManager = new SyncManager(player, {
187+
publishKey: 'pub-c-xxx',
188+
subscribeKey: 'sub-c-xxx',
189+
PubNub: PubNub,
190+
authToken: initialToken,
191+
onTokenExpired: async () => {
192+
const res = await fetch('/api/pubnub/token');
193+
const { token } = await res.json();
194+
return token;
195+
},
196+
});
197+
```
198+
199+
When a `403 Forbidden` response is detected, the library automatically calls `onTokenExpired`, applies the new token, and retries the failed operation.
200+
201+
### Runtime Token Updates
202+
203+
Update the token at any time without reconnecting:
204+
205+
```javascript
206+
syncManager.setAuthToken(newToken);
207+
```
208+
209+
### Role-Based Tokens
210+
211+
Issue different tokens for different roles:
212+
213+
| Role | Channel Permissions | Presence Channel (`-pnpres`) | Use Case |
214+
|------|------------|----------|----------|
215+
| **Master** | `{ read: true, write: true }` | `{ read: true }` | Can publish sync commands |
216+
| **Follower** | `{ read: true }` | `{ read: true }` | Can only receive sync commands |
217+
218+
> **Note:** Both roles need `{ read: true }` on the `-pnpres` suffixed channel for presence events (join/leave) to work.
219+
220+
### Token Debugging
221+
222+
Inspect a token's permissions and TTL:
223+
224+
```javascript
225+
// Instance method (requires connection)
226+
const info = syncManager.parseToken(token);
227+
console.log(info.ttl, info.authorized_uuid, info.resources);
228+
229+
// Static method (no connection needed)
230+
const info = SyncManager.parseToken(token, PubNub);
231+
```
232+
233+
### Access Manager Events
234+
235+
Listen for access denial events:
236+
237+
```javascript
238+
syncManager.addEventListener('accessdenied', (event) => {
239+
console.error('Access denied:', event.reason);
240+
// Redirect to login, show error UI, etc.
241+
});
242+
```
243+
244+
### Demo / Testing Mode
245+
246+
For quick testing, you can pass the Secret Key directly (browser demo only — **never do this in production**):
247+
248+
```javascript
249+
const syncManager = new SyncManager(player, {
250+
publishKey: 'pub-c-xxx',
251+
subscribeKey: 'sub-c-xxx',
252+
secretKey: 'sec-c-xxx', // Demo only!
253+
PubNub: PubNub,
254+
});
255+
256+
syncManager.connect('test-room');
257+
258+
// Grant a token from the client (demo only)
259+
const token = await syncManager.grantToken({ ttl: 30 });
260+
syncManager.setAuthToken(token);
261+
```
262+
263+
### Setup Checklist
264+
265+
1. Enable **Access Manager** on your keyset in the [PubNub Admin Portal](https://admin.pubnub.com)
266+
2. Store your **Secret Key** securely on your server
267+
3. Implement a server endpoint that authenticates users and calls `grantToken()`
268+
4. Pass the token to the client via `authToken` in the SyncManager config
269+
5. Optionally add an `onTokenExpired` callback for automatic refresh
270+
102271
---
103272

104273
## How Sync Works
105274

106-
<img src="assets/shaka-player-diagram.png" alt="PubNub Shaka Player Sync Architecture" width="100%">
275+
<img src="https://raw.githubusercontent.com/PubNubDevelopers/Shaka-Player-Sync/main/assets/shaka-player-diagram.png" alt="PubNub Shaka Player Sync Architecture" width="100%">
107276

108277
1. **Master** controls playback (play, pause, seek)
109278
2. Commands are sent instantly via PubNub to all connected clients
@@ -233,7 +402,7 @@ Apache 2.0 - See [LICENSE](LICENSE)
233402
**Powered by [PubNub](https://www.pubnub.com)**
234403

235404
<a href="https://www.pubnub.com">
236-
<img src="assets/pn-logo.png" alt="PubNub" height="50">
405+
<img src="https://raw.githubusercontent.com/PubNubDevelopers/Shaka-Player-Sync/main/assets/pn-logo.png" alt="PubNub" height="50">
237406
</a>
238407

239408
<br><br>

assets/pn-logo.png

24.1 KB
Loading

assets/shaka-player-diagram.png

31.9 KB
Loading

0 commit comments

Comments
 (0)