@xiboplayer/stats 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -354
- package/package.json +2 -2
- package/src/index.js +2 -0
package/README.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# @xiboplayer/stats
|
|
2
2
|
|
|
3
|
-
Proof of play tracking and CMS logging
|
|
3
|
+
**Proof of play tracking, stats reporting, and CMS logging.**
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Overview
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
7
|
+
Collects and reports display analytics to the Xibo CMS:
|
|
8
|
+
|
|
9
|
+
- **Proof of play** — per-layout and per-widget duration tracking
|
|
10
|
+
- **Aggregation modes** — individual or aggregated stat submission (configurable from CMS)
|
|
11
|
+
- **Log reporting** — display logs batched and submitted to CMS
|
|
12
|
+
- **Fault alerts** — error deduplication and fault reporting
|
|
13
13
|
|
|
14
14
|
## Installation
|
|
15
15
|
|
|
@@ -19,357 +19,20 @@ npm install @xiboplayer/stats
|
|
|
19
19
|
|
|
20
20
|
## Usage
|
|
21
21
|
|
|
22
|
-
### StatsCollector - Proof of Play Tracking
|
|
23
|
-
|
|
24
|
-
The StatsCollector tracks when layouts and widgets (media) are played for reporting to the CMS.
|
|
25
|
-
|
|
26
|
-
```javascript
|
|
27
|
-
import { StatsCollector, formatStats } from '@xiboplayer/stats';
|
|
28
|
-
|
|
29
|
-
// Initialize collector
|
|
30
|
-
const collector = new StatsCollector();
|
|
31
|
-
await collector.init();
|
|
32
|
-
|
|
33
|
-
// Track layout playback
|
|
34
|
-
await collector.startLayout(layoutId, scheduleId);
|
|
35
|
-
// ... layout plays for 5 minutes ...
|
|
36
|
-
await collector.endLayout(layoutId, scheduleId);
|
|
37
|
-
|
|
38
|
-
// Track widget playback
|
|
39
|
-
await collector.startWidget(mediaId, layoutId, scheduleId);
|
|
40
|
-
// ... widget plays for 30 seconds ...
|
|
41
|
-
await collector.endWidget(mediaId, layoutId, scheduleId);
|
|
42
|
-
|
|
43
|
-
// Submit stats to CMS
|
|
44
|
-
const stats = await collector.getStatsForSubmission(50); // Get up to 50 stats
|
|
45
|
-
const xml = formatStats(stats);
|
|
46
|
-
const success = await xmds.submitStats(xml);
|
|
47
|
-
|
|
48
|
-
if (success) {
|
|
49
|
-
await collector.clearSubmittedStats(stats);
|
|
50
|
-
}
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
#### API Reference
|
|
54
|
-
|
|
55
|
-
**`new StatsCollector()`**
|
|
56
|
-
|
|
57
|
-
Create a new stats collector instance.
|
|
58
|
-
|
|
59
|
-
**`async init()`**
|
|
60
|
-
|
|
61
|
-
Initialize IndexedDB storage. Must be called before using other methods.
|
|
62
|
-
|
|
63
|
-
**`async startLayout(layoutId, scheduleId)`**
|
|
64
|
-
|
|
65
|
-
Start tracking a layout. Creates a new stat entry and marks it as in-progress.
|
|
66
|
-
|
|
67
|
-
- `layoutId` (number): Layout ID from CMS
|
|
68
|
-
- `scheduleId` (number): Schedule ID that triggered this layout
|
|
69
|
-
|
|
70
|
-
**`async endLayout(layoutId, scheduleId)`**
|
|
71
|
-
|
|
72
|
-
End tracking a layout. Calculates duration and saves to database.
|
|
73
|
-
|
|
74
|
-
- `layoutId` (number): Layout ID from CMS
|
|
75
|
-
- `scheduleId` (number): Schedule ID
|
|
76
|
-
|
|
77
|
-
**`async startWidget(mediaId, layoutId, scheduleId)`**
|
|
78
|
-
|
|
79
|
-
Start tracking a widget/media item.
|
|
80
|
-
|
|
81
|
-
- `mediaId` (number): Media ID from CMS
|
|
82
|
-
- `layoutId` (number): Parent layout ID
|
|
83
|
-
- `scheduleId` (number): Schedule ID
|
|
84
|
-
|
|
85
|
-
**`async endWidget(mediaId, layoutId, scheduleId)`**
|
|
86
|
-
|
|
87
|
-
End tracking a widget. Calculates duration and saves to database.
|
|
88
|
-
|
|
89
|
-
- `mediaId` (number): Media ID from CMS
|
|
90
|
-
- `layoutId` (number): Parent layout ID
|
|
91
|
-
- `scheduleId` (number): Schedule ID
|
|
92
|
-
|
|
93
|
-
**`async getStatsForSubmission(limit = 50)`**
|
|
94
|
-
|
|
95
|
-
Get unsubmitted stats ready for submission to CMS.
|
|
96
|
-
|
|
97
|
-
- `limit` (number): Maximum number of stats to return (default: 50)
|
|
98
|
-
- Returns: Array of stat objects
|
|
99
|
-
|
|
100
|
-
**`async clearSubmittedStats(stats)`**
|
|
101
|
-
|
|
102
|
-
Delete stats that were successfully submitted to CMS.
|
|
103
|
-
|
|
104
|
-
- `stats` (Array): Array of stat objects to delete
|
|
105
|
-
|
|
106
|
-
**`async getAllStats()`**
|
|
107
|
-
|
|
108
|
-
Get all stats from database (for debugging).
|
|
109
|
-
|
|
110
|
-
- Returns: Array of all stat objects
|
|
111
|
-
|
|
112
|
-
**`async clearAllStats()`**
|
|
113
|
-
|
|
114
|
-
Clear all stats from database (for testing).
|
|
115
|
-
|
|
116
|
-
#### Stat Object Structure
|
|
117
|
-
|
|
118
|
-
```javascript
|
|
119
|
-
{
|
|
120
|
-
id: 123, // Auto-generated ID
|
|
121
|
-
type: 'layout', // 'layout' or 'media'
|
|
122
|
-
layoutId: 456, // Layout ID from CMS
|
|
123
|
-
scheduleId: 789, // Schedule ID
|
|
124
|
-
mediaId: 111, // Only for type='media'
|
|
125
|
-
start: Date, // Start time
|
|
126
|
-
end: Date, // End time
|
|
127
|
-
duration: 300, // Duration in seconds
|
|
128
|
-
count: 1, // Number of plays (always 1)
|
|
129
|
-
submitted: 0 // 0 = not submitted, 1 = submitted
|
|
130
|
-
}
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
### formatStats - XML Formatting
|
|
134
|
-
|
|
135
|
-
Format stats array as XML for XMDS SubmitStats API.
|
|
136
|
-
|
|
137
|
-
```javascript
|
|
138
|
-
import { formatStats } from '@xiboplayer/stats';
|
|
139
|
-
|
|
140
|
-
const stats = await collector.getStatsForSubmission(50);
|
|
141
|
-
const xml = formatStats(stats);
|
|
142
|
-
|
|
143
|
-
console.log(xml);
|
|
144
|
-
// <stats>
|
|
145
|
-
// <stat type="layout" fromdt="2026-02-10 12:00:00" todt="2026-02-10 12:05:00"
|
|
146
|
-
// scheduleid="123" layoutid="456" count="1" duration="300" />
|
|
147
|
-
// <stat type="media" fromdt="2026-02-10 12:00:00" todt="2026-02-10 12:01:00"
|
|
148
|
-
// scheduleid="123" layoutid="456" mediaid="789" count="1" duration="60" />
|
|
149
|
-
// </stats>
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
### LogReporter - CMS Logging
|
|
153
|
-
|
|
154
|
-
The LogReporter collects application logs and submits them to the CMS via XMDS.
|
|
155
|
-
|
|
156
|
-
```javascript
|
|
157
|
-
import { LogReporter, formatLogs } from '@xiboplayer/stats';
|
|
158
|
-
|
|
159
|
-
// Initialize reporter
|
|
160
|
-
const reporter = new LogReporter();
|
|
161
|
-
await reporter.init();
|
|
162
|
-
|
|
163
|
-
// Log messages
|
|
164
|
-
await reporter.error('Failed to load layout 123', 'PLAYER');
|
|
165
|
-
await reporter.audit('User logged in', 'AUTH');
|
|
166
|
-
await reporter.info('Layout loaded successfully', 'RENDERER');
|
|
167
|
-
await reporter.debug('Cache hit for file ABC', 'CACHE');
|
|
168
|
-
|
|
169
|
-
// Submit logs to CMS
|
|
170
|
-
const logs = await reporter.getLogsForSubmission(100); // Get up to 100 logs
|
|
171
|
-
const xml = formatLogs(logs);
|
|
172
|
-
const success = await xmds.submitLog(xml);
|
|
173
|
-
|
|
174
|
-
if (success) {
|
|
175
|
-
await reporter.clearSubmittedLogs(logs);
|
|
176
|
-
}
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
#### API Reference
|
|
180
|
-
|
|
181
|
-
**`new LogReporter()`**
|
|
182
|
-
|
|
183
|
-
Create a new log reporter instance.
|
|
184
|
-
|
|
185
|
-
**`async init()`**
|
|
186
|
-
|
|
187
|
-
Initialize IndexedDB storage. Must be called before using other methods.
|
|
188
|
-
|
|
189
|
-
**`async log(level, message, category = 'PLAYER')`**
|
|
190
|
-
|
|
191
|
-
Log a message with specified level.
|
|
192
|
-
|
|
193
|
-
- `level` (string): Log level - 'error', 'audit', 'info', or 'debug'
|
|
194
|
-
- `message` (string): Log message
|
|
195
|
-
- `category` (string): Log category (default: 'PLAYER')
|
|
196
|
-
|
|
197
|
-
**`async error(message, category = 'PLAYER')`**
|
|
198
|
-
|
|
199
|
-
Shorthand for logging an error message.
|
|
200
|
-
|
|
201
|
-
**`async audit(message, category = 'PLAYER')`**
|
|
202
|
-
|
|
203
|
-
Shorthand for logging an audit message.
|
|
204
|
-
|
|
205
|
-
**`async info(message, category = 'PLAYER')`**
|
|
206
|
-
|
|
207
|
-
Shorthand for logging an info message.
|
|
208
|
-
|
|
209
|
-
**`async debug(message, category = 'PLAYER')`**
|
|
210
|
-
|
|
211
|
-
Shorthand for logging a debug message.
|
|
212
|
-
|
|
213
|
-
**`async getLogsForSubmission(limit = 100)`**
|
|
214
|
-
|
|
215
|
-
Get unsubmitted logs ready for submission to CMS.
|
|
216
|
-
|
|
217
|
-
- `limit` (number): Maximum number of logs to return (default: 100)
|
|
218
|
-
- Returns: Array of log objects
|
|
219
|
-
|
|
220
|
-
**`async clearSubmittedLogs(logs)`**
|
|
221
|
-
|
|
222
|
-
Delete logs that were successfully submitted to CMS.
|
|
223
|
-
|
|
224
|
-
- `logs` (Array): Array of log objects to delete
|
|
225
|
-
|
|
226
|
-
**`async getAllLogs()`**
|
|
227
|
-
|
|
228
|
-
Get all logs from database (for debugging).
|
|
229
|
-
|
|
230
|
-
- Returns: Array of all log objects
|
|
231
|
-
|
|
232
|
-
**`async clearAllLogs()`**
|
|
233
|
-
|
|
234
|
-
Clear all logs from database (for testing).
|
|
235
|
-
|
|
236
|
-
#### Log Object Structure
|
|
237
|
-
|
|
238
22
|
```javascript
|
|
239
|
-
{
|
|
240
|
-
id: 123, // Auto-generated ID
|
|
241
|
-
level: 'error', // 'error', 'audit', 'info', or 'debug'
|
|
242
|
-
message: 'Error text', // Log message
|
|
243
|
-
category: 'PLAYER', // Log category
|
|
244
|
-
timestamp: Date, // When log was created
|
|
245
|
-
submitted: 0 // 0 = not submitted, 1 = submitted
|
|
246
|
-
}
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
#### Common Categories
|
|
23
|
+
import { StatsCollector } from '@xiboplayer/stats';
|
|
250
24
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
- `CACHE` - File caching and downloads
|
|
254
|
-
- `XMDS` - CMS communication
|
|
255
|
-
- `AUTH` - Authentication and registration
|
|
256
|
-
- `SCHEDULE` - Schedule management
|
|
25
|
+
const stats = new StatsCollector({ transport });
|
|
26
|
+
stats.init();
|
|
257
27
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
Format logs array as XML for XMDS SubmitLog API.
|
|
261
|
-
|
|
262
|
-
```javascript
|
|
263
|
-
import { formatLogs } from '@xiboplayer/stats';
|
|
264
|
-
|
|
265
|
-
const logs = await reporter.getLogsForSubmission(100);
|
|
266
|
-
const xml = formatLogs(logs);
|
|
267
|
-
|
|
268
|
-
console.log(xml);
|
|
269
|
-
// <logs>
|
|
270
|
-
// <log date="2026-02-10 12:00:00" category="PLAYER" type="error"
|
|
271
|
-
// message="Failed to load layout 123" />
|
|
272
|
-
// <log date="2026-02-10 12:01:00" category="AUTH" type="audit"
|
|
273
|
-
// message="User logged in" />
|
|
274
|
-
// </logs>
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
## Storage
|
|
278
|
-
|
|
279
|
-
Both StatsCollector and LogReporter use IndexedDB for persistent storage:
|
|
280
|
-
|
|
281
|
-
- **Database Names**: `xibo-player-stats` and `xibo-player-logs`
|
|
282
|
-
- **Indexes**: Both use an index on the `submitted` field for fast queries
|
|
283
|
-
- **Quota Management**: Automatically cleans old submitted entries when quota is exceeded
|
|
284
|
-
- **Offline Support**: Data persists across browser sessions and restarts
|
|
285
|
-
|
|
286
|
-
### Storage Limits
|
|
287
|
-
|
|
288
|
-
- **Chrome**: ~60% of available disk space
|
|
289
|
-
- **Firefox**: ~10% of available disk space
|
|
290
|
-
- **Safari**: ~1GB
|
|
291
|
-
|
|
292
|
-
When storage quota is exceeded, the oldest 100 submitted entries are automatically deleted.
|
|
293
|
-
|
|
294
|
-
## Integration with PWA Platform
|
|
295
|
-
|
|
296
|
-
The stats package is integrated into the PWA player (`xiboplayer-pwa/src/main.ts`):
|
|
297
|
-
|
|
298
|
-
```typescript
|
|
299
|
-
// Initialize stats collector
|
|
300
|
-
this.statsCollector = new StatsCollector();
|
|
301
|
-
await this.statsCollector.init();
|
|
302
|
-
|
|
303
|
-
// Track layout events
|
|
304
|
-
this.renderer.on('layoutStart', (layoutId) => {
|
|
305
|
-
this.statsCollector.startLayout(layoutId, this.currentScheduleId);
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
this.renderer.on('layoutEnd', (layoutId) => {
|
|
309
|
-
this.statsCollector.endLayout(layoutId, this.currentScheduleId);
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
// Track widget events
|
|
313
|
-
this.renderer.on('widgetStart', ({ widgetId, layoutId, mediaId }) => {
|
|
314
|
-
if (mediaId) {
|
|
315
|
-
this.statsCollector.startWidget(mediaId, layoutId, this.currentScheduleId);
|
|
316
|
-
}
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
this.renderer.on('widgetEnd', ({ widgetId, layoutId, mediaId }) => {
|
|
320
|
-
if (mediaId) {
|
|
321
|
-
this.statsCollector.endWidget(mediaId, layoutId, this.currentScheduleId);
|
|
322
|
-
}
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
// Submit stats periodically (every 10 minutes)
|
|
326
|
-
setInterval(async () => {
|
|
327
|
-
const stats = await this.statsCollector.getStatsForSubmission(50);
|
|
328
|
-
if (stats.length > 0) {
|
|
329
|
-
const xml = formatStats(stats);
|
|
330
|
-
const success = await this.xmds.submitStats(xml);
|
|
331
|
-
if (success) {
|
|
332
|
-
await this.statsCollector.clearSubmittedStats(stats);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}, 600000);
|
|
28
|
+
// Stats are collected automatically from player events
|
|
29
|
+
// and submitted during each collection cycle
|
|
336
30
|
```
|
|
337
31
|
|
|
338
|
-
##
|
|
339
|
-
|
|
340
|
-
The package includes comprehensive tests using Vitest and fake-indexeddb:
|
|
341
|
-
|
|
342
|
-
```bash
|
|
343
|
-
# Run tests
|
|
344
|
-
npm test
|
|
345
|
-
|
|
346
|
-
# Run tests in watch mode
|
|
347
|
-
npm run test:watch
|
|
348
|
-
|
|
349
|
-
# Run tests with coverage
|
|
350
|
-
npm run test:coverage
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
### Test Coverage
|
|
354
|
-
|
|
355
|
-
- **StatsCollector**: 32 tests covering initialization, layout tracking, widget tracking, submission flow, edge cases, and database operations
|
|
356
|
-
- **LogReporter**: 35 tests covering initialization, log creation, submission flow, edge cases, and database operations
|
|
357
|
-
- **Total**: 67 tests with 100% pass rate
|
|
358
|
-
|
|
359
|
-
## Compatibility
|
|
360
|
-
|
|
361
|
-
- **Node.js**: 18+ (for ESM support)
|
|
362
|
-
- **Browsers**: Chrome 58+, Firefox 52+, Safari 12+, Edge 79+
|
|
363
|
-
- **IndexedDB**: Required for persistent storage
|
|
364
|
-
|
|
365
|
-
## Author
|
|
366
|
-
|
|
367
|
-
Pau Aliagas <linuxnow@gmail.com>
|
|
368
|
-
|
|
369
|
-
## Repository
|
|
32
|
+
## Dependencies
|
|
370
33
|
|
|
371
|
-
|
|
34
|
+
- `@xiboplayer/utils` — logger, events
|
|
372
35
|
|
|
373
|
-
|
|
36
|
+
---
|
|
374
37
|
|
|
375
|
-
|
|
38
|
+
**Part of the [XiboPlayer SDK](https://github.com/xibo-players/xiboplayer)** | [MCP Server](https://github.com/xibo-players/xiboplayer/tree/main/mcp-server) for AI-assisted development
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xiboplayer/stats",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Proof of play tracking, stats reporting, and CMS logging",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"./collector": "./src/stats-collector.js"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@xiboplayer/utils": "0.
|
|
12
|
+
"@xiboplayer/utils": "0.3.1"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
15
|
"vitest": "^2.0.0",
|