@playcademy/sdk 0.6.1-beta.1 → 0.6.1-beta.3
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 +22 -1
- package/dist/index.d.ts +24 -4
- package/dist/index.js +177 -33
- package/dist/internal.js +177 -33
- package/dist/types.d.ts +24 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -240,6 +240,23 @@ client.timeback.startActivity({
|
|
|
240
240
|
})
|
|
241
241
|
// Auto-derived: activityName "Math Quiz Level 1"
|
|
242
242
|
// Auto-filled by backend: appName, subject, sensorUrl
|
|
243
|
+
// Automatically pauses active time when the tab is hidden, the shell pauses,
|
|
244
|
+
// or the player has no visible keyboard/mouse activity for 10 minutes
|
|
245
|
+
|
|
246
|
+
// Customize inactivity handling
|
|
247
|
+
client.timeback.startActivity(
|
|
248
|
+
{ activityId: 'math-quiz-level-1' },
|
|
249
|
+
{
|
|
250
|
+
inactivityTimeoutMs: 5 * 60 * 1000, // 5 minutes
|
|
251
|
+
heartbeatIntervalMs: 15_000,
|
|
252
|
+
},
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
// Disable keyboard/mouse inactivity tracking
|
|
256
|
+
client.timeback.startActivity(
|
|
257
|
+
{ activityId: 'math-quiz-level-1' },
|
|
258
|
+
{ inactivityTimeoutMs: Infinity },
|
|
259
|
+
)
|
|
243
260
|
|
|
244
261
|
// ... player completes activity ...
|
|
245
262
|
|
|
@@ -323,12 +340,16 @@ const { token } = await client.realtime.token.get()
|
|
|
323
340
|
|
|
324
341
|
- `role`: The user's TimeBack role (`'student'`, `'parent'`, `'teacher'`, or `'administrator'`)
|
|
325
342
|
- `enrollments`: Array of course enrollments with `subject`, `grade`, and `courseId`
|
|
326
|
-
- `startActivity(metadata)`: Start tracking an activity (stores start time and metadata)
|
|
343
|
+
- `startActivity(metadata, options?)`: Start tracking an activity (stores start time and metadata)
|
|
327
344
|
- `metadata.activityId`: Unique activity identifier (required)
|
|
328
345
|
- `metadata.activityName`: Human-readable activity name
|
|
329
346
|
- `metadata.subject`: Subject area (Math, Reading, Science, etc.)
|
|
330
347
|
- `metadata.appName`: Application name
|
|
331
348
|
- `metadata.sensorUrl`: Sensor URL for tracking
|
|
349
|
+
- Automatically pauses active time when the tab is hidden, the shell pauses the game, or the player is idle while visible
|
|
350
|
+
- `options.pausedHeartbeatTimeoutMs`: Stop periodic heartbeats after a long automatic pause (`hidden` or `inactivity`); `Infinity` keeps them running
|
|
351
|
+
- `options.heartbeatIntervalMs`: Flush incremental heartbeats (`Infinity` disables periodic heartbeats)
|
|
352
|
+
- `options.inactivityTimeoutMs`: Keyboard/mouse idle timeout while visible (defaults to 10 minutes; `Infinity` disables)
|
|
332
353
|
- `endActivity(scoreData)`: End activity and submit results
|
|
333
354
|
- `scoreData.correctQuestions`: Number of correct answers
|
|
334
355
|
- `scoreData.totalQuestions`: Total number of questions
|
package/dist/index.d.ts
CHANGED
|
@@ -1681,16 +1681,34 @@ declare abstract class PlaycademyBaseClient {
|
|
|
1681
1681
|
*/
|
|
1682
1682
|
interface StartActivityOptions {
|
|
1683
1683
|
/**
|
|
1684
|
-
* How long
|
|
1685
|
-
*
|
|
1684
|
+
* How long heartbeats continue after the activity is automatically paused
|
|
1685
|
+
* because the tab is hidden or the player is inactive while visible.
|
|
1686
|
+
* Defaults to 10 minutes. Set to `Infinity` to keep heartbeats running
|
|
1687
|
+
* indefinitely during automatic pauses. Invalid values fall back to the
|
|
1688
|
+
* 10-minute default.
|
|
1689
|
+
*/
|
|
1690
|
+
pausedHeartbeatTimeoutMs?: number;
|
|
1691
|
+
/**
|
|
1692
|
+
* @deprecated Use `pausedHeartbeatTimeoutMs` instead.
|
|
1693
|
+
*
|
|
1694
|
+
* Backward-compatible alias for callers that still use the old option
|
|
1695
|
+
* name from earlier SDK releases.
|
|
1686
1696
|
*/
|
|
1687
1697
|
hiddenTimeoutMs?: number;
|
|
1688
1698
|
/**
|
|
1689
1699
|
* How often to flush periodic heartbeats with accumulated time data.
|
|
1690
1700
|
* Defaults to 15 seconds. Set to `Infinity` to disable the interval;
|
|
1691
|
-
* final unload/endActivity flushes still run.
|
|
1701
|
+
* final unload/endActivity flushes still run. Values must be greater than
|
|
1702
|
+
* 0 or `Infinity`; invalid values fall back to the 15-second default.
|
|
1692
1703
|
*/
|
|
1693
1704
|
heartbeatIntervalMs?: number;
|
|
1705
|
+
/**
|
|
1706
|
+
* How long the tab can remain visible without keyboard or mouse activity
|
|
1707
|
+
* before the activity is marked inactive. Defaults to 10 minutes. Set to
|
|
1708
|
+
* `Infinity` to disable keyboard/mouse inactivity tracking. Invalid values
|
|
1709
|
+
* fall back to the 10-minute default.
|
|
1710
|
+
*/
|
|
1711
|
+
inactivityTimeoutMs?: number;
|
|
1694
1712
|
/**
|
|
1695
1713
|
* Stable identifier for this activity run. When provided, it is used on
|
|
1696
1714
|
* every heartbeat and on endActivity instead of a freshly-generated UUID.
|
|
@@ -1772,7 +1790,9 @@ declare class PlaycademyClient extends PlaycademyBaseClient {
|
|
|
1772
1790
|
* - `user.fetch()` - Refresh user context from server
|
|
1773
1791
|
*
|
|
1774
1792
|
* Activity tracking:
|
|
1775
|
-
* - `startActivity(metadata)` - Begin tracking an activity
|
|
1793
|
+
* - `startActivity(metadata)` - Begin tracking an activity with automatic
|
|
1794
|
+
* hidden-tab and visible-tab inactivity handling, plus configurable
|
|
1795
|
+
* paused-heartbeat timeout behavior
|
|
1776
1796
|
* - `pauseActivity()` / `resumeActivity()` - Pause/resume timer
|
|
1777
1797
|
* - `endActivity(scoreData)` - Submit activity results to TimeBack
|
|
1778
1798
|
*/
|
package/dist/index.js
CHANGED
|
@@ -1451,9 +1451,21 @@ function isValidUUID(value) {
|
|
|
1451
1451
|
}
|
|
1452
1452
|
|
|
1453
1453
|
// src/core/activity-tracker.ts
|
|
1454
|
-
var
|
|
1454
|
+
var DEFAULT_PAUSED_HEARTBEAT_TIMEOUT_MS = 10 * 60 * 1000;
|
|
1455
1455
|
var DEFAULT_HEARTBEAT_INTERVAL_MS = 15000;
|
|
1456
|
+
var DEFAULT_INACTIVITY_TIMEOUT_MS = 10 * 60 * 1000;
|
|
1456
1457
|
var HEARTBEAT_RETRY_POLICY = { retryableMethods: ["POST"] };
|
|
1458
|
+
var USER_ACTIVITY_EVENTS = ["keydown", "pointerdown", "pointermove", "wheel"];
|
|
1459
|
+
var USER_ACTIVITY_LISTENER_OPTIONS = { capture: true };
|
|
1460
|
+
function normalizeDelayMs(value, defaultValue, allowZero = true) {
|
|
1461
|
+
if (value === Infinity) {
|
|
1462
|
+
return Infinity;
|
|
1463
|
+
}
|
|
1464
|
+
if (typeof value === "number" && Number.isFinite(value) && (allowZero ? value >= 0 : value > 0)) {
|
|
1465
|
+
return value;
|
|
1466
|
+
}
|
|
1467
|
+
return defaultValue;
|
|
1468
|
+
}
|
|
1457
1469
|
function getCurrentPausedTotal(activity, now = Date.now()) {
|
|
1458
1470
|
if (activity.pauseStartTime === null) {
|
|
1459
1471
|
return activity.pausedTime;
|
|
@@ -1503,9 +1515,40 @@ function stopHeartbeatInterval(activity) {
|
|
|
1503
1515
|
clearInterval(activity.heartbeatIntervalId);
|
|
1504
1516
|
activity.heartbeatIntervalId = null;
|
|
1505
1517
|
}
|
|
1518
|
+
function clearInactivityTimeout(activity) {
|
|
1519
|
+
if (activity.inactivityTimeoutId !== null) {
|
|
1520
|
+
clearTimeout(activity.inactivityTimeoutId);
|
|
1521
|
+
activity.inactivityTimeoutId = null;
|
|
1522
|
+
}
|
|
1523
|
+
activity.inactivityTimerStartedAt = null;
|
|
1524
|
+
}
|
|
1525
|
+
function clearPausedHeartbeatTimeout(activity) {
|
|
1526
|
+
if (activity.pausedHeartbeatTimeoutId !== null) {
|
|
1527
|
+
clearTimeout(activity.pausedHeartbeatTimeoutId);
|
|
1528
|
+
activity.pausedHeartbeatTimeoutId = null;
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
function hasBlockingPauseReason(activity) {
|
|
1532
|
+
for (const reason of activity.pauseReasons) {
|
|
1533
|
+
if (reason !== "inactivity") {
|
|
1534
|
+
return true;
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
return false;
|
|
1538
|
+
}
|
|
1539
|
+
function isDocumentHidden() {
|
|
1540
|
+
return typeof document !== "undefined" && document.visibilityState === "hidden";
|
|
1541
|
+
}
|
|
1542
|
+
function isAutoPauseReason(reason) {
|
|
1543
|
+
return reason === "hidden" || reason === "inactivity";
|
|
1544
|
+
}
|
|
1545
|
+
function hasAutoPauseReason(activity) {
|
|
1546
|
+
return activity.pauseReasons.has("hidden") || activity.pauseReasons.has("inactivity");
|
|
1547
|
+
}
|
|
1506
1548
|
function createTimebackActivityTracker(client) {
|
|
1507
1549
|
let currentActivity = null;
|
|
1508
1550
|
let boundVisibilityHandler = null;
|
|
1551
|
+
let boundUserInteractionHandler = null;
|
|
1509
1552
|
let boundShellPauseHandler = null;
|
|
1510
1553
|
let boundShellResumeHandler = null;
|
|
1511
1554
|
let boundPageHideHandler = null;
|
|
@@ -1517,25 +1560,134 @@ function createTimebackActivityTracker(client) {
|
|
|
1517
1560
|
flushHeartbeat();
|
|
1518
1561
|
}, activity.heartbeatIntervalMs);
|
|
1519
1562
|
}
|
|
1563
|
+
function resetPausedHeartbeatWindow(activity) {
|
|
1564
|
+
const now = Date.now();
|
|
1565
|
+
const pausedAtReset = getCurrentPausedTotal(activity, now);
|
|
1566
|
+
activity.pausedHeartbeatTimedOut = false;
|
|
1567
|
+
activity.windowStartTime = now;
|
|
1568
|
+
activity.windowPausedAtStart = pausedAtReset;
|
|
1569
|
+
startHeartbeatInterval(activity);
|
|
1570
|
+
}
|
|
1571
|
+
function armPausedHeartbeatTimeout(activity) {
|
|
1572
|
+
if (activity.pausedHeartbeatTimeoutMs === Infinity) {
|
|
1573
|
+
return;
|
|
1574
|
+
}
|
|
1575
|
+
clearPausedHeartbeatTimeout(activity);
|
|
1576
|
+
const trackedActivity = activity;
|
|
1577
|
+
activity.pausedHeartbeatTimeoutId = setTimeout(() => {
|
|
1578
|
+
if (currentActivity !== trackedActivity) {
|
|
1579
|
+
return;
|
|
1580
|
+
}
|
|
1581
|
+
trackedActivity.pausedHeartbeatTimeoutId = null;
|
|
1582
|
+
trackedActivity.pausedHeartbeatTimedOut = true;
|
|
1583
|
+
stopHeartbeatInterval(trackedActivity);
|
|
1584
|
+
}, activity.pausedHeartbeatTimeoutMs);
|
|
1585
|
+
}
|
|
1520
1586
|
function addPauseReason(reason) {
|
|
1521
1587
|
if (!currentActivity) {
|
|
1522
1588
|
return;
|
|
1523
1589
|
}
|
|
1524
1590
|
const wasPaused = currentActivity.pauseReasons.size > 0;
|
|
1591
|
+
const wasAutoPaused = hasAutoPauseReason(currentActivity);
|
|
1592
|
+
const alreadyHadReason = currentActivity.pauseReasons.has(reason);
|
|
1525
1593
|
currentActivity.pauseReasons.add(reason);
|
|
1526
1594
|
if (!wasPaused && currentActivity.pauseReasons.size > 0) {
|
|
1527
1595
|
currentActivity.pauseStartTime = Date.now();
|
|
1528
1596
|
}
|
|
1597
|
+
if (isAutoPauseReason(reason) && !alreadyHadReason && !wasAutoPaused) {
|
|
1598
|
+
armPausedHeartbeatTimeout(currentActivity);
|
|
1599
|
+
}
|
|
1600
|
+
syncInactivityTracking();
|
|
1529
1601
|
}
|
|
1530
1602
|
function removePauseReason(reason) {
|
|
1531
1603
|
if (!currentActivity) {
|
|
1532
1604
|
return;
|
|
1533
1605
|
}
|
|
1606
|
+
const hadReason = currentActivity.pauseReasons.has(reason);
|
|
1607
|
+
const wasAutoPaused = hasAutoPauseReason(currentActivity);
|
|
1534
1608
|
currentActivity.pauseReasons.delete(reason);
|
|
1535
1609
|
if (currentActivity.pauseReasons.size === 0 && currentActivity.pauseStartTime !== null) {
|
|
1536
1610
|
currentActivity.pausedTime += Date.now() - currentActivity.pauseStartTime;
|
|
1537
1611
|
currentActivity.pauseStartTime = null;
|
|
1538
1612
|
}
|
|
1613
|
+
if (isAutoPauseReason(reason) && hadReason && wasAutoPaused && !hasAutoPauseReason(currentActivity)) {
|
|
1614
|
+
clearPausedHeartbeatTimeout(currentActivity);
|
|
1615
|
+
if (currentActivity.pausedHeartbeatTimedOut) {
|
|
1616
|
+
resetPausedHeartbeatWindow(currentActivity);
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
syncInactivityTracking();
|
|
1620
|
+
}
|
|
1621
|
+
function captureRemainingInactivityMs(activity, now = Date.now()) {
|
|
1622
|
+
if (activity.inactivityTimerStartedAt === null) {
|
|
1623
|
+
return;
|
|
1624
|
+
}
|
|
1625
|
+
const elapsedMs = Math.max(0, now - activity.inactivityTimerStartedAt);
|
|
1626
|
+
activity.remainingInactivityMs = Math.max(0, activity.remainingInactivityMs - elapsedMs);
|
|
1627
|
+
clearInactivityTimeout(activity);
|
|
1628
|
+
}
|
|
1629
|
+
function shouldRunInactivityCountdown(activity) {
|
|
1630
|
+
if (activity.inactivityTimeoutMs === Infinity) {
|
|
1631
|
+
return false;
|
|
1632
|
+
}
|
|
1633
|
+
if (isDocumentHidden()) {
|
|
1634
|
+
return false;
|
|
1635
|
+
}
|
|
1636
|
+
if (activity.pauseReasons.has("inactivity")) {
|
|
1637
|
+
return false;
|
|
1638
|
+
}
|
|
1639
|
+
return !hasBlockingPauseReason(activity);
|
|
1640
|
+
}
|
|
1641
|
+
function armInactivityTimeout(activity) {
|
|
1642
|
+
if (!shouldRunInactivityCountdown(activity)) {
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
if (activity.remainingInactivityMs <= 0) {
|
|
1646
|
+
addPauseReason("inactivity");
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
clearInactivityTimeout(activity);
|
|
1650
|
+
const trackedActivity = activity;
|
|
1651
|
+
activity.inactivityTimerStartedAt = Date.now();
|
|
1652
|
+
activity.inactivityTimeoutId = setTimeout(() => {
|
|
1653
|
+
if (currentActivity !== trackedActivity) {
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1656
|
+
trackedActivity.remainingInactivityMs = 0;
|
|
1657
|
+
clearInactivityTimeout(trackedActivity);
|
|
1658
|
+
addPauseReason("inactivity");
|
|
1659
|
+
}, trackedActivity.remainingInactivityMs);
|
|
1660
|
+
}
|
|
1661
|
+
function syncInactivityTracking() {
|
|
1662
|
+
const activity = currentActivity;
|
|
1663
|
+
if (!activity) {
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
if (shouldRunInactivityCountdown(activity)) {
|
|
1667
|
+
if (activity.inactivityTimeoutId === null) {
|
|
1668
|
+
armInactivityTimeout(activity);
|
|
1669
|
+
}
|
|
1670
|
+
return;
|
|
1671
|
+
}
|
|
1672
|
+
if (activity.inactivityTimeoutId !== null) {
|
|
1673
|
+
captureRemainingInactivityMs(activity);
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
function resetInactivityTracking(activity) {
|
|
1677
|
+
clearInactivityTimeout(activity);
|
|
1678
|
+
activity.remainingInactivityMs = activity.inactivityTimeoutMs;
|
|
1679
|
+
}
|
|
1680
|
+
function handleUserInteraction() {
|
|
1681
|
+
const activity = currentActivity;
|
|
1682
|
+
if (!activity || activity.inactivityTimeoutMs === Infinity || isDocumentHidden()) {
|
|
1683
|
+
return;
|
|
1684
|
+
}
|
|
1685
|
+
resetInactivityTracking(activity);
|
|
1686
|
+
if (activity.pauseReasons.has("inactivity")) {
|
|
1687
|
+
removePauseReason("inactivity");
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1690
|
+
syncInactivityTracking();
|
|
1539
1691
|
}
|
|
1540
1692
|
function handleVisibilityChange() {
|
|
1541
1693
|
if (!currentActivity) {
|
|
@@ -1543,34 +1695,8 @@ function createTimebackActivityTracker(client) {
|
|
|
1543
1695
|
}
|
|
1544
1696
|
if (document.visibilityState === "hidden") {
|
|
1545
1697
|
addPauseReason("hidden");
|
|
1546
|
-
if (currentActivity.hiddenTimeoutMs !== Infinity) {
|
|
1547
|
-
if (currentActivity.hiddenTimeoutId !== null) {
|
|
1548
|
-
clearTimeout(currentActivity.hiddenTimeoutId);
|
|
1549
|
-
}
|
|
1550
|
-
const activity = currentActivity;
|
|
1551
|
-
currentActivity.hiddenTimeoutId = setTimeout(() => {
|
|
1552
|
-
if (currentActivity === activity) {
|
|
1553
|
-
activity.hiddenTimeoutId = null;
|
|
1554
|
-
activity.hiddenTimedOut = true;
|
|
1555
|
-
stopHeartbeatInterval(activity);
|
|
1556
|
-
}
|
|
1557
|
-
}, activity.hiddenTimeoutMs);
|
|
1558
|
-
}
|
|
1559
1698
|
} else {
|
|
1560
|
-
const shouldResetWindow = currentActivity.hiddenTimedOut;
|
|
1561
|
-
if (currentActivity.hiddenTimeoutId !== null) {
|
|
1562
|
-
clearTimeout(currentActivity.hiddenTimeoutId);
|
|
1563
|
-
currentActivity.hiddenTimeoutId = null;
|
|
1564
|
-
}
|
|
1565
1699
|
removePauseReason("hidden");
|
|
1566
|
-
if (shouldResetWindow) {
|
|
1567
|
-
const now = Date.now();
|
|
1568
|
-
const pausedAtReset = getCurrentPausedTotal(currentActivity, now);
|
|
1569
|
-
currentActivity.hiddenTimedOut = false;
|
|
1570
|
-
currentActivity.windowStartTime = now;
|
|
1571
|
-
currentActivity.windowPausedAtStart = pausedAtReset;
|
|
1572
|
-
startHeartbeatInterval(currentActivity);
|
|
1573
|
-
}
|
|
1574
1700
|
}
|
|
1575
1701
|
}
|
|
1576
1702
|
function handleShellPause() {
|
|
@@ -1645,6 +1771,12 @@ function createTimebackActivityTracker(client) {
|
|
|
1645
1771
|
document.removeEventListener("visibilitychange", boundVisibilityHandler);
|
|
1646
1772
|
boundVisibilityHandler = null;
|
|
1647
1773
|
}
|
|
1774
|
+
if (boundUserInteractionHandler && typeof document !== "undefined") {
|
|
1775
|
+
for (const eventName of USER_ACTIVITY_EVENTS) {
|
|
1776
|
+
document.removeEventListener(eventName, boundUserInteractionHandler, USER_ACTIVITY_LISTENER_OPTIONS);
|
|
1777
|
+
}
|
|
1778
|
+
boundUserInteractionHandler = null;
|
|
1779
|
+
}
|
|
1648
1780
|
if (boundShellPauseHandler) {
|
|
1649
1781
|
messaging.unlisten("PLAYCADEMY_PAUSE" /* PAUSE */, boundShellPauseHandler);
|
|
1650
1782
|
boundShellPauseHandler = null;
|
|
@@ -1657,8 +1789,9 @@ function createTimebackActivityTracker(client) {
|
|
|
1657
1789
|
globalThis.window.removeEventListener("pagehide", boundPageHideHandler);
|
|
1658
1790
|
boundPageHideHandler = null;
|
|
1659
1791
|
}
|
|
1660
|
-
if (currentActivity
|
|
1661
|
-
|
|
1792
|
+
if (currentActivity) {
|
|
1793
|
+
clearInactivityTimeout(currentActivity);
|
|
1794
|
+
clearPausedHeartbeatTimeout(currentActivity);
|
|
1662
1795
|
}
|
|
1663
1796
|
if (currentActivity?.heartbeatIntervalId != null) {
|
|
1664
1797
|
stopHeartbeatInterval(currentActivity);
|
|
@@ -1671,7 +1804,9 @@ function createTimebackActivityTracker(client) {
|
|
|
1671
1804
|
}
|
|
1672
1805
|
cleanupListeners();
|
|
1673
1806
|
const now = Date.now();
|
|
1674
|
-
const heartbeatIntervalMs = options?.heartbeatIntervalMs
|
|
1807
|
+
const heartbeatIntervalMs = normalizeDelayMs(options?.heartbeatIntervalMs, DEFAULT_HEARTBEAT_INTERVAL_MS, false);
|
|
1808
|
+
const pausedHeartbeatTimeoutMs = normalizeDelayMs(options?.pausedHeartbeatTimeoutMs ?? options?.hiddenTimeoutMs, DEFAULT_PAUSED_HEARTBEAT_TIMEOUT_MS, false);
|
|
1809
|
+
const inactivityTimeoutMs = normalizeDelayMs(options?.inactivityTimeoutMs, DEFAULT_INACTIVITY_TIMEOUT_MS, false);
|
|
1675
1810
|
currentActivity = {
|
|
1676
1811
|
runId: options?.runId ?? crypto.randomUUID(),
|
|
1677
1812
|
resumeId: crypto.randomUUID(),
|
|
@@ -1680,13 +1815,17 @@ function createTimebackActivityTracker(client) {
|
|
|
1680
1815
|
pausedTime: 0,
|
|
1681
1816
|
pauseStartTime: null,
|
|
1682
1817
|
pauseReasons: new Set,
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1818
|
+
pausedHeartbeatTimeoutId: null,
|
|
1819
|
+
pausedHeartbeatTimedOut: false,
|
|
1820
|
+
pausedHeartbeatTimeoutMs,
|
|
1686
1821
|
windowStartTime: now,
|
|
1687
1822
|
windowPausedAtStart: 0,
|
|
1688
1823
|
heartbeatIntervalId: null,
|
|
1689
1824
|
heartbeatIntervalMs,
|
|
1825
|
+
inactivityTimeoutId: null,
|
|
1826
|
+
inactivityTimeoutMs,
|
|
1827
|
+
inactivityTimerStartedAt: null,
|
|
1828
|
+
remainingInactivityMs: inactivityTimeoutMs,
|
|
1690
1829
|
flushInFlight: null,
|
|
1691
1830
|
totalPersistedActiveMs: 0,
|
|
1692
1831
|
totalPersistedPausedMs: 0
|
|
@@ -1694,6 +1833,10 @@ function createTimebackActivityTracker(client) {
|
|
|
1694
1833
|
if (typeof document !== "undefined") {
|
|
1695
1834
|
boundVisibilityHandler = handleVisibilityChange;
|
|
1696
1835
|
document.addEventListener("visibilitychange", boundVisibilityHandler);
|
|
1836
|
+
boundUserInteractionHandler = handleUserInteraction;
|
|
1837
|
+
for (const eventName of USER_ACTIVITY_EVENTS) {
|
|
1838
|
+
document.addEventListener(eventName, boundUserInteractionHandler, USER_ACTIVITY_LISTENER_OPTIONS);
|
|
1839
|
+
}
|
|
1697
1840
|
if (document.visibilityState === "hidden") {
|
|
1698
1841
|
handleVisibilityChange();
|
|
1699
1842
|
}
|
|
@@ -1707,6 +1850,7 @@ function createTimebackActivityTracker(client) {
|
|
|
1707
1850
|
boundShellResumeHandler = handleShellResume;
|
|
1708
1851
|
messaging.listen("PLAYCADEMY_PAUSE" /* PAUSE */, boundShellPauseHandler);
|
|
1709
1852
|
messaging.listen("PLAYCADEMY_RESUME" /* RESUME */, boundShellResumeHandler);
|
|
1853
|
+
syncInactivityTracking();
|
|
1710
1854
|
},
|
|
1711
1855
|
pauseActivity() {
|
|
1712
1856
|
if (!currentActivity) {
|
package/dist/internal.js
CHANGED
|
@@ -1451,9 +1451,21 @@ function isValidUUID(value) {
|
|
|
1451
1451
|
}
|
|
1452
1452
|
|
|
1453
1453
|
// src/core/activity-tracker.ts
|
|
1454
|
-
var
|
|
1454
|
+
var DEFAULT_PAUSED_HEARTBEAT_TIMEOUT_MS = 10 * 60 * 1000;
|
|
1455
1455
|
var DEFAULT_HEARTBEAT_INTERVAL_MS = 15000;
|
|
1456
|
+
var DEFAULT_INACTIVITY_TIMEOUT_MS = 10 * 60 * 1000;
|
|
1456
1457
|
var HEARTBEAT_RETRY_POLICY = { retryableMethods: ["POST"] };
|
|
1458
|
+
var USER_ACTIVITY_EVENTS = ["keydown", "pointerdown", "pointermove", "wheel"];
|
|
1459
|
+
var USER_ACTIVITY_LISTENER_OPTIONS = { capture: true };
|
|
1460
|
+
function normalizeDelayMs(value, defaultValue, allowZero = true) {
|
|
1461
|
+
if (value === Infinity) {
|
|
1462
|
+
return Infinity;
|
|
1463
|
+
}
|
|
1464
|
+
if (typeof value === "number" && Number.isFinite(value) && (allowZero ? value >= 0 : value > 0)) {
|
|
1465
|
+
return value;
|
|
1466
|
+
}
|
|
1467
|
+
return defaultValue;
|
|
1468
|
+
}
|
|
1457
1469
|
function getCurrentPausedTotal(activity, now = Date.now()) {
|
|
1458
1470
|
if (activity.pauseStartTime === null) {
|
|
1459
1471
|
return activity.pausedTime;
|
|
@@ -1503,9 +1515,40 @@ function stopHeartbeatInterval(activity) {
|
|
|
1503
1515
|
clearInterval(activity.heartbeatIntervalId);
|
|
1504
1516
|
activity.heartbeatIntervalId = null;
|
|
1505
1517
|
}
|
|
1518
|
+
function clearInactivityTimeout(activity) {
|
|
1519
|
+
if (activity.inactivityTimeoutId !== null) {
|
|
1520
|
+
clearTimeout(activity.inactivityTimeoutId);
|
|
1521
|
+
activity.inactivityTimeoutId = null;
|
|
1522
|
+
}
|
|
1523
|
+
activity.inactivityTimerStartedAt = null;
|
|
1524
|
+
}
|
|
1525
|
+
function clearPausedHeartbeatTimeout(activity) {
|
|
1526
|
+
if (activity.pausedHeartbeatTimeoutId !== null) {
|
|
1527
|
+
clearTimeout(activity.pausedHeartbeatTimeoutId);
|
|
1528
|
+
activity.pausedHeartbeatTimeoutId = null;
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
function hasBlockingPauseReason(activity) {
|
|
1532
|
+
for (const reason of activity.pauseReasons) {
|
|
1533
|
+
if (reason !== "inactivity") {
|
|
1534
|
+
return true;
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
return false;
|
|
1538
|
+
}
|
|
1539
|
+
function isDocumentHidden() {
|
|
1540
|
+
return typeof document !== "undefined" && document.visibilityState === "hidden";
|
|
1541
|
+
}
|
|
1542
|
+
function isAutoPauseReason(reason) {
|
|
1543
|
+
return reason === "hidden" || reason === "inactivity";
|
|
1544
|
+
}
|
|
1545
|
+
function hasAutoPauseReason(activity) {
|
|
1546
|
+
return activity.pauseReasons.has("hidden") || activity.pauseReasons.has("inactivity");
|
|
1547
|
+
}
|
|
1506
1548
|
function createTimebackActivityTracker(client) {
|
|
1507
1549
|
let currentActivity = null;
|
|
1508
1550
|
let boundVisibilityHandler = null;
|
|
1551
|
+
let boundUserInteractionHandler = null;
|
|
1509
1552
|
let boundShellPauseHandler = null;
|
|
1510
1553
|
let boundShellResumeHandler = null;
|
|
1511
1554
|
let boundPageHideHandler = null;
|
|
@@ -1517,25 +1560,134 @@ function createTimebackActivityTracker(client) {
|
|
|
1517
1560
|
flushHeartbeat();
|
|
1518
1561
|
}, activity.heartbeatIntervalMs);
|
|
1519
1562
|
}
|
|
1563
|
+
function resetPausedHeartbeatWindow(activity) {
|
|
1564
|
+
const now = Date.now();
|
|
1565
|
+
const pausedAtReset = getCurrentPausedTotal(activity, now);
|
|
1566
|
+
activity.pausedHeartbeatTimedOut = false;
|
|
1567
|
+
activity.windowStartTime = now;
|
|
1568
|
+
activity.windowPausedAtStart = pausedAtReset;
|
|
1569
|
+
startHeartbeatInterval(activity);
|
|
1570
|
+
}
|
|
1571
|
+
function armPausedHeartbeatTimeout(activity) {
|
|
1572
|
+
if (activity.pausedHeartbeatTimeoutMs === Infinity) {
|
|
1573
|
+
return;
|
|
1574
|
+
}
|
|
1575
|
+
clearPausedHeartbeatTimeout(activity);
|
|
1576
|
+
const trackedActivity = activity;
|
|
1577
|
+
activity.pausedHeartbeatTimeoutId = setTimeout(() => {
|
|
1578
|
+
if (currentActivity !== trackedActivity) {
|
|
1579
|
+
return;
|
|
1580
|
+
}
|
|
1581
|
+
trackedActivity.pausedHeartbeatTimeoutId = null;
|
|
1582
|
+
trackedActivity.pausedHeartbeatTimedOut = true;
|
|
1583
|
+
stopHeartbeatInterval(trackedActivity);
|
|
1584
|
+
}, activity.pausedHeartbeatTimeoutMs);
|
|
1585
|
+
}
|
|
1520
1586
|
function addPauseReason(reason) {
|
|
1521
1587
|
if (!currentActivity) {
|
|
1522
1588
|
return;
|
|
1523
1589
|
}
|
|
1524
1590
|
const wasPaused = currentActivity.pauseReasons.size > 0;
|
|
1591
|
+
const wasAutoPaused = hasAutoPauseReason(currentActivity);
|
|
1592
|
+
const alreadyHadReason = currentActivity.pauseReasons.has(reason);
|
|
1525
1593
|
currentActivity.pauseReasons.add(reason);
|
|
1526
1594
|
if (!wasPaused && currentActivity.pauseReasons.size > 0) {
|
|
1527
1595
|
currentActivity.pauseStartTime = Date.now();
|
|
1528
1596
|
}
|
|
1597
|
+
if (isAutoPauseReason(reason) && !alreadyHadReason && !wasAutoPaused) {
|
|
1598
|
+
armPausedHeartbeatTimeout(currentActivity);
|
|
1599
|
+
}
|
|
1600
|
+
syncInactivityTracking();
|
|
1529
1601
|
}
|
|
1530
1602
|
function removePauseReason(reason) {
|
|
1531
1603
|
if (!currentActivity) {
|
|
1532
1604
|
return;
|
|
1533
1605
|
}
|
|
1606
|
+
const hadReason = currentActivity.pauseReasons.has(reason);
|
|
1607
|
+
const wasAutoPaused = hasAutoPauseReason(currentActivity);
|
|
1534
1608
|
currentActivity.pauseReasons.delete(reason);
|
|
1535
1609
|
if (currentActivity.pauseReasons.size === 0 && currentActivity.pauseStartTime !== null) {
|
|
1536
1610
|
currentActivity.pausedTime += Date.now() - currentActivity.pauseStartTime;
|
|
1537
1611
|
currentActivity.pauseStartTime = null;
|
|
1538
1612
|
}
|
|
1613
|
+
if (isAutoPauseReason(reason) && hadReason && wasAutoPaused && !hasAutoPauseReason(currentActivity)) {
|
|
1614
|
+
clearPausedHeartbeatTimeout(currentActivity);
|
|
1615
|
+
if (currentActivity.pausedHeartbeatTimedOut) {
|
|
1616
|
+
resetPausedHeartbeatWindow(currentActivity);
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
syncInactivityTracking();
|
|
1620
|
+
}
|
|
1621
|
+
function captureRemainingInactivityMs(activity, now = Date.now()) {
|
|
1622
|
+
if (activity.inactivityTimerStartedAt === null) {
|
|
1623
|
+
return;
|
|
1624
|
+
}
|
|
1625
|
+
const elapsedMs = Math.max(0, now - activity.inactivityTimerStartedAt);
|
|
1626
|
+
activity.remainingInactivityMs = Math.max(0, activity.remainingInactivityMs - elapsedMs);
|
|
1627
|
+
clearInactivityTimeout(activity);
|
|
1628
|
+
}
|
|
1629
|
+
function shouldRunInactivityCountdown(activity) {
|
|
1630
|
+
if (activity.inactivityTimeoutMs === Infinity) {
|
|
1631
|
+
return false;
|
|
1632
|
+
}
|
|
1633
|
+
if (isDocumentHidden()) {
|
|
1634
|
+
return false;
|
|
1635
|
+
}
|
|
1636
|
+
if (activity.pauseReasons.has("inactivity")) {
|
|
1637
|
+
return false;
|
|
1638
|
+
}
|
|
1639
|
+
return !hasBlockingPauseReason(activity);
|
|
1640
|
+
}
|
|
1641
|
+
function armInactivityTimeout(activity) {
|
|
1642
|
+
if (!shouldRunInactivityCountdown(activity)) {
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
if (activity.remainingInactivityMs <= 0) {
|
|
1646
|
+
addPauseReason("inactivity");
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
clearInactivityTimeout(activity);
|
|
1650
|
+
const trackedActivity = activity;
|
|
1651
|
+
activity.inactivityTimerStartedAt = Date.now();
|
|
1652
|
+
activity.inactivityTimeoutId = setTimeout(() => {
|
|
1653
|
+
if (currentActivity !== trackedActivity) {
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1656
|
+
trackedActivity.remainingInactivityMs = 0;
|
|
1657
|
+
clearInactivityTimeout(trackedActivity);
|
|
1658
|
+
addPauseReason("inactivity");
|
|
1659
|
+
}, trackedActivity.remainingInactivityMs);
|
|
1660
|
+
}
|
|
1661
|
+
function syncInactivityTracking() {
|
|
1662
|
+
const activity = currentActivity;
|
|
1663
|
+
if (!activity) {
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
if (shouldRunInactivityCountdown(activity)) {
|
|
1667
|
+
if (activity.inactivityTimeoutId === null) {
|
|
1668
|
+
armInactivityTimeout(activity);
|
|
1669
|
+
}
|
|
1670
|
+
return;
|
|
1671
|
+
}
|
|
1672
|
+
if (activity.inactivityTimeoutId !== null) {
|
|
1673
|
+
captureRemainingInactivityMs(activity);
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
function resetInactivityTracking(activity) {
|
|
1677
|
+
clearInactivityTimeout(activity);
|
|
1678
|
+
activity.remainingInactivityMs = activity.inactivityTimeoutMs;
|
|
1679
|
+
}
|
|
1680
|
+
function handleUserInteraction() {
|
|
1681
|
+
const activity = currentActivity;
|
|
1682
|
+
if (!activity || activity.inactivityTimeoutMs === Infinity || isDocumentHidden()) {
|
|
1683
|
+
return;
|
|
1684
|
+
}
|
|
1685
|
+
resetInactivityTracking(activity);
|
|
1686
|
+
if (activity.pauseReasons.has("inactivity")) {
|
|
1687
|
+
removePauseReason("inactivity");
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1690
|
+
syncInactivityTracking();
|
|
1539
1691
|
}
|
|
1540
1692
|
function handleVisibilityChange() {
|
|
1541
1693
|
if (!currentActivity) {
|
|
@@ -1543,34 +1695,8 @@ function createTimebackActivityTracker(client) {
|
|
|
1543
1695
|
}
|
|
1544
1696
|
if (document.visibilityState === "hidden") {
|
|
1545
1697
|
addPauseReason("hidden");
|
|
1546
|
-
if (currentActivity.hiddenTimeoutMs !== Infinity) {
|
|
1547
|
-
if (currentActivity.hiddenTimeoutId !== null) {
|
|
1548
|
-
clearTimeout(currentActivity.hiddenTimeoutId);
|
|
1549
|
-
}
|
|
1550
|
-
const activity = currentActivity;
|
|
1551
|
-
currentActivity.hiddenTimeoutId = setTimeout(() => {
|
|
1552
|
-
if (currentActivity === activity) {
|
|
1553
|
-
activity.hiddenTimeoutId = null;
|
|
1554
|
-
activity.hiddenTimedOut = true;
|
|
1555
|
-
stopHeartbeatInterval(activity);
|
|
1556
|
-
}
|
|
1557
|
-
}, activity.hiddenTimeoutMs);
|
|
1558
|
-
}
|
|
1559
1698
|
} else {
|
|
1560
|
-
const shouldResetWindow = currentActivity.hiddenTimedOut;
|
|
1561
|
-
if (currentActivity.hiddenTimeoutId !== null) {
|
|
1562
|
-
clearTimeout(currentActivity.hiddenTimeoutId);
|
|
1563
|
-
currentActivity.hiddenTimeoutId = null;
|
|
1564
|
-
}
|
|
1565
1699
|
removePauseReason("hidden");
|
|
1566
|
-
if (shouldResetWindow) {
|
|
1567
|
-
const now = Date.now();
|
|
1568
|
-
const pausedAtReset = getCurrentPausedTotal(currentActivity, now);
|
|
1569
|
-
currentActivity.hiddenTimedOut = false;
|
|
1570
|
-
currentActivity.windowStartTime = now;
|
|
1571
|
-
currentActivity.windowPausedAtStart = pausedAtReset;
|
|
1572
|
-
startHeartbeatInterval(currentActivity);
|
|
1573
|
-
}
|
|
1574
1700
|
}
|
|
1575
1701
|
}
|
|
1576
1702
|
function handleShellPause() {
|
|
@@ -1645,6 +1771,12 @@ function createTimebackActivityTracker(client) {
|
|
|
1645
1771
|
document.removeEventListener("visibilitychange", boundVisibilityHandler);
|
|
1646
1772
|
boundVisibilityHandler = null;
|
|
1647
1773
|
}
|
|
1774
|
+
if (boundUserInteractionHandler && typeof document !== "undefined") {
|
|
1775
|
+
for (const eventName of USER_ACTIVITY_EVENTS) {
|
|
1776
|
+
document.removeEventListener(eventName, boundUserInteractionHandler, USER_ACTIVITY_LISTENER_OPTIONS);
|
|
1777
|
+
}
|
|
1778
|
+
boundUserInteractionHandler = null;
|
|
1779
|
+
}
|
|
1648
1780
|
if (boundShellPauseHandler) {
|
|
1649
1781
|
messaging.unlisten("PLAYCADEMY_PAUSE" /* PAUSE */, boundShellPauseHandler);
|
|
1650
1782
|
boundShellPauseHandler = null;
|
|
@@ -1657,8 +1789,9 @@ function createTimebackActivityTracker(client) {
|
|
|
1657
1789
|
globalThis.window.removeEventListener("pagehide", boundPageHideHandler);
|
|
1658
1790
|
boundPageHideHandler = null;
|
|
1659
1791
|
}
|
|
1660
|
-
if (currentActivity
|
|
1661
|
-
|
|
1792
|
+
if (currentActivity) {
|
|
1793
|
+
clearInactivityTimeout(currentActivity);
|
|
1794
|
+
clearPausedHeartbeatTimeout(currentActivity);
|
|
1662
1795
|
}
|
|
1663
1796
|
if (currentActivity?.heartbeatIntervalId != null) {
|
|
1664
1797
|
stopHeartbeatInterval(currentActivity);
|
|
@@ -1671,7 +1804,9 @@ function createTimebackActivityTracker(client) {
|
|
|
1671
1804
|
}
|
|
1672
1805
|
cleanupListeners();
|
|
1673
1806
|
const now = Date.now();
|
|
1674
|
-
const heartbeatIntervalMs = options?.heartbeatIntervalMs
|
|
1807
|
+
const heartbeatIntervalMs = normalizeDelayMs(options?.heartbeatIntervalMs, DEFAULT_HEARTBEAT_INTERVAL_MS, false);
|
|
1808
|
+
const pausedHeartbeatTimeoutMs = normalizeDelayMs(options?.pausedHeartbeatTimeoutMs ?? options?.hiddenTimeoutMs, DEFAULT_PAUSED_HEARTBEAT_TIMEOUT_MS, false);
|
|
1809
|
+
const inactivityTimeoutMs = normalizeDelayMs(options?.inactivityTimeoutMs, DEFAULT_INACTIVITY_TIMEOUT_MS, false);
|
|
1675
1810
|
currentActivity = {
|
|
1676
1811
|
runId: options?.runId ?? crypto.randomUUID(),
|
|
1677
1812
|
resumeId: crypto.randomUUID(),
|
|
@@ -1680,13 +1815,17 @@ function createTimebackActivityTracker(client) {
|
|
|
1680
1815
|
pausedTime: 0,
|
|
1681
1816
|
pauseStartTime: null,
|
|
1682
1817
|
pauseReasons: new Set,
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1818
|
+
pausedHeartbeatTimeoutId: null,
|
|
1819
|
+
pausedHeartbeatTimedOut: false,
|
|
1820
|
+
pausedHeartbeatTimeoutMs,
|
|
1686
1821
|
windowStartTime: now,
|
|
1687
1822
|
windowPausedAtStart: 0,
|
|
1688
1823
|
heartbeatIntervalId: null,
|
|
1689
1824
|
heartbeatIntervalMs,
|
|
1825
|
+
inactivityTimeoutId: null,
|
|
1826
|
+
inactivityTimeoutMs,
|
|
1827
|
+
inactivityTimerStartedAt: null,
|
|
1828
|
+
remainingInactivityMs: inactivityTimeoutMs,
|
|
1690
1829
|
flushInFlight: null,
|
|
1691
1830
|
totalPersistedActiveMs: 0,
|
|
1692
1831
|
totalPersistedPausedMs: 0
|
|
@@ -1694,6 +1833,10 @@ function createTimebackActivityTracker(client) {
|
|
|
1694
1833
|
if (typeof document !== "undefined") {
|
|
1695
1834
|
boundVisibilityHandler = handleVisibilityChange;
|
|
1696
1835
|
document.addEventListener("visibilitychange", boundVisibilityHandler);
|
|
1836
|
+
boundUserInteractionHandler = handleUserInteraction;
|
|
1837
|
+
for (const eventName of USER_ACTIVITY_EVENTS) {
|
|
1838
|
+
document.addEventListener(eventName, boundUserInteractionHandler, USER_ACTIVITY_LISTENER_OPTIONS);
|
|
1839
|
+
}
|
|
1697
1840
|
if (document.visibilityState === "hidden") {
|
|
1698
1841
|
handleVisibilityChange();
|
|
1699
1842
|
}
|
|
@@ -1707,6 +1850,7 @@ function createTimebackActivityTracker(client) {
|
|
|
1707
1850
|
boundShellResumeHandler = handleShellResume;
|
|
1708
1851
|
messaging.listen("PLAYCADEMY_PAUSE" /* PAUSE */, boundShellPauseHandler);
|
|
1709
1852
|
messaging.listen("PLAYCADEMY_RESUME" /* RESUME */, boundShellResumeHandler);
|
|
1853
|
+
syncInactivityTracking();
|
|
1710
1854
|
},
|
|
1711
1855
|
pauseActivity() {
|
|
1712
1856
|
if (!currentActivity) {
|
package/dist/types.d.ts
CHANGED
|
@@ -5174,16 +5174,34 @@ declare abstract class PlaycademyBaseClient {
|
|
|
5174
5174
|
*/
|
|
5175
5175
|
interface StartActivityOptions {
|
|
5176
5176
|
/**
|
|
5177
|
-
* How long
|
|
5178
|
-
*
|
|
5177
|
+
* How long heartbeats continue after the activity is automatically paused
|
|
5178
|
+
* because the tab is hidden or the player is inactive while visible.
|
|
5179
|
+
* Defaults to 10 minutes. Set to `Infinity` to keep heartbeats running
|
|
5180
|
+
* indefinitely during automatic pauses. Invalid values fall back to the
|
|
5181
|
+
* 10-minute default.
|
|
5182
|
+
*/
|
|
5183
|
+
pausedHeartbeatTimeoutMs?: number;
|
|
5184
|
+
/**
|
|
5185
|
+
* @deprecated Use `pausedHeartbeatTimeoutMs` instead.
|
|
5186
|
+
*
|
|
5187
|
+
* Backward-compatible alias for callers that still use the old option
|
|
5188
|
+
* name from earlier SDK releases.
|
|
5179
5189
|
*/
|
|
5180
5190
|
hiddenTimeoutMs?: number;
|
|
5181
5191
|
/**
|
|
5182
5192
|
* How often to flush periodic heartbeats with accumulated time data.
|
|
5183
5193
|
* Defaults to 15 seconds. Set to `Infinity` to disable the interval;
|
|
5184
|
-
* final unload/endActivity flushes still run.
|
|
5194
|
+
* final unload/endActivity flushes still run. Values must be greater than
|
|
5195
|
+
* 0 or `Infinity`; invalid values fall back to the 15-second default.
|
|
5185
5196
|
*/
|
|
5186
5197
|
heartbeatIntervalMs?: number;
|
|
5198
|
+
/**
|
|
5199
|
+
* How long the tab can remain visible without keyboard or mouse activity
|
|
5200
|
+
* before the activity is marked inactive. Defaults to 10 minutes. Set to
|
|
5201
|
+
* `Infinity` to disable keyboard/mouse inactivity tracking. Invalid values
|
|
5202
|
+
* fall back to the 10-minute default.
|
|
5203
|
+
*/
|
|
5204
|
+
inactivityTimeoutMs?: number;
|
|
5187
5205
|
/**
|
|
5188
5206
|
* Stable identifier for this activity run. When provided, it is used on
|
|
5189
5207
|
* every heartbeat and on endActivity instead of a freshly-generated UUID.
|
|
@@ -5265,7 +5283,9 @@ declare class PlaycademyClient extends PlaycademyBaseClient {
|
|
|
5265
5283
|
* - `user.fetch()` - Refresh user context from server
|
|
5266
5284
|
*
|
|
5267
5285
|
* Activity tracking:
|
|
5268
|
-
* - `startActivity(metadata)` - Begin tracking an activity
|
|
5286
|
+
* - `startActivity(metadata)` - Begin tracking an activity with automatic
|
|
5287
|
+
* hidden-tab and visible-tab inactivity handling, plus configurable
|
|
5288
|
+
* paused-heartbeat timeout behavior
|
|
5269
5289
|
* - `pauseActivity()` / `resumeActivity()` - Pause/resume timer
|
|
5270
5290
|
* - `endActivity(scoreData)` - Submit activity results to TimeBack
|
|
5271
5291
|
*/
|