@playcademy/sdk 0.7.3 → 0.7.4-beta.2
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/dist/index.js +42 -10
- package/dist/internal.d.ts +48 -1
- package/dist/internal.js +42 -10
- package/dist/types.d.ts +48 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1692,34 +1692,47 @@ function createTimebackActivityTracker(client) {
|
|
|
1692
1692
|
activity.windowPausedAtStart = pausedAtReset;
|
|
1693
1693
|
startHeartbeatInterval(activity);
|
|
1694
1694
|
}
|
|
1695
|
-
function
|
|
1695
|
+
function markPausedHeartbeatTimedOut(activity) {
|
|
1696
|
+
activity.pausedHeartbeatTimeoutId = null;
|
|
1697
|
+
activity.pausedHeartbeatTimedOut = true;
|
|
1698
|
+
stopHeartbeatInterval(activity);
|
|
1699
|
+
}
|
|
1700
|
+
function armPausedHeartbeatTimeout(activity, startedAt = Date.now()) {
|
|
1696
1701
|
if (activity.pausedHeartbeatTimeoutMs === Infinity) {
|
|
1697
1702
|
return;
|
|
1698
1703
|
}
|
|
1699
1704
|
clearPausedHeartbeatTimeout(activity);
|
|
1705
|
+
const now = Date.now();
|
|
1706
|
+
const elapsedPauseMs = Math.max(0, now - Math.min(startedAt, now));
|
|
1707
|
+
const remainingTimeoutMs = activity.pausedHeartbeatTimeoutMs - elapsedPauseMs;
|
|
1708
|
+
if (remainingTimeoutMs <= 0) {
|
|
1709
|
+
markPausedHeartbeatTimedOut(activity);
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
1700
1712
|
const trackedActivity = activity;
|
|
1701
1713
|
activity.pausedHeartbeatTimeoutId = setTimeout(() => {
|
|
1702
1714
|
if (currentActivity !== trackedActivity) {
|
|
1703
1715
|
return;
|
|
1704
1716
|
}
|
|
1705
|
-
trackedActivity
|
|
1706
|
-
|
|
1707
|
-
stopHeartbeatInterval(trackedActivity);
|
|
1708
|
-
}, activity.pausedHeartbeatTimeoutMs);
|
|
1717
|
+
markPausedHeartbeatTimedOut(trackedActivity);
|
|
1718
|
+
}, remainingTimeoutMs);
|
|
1709
1719
|
}
|
|
1710
|
-
function addPauseReason(reason) {
|
|
1720
|
+
function addPauseReason(reason, startedAt = Date.now()) {
|
|
1711
1721
|
if (!currentActivity) {
|
|
1712
1722
|
return;
|
|
1713
1723
|
}
|
|
1724
|
+
if (reason !== "inactivity") {
|
|
1725
|
+
applyOverdueInactivity();
|
|
1726
|
+
}
|
|
1714
1727
|
const wasPaused = currentActivity.pauseReasons.size > 0;
|
|
1715
1728
|
const wasAutoPaused = hasAutoPauseReason(currentActivity);
|
|
1716
1729
|
const alreadyHadReason = currentActivity.pauseReasons.has(reason);
|
|
1717
1730
|
currentActivity.pauseReasons.add(reason);
|
|
1718
1731
|
if (!wasPaused && currentActivity.pauseReasons.size > 0) {
|
|
1719
|
-
currentActivity.pauseStartTime = Date.now();
|
|
1732
|
+
currentActivity.pauseStartTime = Math.min(startedAt, Date.now());
|
|
1720
1733
|
}
|
|
1721
1734
|
if (isAutoPauseReason(reason) && !alreadyHadReason && !wasAutoPaused) {
|
|
1722
|
-
armPausedHeartbeatTimeout(currentActivity);
|
|
1735
|
+
armPausedHeartbeatTimeout(currentActivity, startedAt);
|
|
1723
1736
|
}
|
|
1724
1737
|
syncInactivityTracking();
|
|
1725
1738
|
}
|
|
@@ -1772,16 +1785,31 @@ function createTimebackActivityTracker(client) {
|
|
|
1772
1785
|
}
|
|
1773
1786
|
clearInactivityTimeout(activity);
|
|
1774
1787
|
const trackedActivity = activity;
|
|
1775
|
-
|
|
1788
|
+
const timerStartedAt = Date.now();
|
|
1789
|
+
const inactivityStartedAt = timerStartedAt + trackedActivity.remainingInactivityMs;
|
|
1790
|
+
activity.inactivityTimerStartedAt = timerStartedAt;
|
|
1776
1791
|
activity.inactivityTimeoutId = setTimeout(() => {
|
|
1777
1792
|
if (currentActivity !== trackedActivity) {
|
|
1778
1793
|
return;
|
|
1779
1794
|
}
|
|
1780
1795
|
trackedActivity.remainingInactivityMs = 0;
|
|
1781
1796
|
clearInactivityTimeout(trackedActivity);
|
|
1782
|
-
addPauseReason("inactivity");
|
|
1797
|
+
addPauseReason("inactivity", inactivityStartedAt);
|
|
1783
1798
|
}, trackedActivity.remainingInactivityMs);
|
|
1784
1799
|
}
|
|
1800
|
+
function applyOverdueInactivity(now = Date.now()) {
|
|
1801
|
+
const activity = currentActivity;
|
|
1802
|
+
if (!activity || activity.inactivityTimerStartedAt === null || activity.pauseReasons.has("inactivity")) {
|
|
1803
|
+
return;
|
|
1804
|
+
}
|
|
1805
|
+
const inactivityStartedAt = activity.inactivityTimerStartedAt + activity.remainingInactivityMs;
|
|
1806
|
+
if (now < inactivityStartedAt) {
|
|
1807
|
+
return;
|
|
1808
|
+
}
|
|
1809
|
+
activity.remainingInactivityMs = 0;
|
|
1810
|
+
clearInactivityTimeout(activity);
|
|
1811
|
+
addPauseReason("inactivity", inactivityStartedAt);
|
|
1812
|
+
}
|
|
1785
1813
|
function syncInactivityTracking() {
|
|
1786
1814
|
const activity = currentActivity;
|
|
1787
1815
|
if (!activity) {
|
|
@@ -1802,6 +1830,7 @@ function createTimebackActivityTracker(client) {
|
|
|
1802
1830
|
activity.remainingInactivityMs = activity.inactivityTimeoutMs;
|
|
1803
1831
|
}
|
|
1804
1832
|
function handleUserInteraction() {
|
|
1833
|
+
applyOverdueInactivity();
|
|
1805
1834
|
const activity = currentActivity;
|
|
1806
1835
|
if (!activity || activity.inactivityTimeoutMs === Infinity || isDocumentHidden()) {
|
|
1807
1836
|
return;
|
|
@@ -1840,6 +1869,7 @@ function createTimebackActivityTracker(client) {
|
|
|
1840
1869
|
};
|
|
1841
1870
|
}
|
|
1842
1871
|
async function flushHeartbeat(isFinal) {
|
|
1872
|
+
applyOverdueInactivity();
|
|
1843
1873
|
const activity = currentActivity;
|
|
1844
1874
|
if (!activity) {
|
|
1845
1875
|
return;
|
|
@@ -1860,6 +1890,7 @@ function createTimebackActivityTracker(client) {
|
|
|
1860
1890
|
});
|
|
1861
1891
|
}
|
|
1862
1892
|
function handlePageHide() {
|
|
1893
|
+
applyOverdueInactivity();
|
|
1863
1894
|
const activity = currentActivity;
|
|
1864
1895
|
if (!activity) {
|
|
1865
1896
|
return;
|
|
@@ -1999,6 +2030,7 @@ function createTimebackActivityTracker(client) {
|
|
|
1999
2030
|
throw new Error("No activity in progress. Call startActivity() before endActivity().");
|
|
2000
2031
|
}
|
|
2001
2032
|
const activity = currentActivity;
|
|
2033
|
+
applyOverdueInactivity();
|
|
2002
2034
|
cleanupListeners();
|
|
2003
2035
|
await flushHeartbeat(true);
|
|
2004
2036
|
if (activity.pauseStartTime !== null) {
|
package/dist/internal.d.ts
CHANGED
|
@@ -733,6 +733,53 @@ interface QtiTestQuestionsResponse {
|
|
|
733
733
|
questions: QtiTestQuestionRef[];
|
|
734
734
|
}
|
|
735
735
|
|
|
736
|
+
/**
|
|
737
|
+
* Game-side learning metrics for a single student.
|
|
738
|
+
*
|
|
739
|
+
* Returned by opted-in game workers from their reserved
|
|
740
|
+
* `/__playcademy/metrics` route.
|
|
741
|
+
*/
|
|
742
|
+
interface GameMetricsResponse {
|
|
743
|
+
/** Student identifier (Playcademy user ID). */
|
|
744
|
+
studentId: string;
|
|
745
|
+
/** Student email, when available to the game. */
|
|
746
|
+
email?: string;
|
|
747
|
+
/** Per-course metrics grouped by TimeBack grade and subject. */
|
|
748
|
+
courses: GameCourseMetrics[];
|
|
749
|
+
}
|
|
750
|
+
interface GameCourseMetrics {
|
|
751
|
+
/** Grade level matching the game's TimeBack integration. */
|
|
752
|
+
grade: TimebackGrade;
|
|
753
|
+
/** Subject matching the game's TimeBack integration. */
|
|
754
|
+
subject: TimebackSubject;
|
|
755
|
+
/** Total XP the game has recorded for this student in this course. */
|
|
756
|
+
totalXp: number;
|
|
757
|
+
/** Mastered units the game has recorded for this course. */
|
|
758
|
+
masteredUnits: number;
|
|
759
|
+
/** Total active time in seconds the game has recorded for this course. */
|
|
760
|
+
activeTimeSeconds: number;
|
|
761
|
+
/** Optional per-activity breakdown. */
|
|
762
|
+
activities?: GameActivityMetrics[];
|
|
763
|
+
}
|
|
764
|
+
interface GameActivityMetrics {
|
|
765
|
+
activityId: string;
|
|
766
|
+
activityName?: string;
|
|
767
|
+
totalXp: number;
|
|
768
|
+
masteredUnits: number;
|
|
769
|
+
activeTimeSeconds: number;
|
|
770
|
+
completionCount: number;
|
|
771
|
+
lastCompletedAt?: string;
|
|
772
|
+
}
|
|
773
|
+
type GameMetricsUnsupportedReason = 'no_timeback_integration' | 'no_active_deployment' | 'route_not_implemented' | 'fetch_failed' | 'invalid_response';
|
|
774
|
+
type GameMetricsProxyResponse = {
|
|
775
|
+
supported: true;
|
|
776
|
+
metrics: GameMetricsResponse;
|
|
777
|
+
} | {
|
|
778
|
+
supported: false;
|
|
779
|
+
reason: GameMetricsUnsupportedReason;
|
|
780
|
+
details?: string;
|
|
781
|
+
};
|
|
782
|
+
|
|
736
783
|
/**
|
|
737
784
|
* Achievement Types
|
|
738
785
|
*
|
|
@@ -7757,4 +7804,4 @@ declare class PlaycademyInternalClient extends PlaycademyBaseClient {
|
|
|
7757
7804
|
}
|
|
7758
7805
|
|
|
7759
7806
|
export { AchievementCompletionType, ApiError, ConnectionManager, ConnectionMonitor, MessageEvents, NotificationStatus, NotificationType, PlaycademyInternalClient as PlaycademyClient, PlaycademyError, PlaycademyInternalClient, extractApiErrorInfo, messaging };
|
|
7760
|
-
export type { AchievementCurrent, AchievementHistoryEntry, AchievementProgressResponse, AchievementScopeType, AchievementWithStatus, ApiErrorCode, ApiErrorInfo, AssessmentBankStatus, AssessmentRow, AssessmentSummary, AuthCallbackPayload, AuthOptions, AuthProviderType, AuthResult, AuthServerMessage, AuthStateChangePayload, AuthStateUpdate, AuthenticatedUser, BetterAuthApiKey, BetterAuthApiKeyResponse, BetterAuthSignInResponse, BucketFile, CharacterComponentRow as CharacterComponent, CharacterComponentType, CharacterComponentWithSpriteUrl, CharacterComponentsOptions, ClientConfig, ClientEvents, ConnectionMonitorConfig, ConnectionState, ConnectionStatePayload, CourseXp, CreateCharacterData, CreateMapObjectData, CurrencyRow as Currency, DemoEndOptions, DemoEndPayload, DevUploadEvent, DevUploadHooks, DeveloperStatusEnumType, DeveloperStatusResponse, DeveloperStatusValue, DisconnectContext, DisconnectHandler, DisplayAlertPayload, ErrorResponseBody, EventListeners, ExternalGame, FetchedGame, Game, GameContextPayload, GameCustomHostname, GameInitUser, GameLeaderboardEntry, MapRow as GameMap, GamePlatform, GameRow as GameRecord, GameSessionRow as GameSession, GameTimebackIntegration, GameTokenResponse, GameType, GameUser, GetXpOptions, HostedGame, InitErrorPayload, InitPayload, InsertCurrencyInput, InsertItemInput, InsertShopListingInput, InteractionType, InventoryItemRow as InventoryItem, InventoryItemWithItem, InventoryMutationResponse, ItemRow as Item, ItemType, KVKeyEntry, KVKeyMetadata, KVSeedEntry, KVStatsResponse, KeyEventPayload, LeaderboardEntry, LeaderboardOptions, LeaderboardTimeframe, LevelConfigRow as LevelConfig, LevelProgressResponse, LevelUpCheckResult, LoginResponse, ManifestV1, MapData, MapElementRow as MapElement, MapElementMetadata, MapElementWithGame, MapObjectRow as MapObject, MapObjectWithItem, MessageEventMap, NotificationRow as Notification, NotificationStats, PlaceableItemMetadata, PlatformTimebackUser, PlatformTimebackUserContext, PlaycademyMode, PlaycademyServerClientConfig, PlaycademyServerClientState, PlayerCharacterRow as PlayerCharacter, PlayerCharacterAccessoryRow as PlayerCharacterAccessory, PlayerCurrency, PlayerInventoryItem, PlayerProfile, PlayerSessionPayload, PopulateStudentResponse, QtiTestQuestionRef, QtiTestQuestionsResponse, RealtimeTokenResponse, ScoreSubmission, ShopCurrency, ShopDisplayItem, ShopListingRow as ShopListing, ShopViewResponse, SpriteAnimationFrame, SpriteConfigWithDimensions, SpriteTemplateRow as SpriteTemplate, SpriteTemplateData, StartSessionResponse, TelemetryPayload, TimebackEnrollment, TimebackInitContext, TimebackOrganization, TimebackUser, TimebackUserContext, TimebackUserXp, TodayXpResponse, TokenRefreshPayload, TokenType, TotalXpResponse, UpdateCharacterData, UpdateCurrencyInput, UpdateItemInput, UpdateShopListingInput, UpsertGameMetadataInput, UserRow as User, UserEnrollment, UserInfo, UserLevelRow as UserLevel, UserLevelWithConfig, UserOrganization, UserRank, UserRankResponse, UserRoleEnumType, UserScore, UserTimebackData, XPAddResult, XpHistoryResponse, XpResponse, XpSummaryResponse };
|
|
7807
|
+
export type { AchievementCurrent, AchievementHistoryEntry, AchievementProgressResponse, AchievementScopeType, AchievementWithStatus, ApiErrorCode, ApiErrorInfo, AssessmentBankStatus, AssessmentRow, AssessmentSummary, AuthCallbackPayload, AuthOptions, AuthProviderType, AuthResult, AuthServerMessage, AuthStateChangePayload, AuthStateUpdate, AuthenticatedUser, BetterAuthApiKey, BetterAuthApiKeyResponse, BetterAuthSignInResponse, BucketFile, CharacterComponentRow as CharacterComponent, CharacterComponentType, CharacterComponentWithSpriteUrl, CharacterComponentsOptions, ClientConfig, ClientEvents, ConnectionMonitorConfig, ConnectionState, ConnectionStatePayload, CourseXp, CreateCharacterData, CreateMapObjectData, CurrencyRow as Currency, DemoEndOptions, DemoEndPayload, DevUploadEvent, DevUploadHooks, DeveloperStatusEnumType, DeveloperStatusResponse, DeveloperStatusValue, DisconnectContext, DisconnectHandler, DisplayAlertPayload, ErrorResponseBody, EventListeners, ExternalGame, FetchedGame, Game, GameActivityMetrics, GameContextPayload, GameCourseMetrics, GameCustomHostname, GameInitUser, GameLeaderboardEntry, MapRow as GameMap, GameMetricsProxyResponse, GameMetricsResponse, GameMetricsUnsupportedReason, GamePlatform, GameRow as GameRecord, GameSessionRow as GameSession, GameTimebackIntegration, GameTokenResponse, GameType, GameUser, GetXpOptions, HostedGame, InitErrorPayload, InitPayload, InsertCurrencyInput, InsertItemInput, InsertShopListingInput, InteractionType, InventoryItemRow as InventoryItem, InventoryItemWithItem, InventoryMutationResponse, ItemRow as Item, ItemType, KVKeyEntry, KVKeyMetadata, KVSeedEntry, KVStatsResponse, KeyEventPayload, LeaderboardEntry, LeaderboardOptions, LeaderboardTimeframe, LevelConfigRow as LevelConfig, LevelProgressResponse, LevelUpCheckResult, LoginResponse, ManifestV1, MapData, MapElementRow as MapElement, MapElementMetadata, MapElementWithGame, MapObjectRow as MapObject, MapObjectWithItem, MessageEventMap, NotificationRow as Notification, NotificationStats, PlaceableItemMetadata, PlatformTimebackUser, PlatformTimebackUserContext, PlaycademyMode, PlaycademyServerClientConfig, PlaycademyServerClientState, PlayerCharacterRow as PlayerCharacter, PlayerCharacterAccessoryRow as PlayerCharacterAccessory, PlayerCurrency, PlayerInventoryItem, PlayerProfile, PlayerSessionPayload, PopulateStudentResponse, QtiTestQuestionRef, QtiTestQuestionsResponse, RealtimeTokenResponse, ScoreSubmission, ShopCurrency, ShopDisplayItem, ShopListingRow as ShopListing, ShopViewResponse, SpriteAnimationFrame, SpriteConfigWithDimensions, SpriteTemplateRow as SpriteTemplate, SpriteTemplateData, StartSessionResponse, TelemetryPayload, TimebackEnrollment, TimebackInitContext, TimebackOrganization, TimebackUser, TimebackUserContext, TimebackUserXp, TodayXpResponse, TokenRefreshPayload, TokenType, TotalXpResponse, UpdateCharacterData, UpdateCurrencyInput, UpdateItemInput, UpdateShopListingInput, UpsertGameMetadataInput, UserRow as User, UserEnrollment, UserInfo, UserLevelRow as UserLevel, UserLevelWithConfig, UserOrganization, UserRank, UserRankResponse, UserRoleEnumType, UserScore, UserTimebackData, XPAddResult, XpHistoryResponse, XpResponse, XpSummaryResponse };
|
package/dist/internal.js
CHANGED
|
@@ -1692,34 +1692,47 @@ function createTimebackActivityTracker(client) {
|
|
|
1692
1692
|
activity.windowPausedAtStart = pausedAtReset;
|
|
1693
1693
|
startHeartbeatInterval(activity);
|
|
1694
1694
|
}
|
|
1695
|
-
function
|
|
1695
|
+
function markPausedHeartbeatTimedOut(activity) {
|
|
1696
|
+
activity.pausedHeartbeatTimeoutId = null;
|
|
1697
|
+
activity.pausedHeartbeatTimedOut = true;
|
|
1698
|
+
stopHeartbeatInterval(activity);
|
|
1699
|
+
}
|
|
1700
|
+
function armPausedHeartbeatTimeout(activity, startedAt = Date.now()) {
|
|
1696
1701
|
if (activity.pausedHeartbeatTimeoutMs === Infinity) {
|
|
1697
1702
|
return;
|
|
1698
1703
|
}
|
|
1699
1704
|
clearPausedHeartbeatTimeout(activity);
|
|
1705
|
+
const now = Date.now();
|
|
1706
|
+
const elapsedPauseMs = Math.max(0, now - Math.min(startedAt, now));
|
|
1707
|
+
const remainingTimeoutMs = activity.pausedHeartbeatTimeoutMs - elapsedPauseMs;
|
|
1708
|
+
if (remainingTimeoutMs <= 0) {
|
|
1709
|
+
markPausedHeartbeatTimedOut(activity);
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
1700
1712
|
const trackedActivity = activity;
|
|
1701
1713
|
activity.pausedHeartbeatTimeoutId = setTimeout(() => {
|
|
1702
1714
|
if (currentActivity !== trackedActivity) {
|
|
1703
1715
|
return;
|
|
1704
1716
|
}
|
|
1705
|
-
trackedActivity
|
|
1706
|
-
|
|
1707
|
-
stopHeartbeatInterval(trackedActivity);
|
|
1708
|
-
}, activity.pausedHeartbeatTimeoutMs);
|
|
1717
|
+
markPausedHeartbeatTimedOut(trackedActivity);
|
|
1718
|
+
}, remainingTimeoutMs);
|
|
1709
1719
|
}
|
|
1710
|
-
function addPauseReason(reason) {
|
|
1720
|
+
function addPauseReason(reason, startedAt = Date.now()) {
|
|
1711
1721
|
if (!currentActivity) {
|
|
1712
1722
|
return;
|
|
1713
1723
|
}
|
|
1724
|
+
if (reason !== "inactivity") {
|
|
1725
|
+
applyOverdueInactivity();
|
|
1726
|
+
}
|
|
1714
1727
|
const wasPaused = currentActivity.pauseReasons.size > 0;
|
|
1715
1728
|
const wasAutoPaused = hasAutoPauseReason(currentActivity);
|
|
1716
1729
|
const alreadyHadReason = currentActivity.pauseReasons.has(reason);
|
|
1717
1730
|
currentActivity.pauseReasons.add(reason);
|
|
1718
1731
|
if (!wasPaused && currentActivity.pauseReasons.size > 0) {
|
|
1719
|
-
currentActivity.pauseStartTime = Date.now();
|
|
1732
|
+
currentActivity.pauseStartTime = Math.min(startedAt, Date.now());
|
|
1720
1733
|
}
|
|
1721
1734
|
if (isAutoPauseReason(reason) && !alreadyHadReason && !wasAutoPaused) {
|
|
1722
|
-
armPausedHeartbeatTimeout(currentActivity);
|
|
1735
|
+
armPausedHeartbeatTimeout(currentActivity, startedAt);
|
|
1723
1736
|
}
|
|
1724
1737
|
syncInactivityTracking();
|
|
1725
1738
|
}
|
|
@@ -1772,16 +1785,31 @@ function createTimebackActivityTracker(client) {
|
|
|
1772
1785
|
}
|
|
1773
1786
|
clearInactivityTimeout(activity);
|
|
1774
1787
|
const trackedActivity = activity;
|
|
1775
|
-
|
|
1788
|
+
const timerStartedAt = Date.now();
|
|
1789
|
+
const inactivityStartedAt = timerStartedAt + trackedActivity.remainingInactivityMs;
|
|
1790
|
+
activity.inactivityTimerStartedAt = timerStartedAt;
|
|
1776
1791
|
activity.inactivityTimeoutId = setTimeout(() => {
|
|
1777
1792
|
if (currentActivity !== trackedActivity) {
|
|
1778
1793
|
return;
|
|
1779
1794
|
}
|
|
1780
1795
|
trackedActivity.remainingInactivityMs = 0;
|
|
1781
1796
|
clearInactivityTimeout(trackedActivity);
|
|
1782
|
-
addPauseReason("inactivity");
|
|
1797
|
+
addPauseReason("inactivity", inactivityStartedAt);
|
|
1783
1798
|
}, trackedActivity.remainingInactivityMs);
|
|
1784
1799
|
}
|
|
1800
|
+
function applyOverdueInactivity(now = Date.now()) {
|
|
1801
|
+
const activity = currentActivity;
|
|
1802
|
+
if (!activity || activity.inactivityTimerStartedAt === null || activity.pauseReasons.has("inactivity")) {
|
|
1803
|
+
return;
|
|
1804
|
+
}
|
|
1805
|
+
const inactivityStartedAt = activity.inactivityTimerStartedAt + activity.remainingInactivityMs;
|
|
1806
|
+
if (now < inactivityStartedAt) {
|
|
1807
|
+
return;
|
|
1808
|
+
}
|
|
1809
|
+
activity.remainingInactivityMs = 0;
|
|
1810
|
+
clearInactivityTimeout(activity);
|
|
1811
|
+
addPauseReason("inactivity", inactivityStartedAt);
|
|
1812
|
+
}
|
|
1785
1813
|
function syncInactivityTracking() {
|
|
1786
1814
|
const activity = currentActivity;
|
|
1787
1815
|
if (!activity) {
|
|
@@ -1802,6 +1830,7 @@ function createTimebackActivityTracker(client) {
|
|
|
1802
1830
|
activity.remainingInactivityMs = activity.inactivityTimeoutMs;
|
|
1803
1831
|
}
|
|
1804
1832
|
function handleUserInteraction() {
|
|
1833
|
+
applyOverdueInactivity();
|
|
1805
1834
|
const activity = currentActivity;
|
|
1806
1835
|
if (!activity || activity.inactivityTimeoutMs === Infinity || isDocumentHidden()) {
|
|
1807
1836
|
return;
|
|
@@ -1840,6 +1869,7 @@ function createTimebackActivityTracker(client) {
|
|
|
1840
1869
|
};
|
|
1841
1870
|
}
|
|
1842
1871
|
async function flushHeartbeat(isFinal) {
|
|
1872
|
+
applyOverdueInactivity();
|
|
1843
1873
|
const activity = currentActivity;
|
|
1844
1874
|
if (!activity) {
|
|
1845
1875
|
return;
|
|
@@ -1860,6 +1890,7 @@ function createTimebackActivityTracker(client) {
|
|
|
1860
1890
|
});
|
|
1861
1891
|
}
|
|
1862
1892
|
function handlePageHide() {
|
|
1893
|
+
applyOverdueInactivity();
|
|
1863
1894
|
const activity = currentActivity;
|
|
1864
1895
|
if (!activity) {
|
|
1865
1896
|
return;
|
|
@@ -1999,6 +2030,7 @@ function createTimebackActivityTracker(client) {
|
|
|
1999
2030
|
throw new Error("No activity in progress. Call startActivity() before endActivity().");
|
|
2000
2031
|
}
|
|
2001
2032
|
const activity = currentActivity;
|
|
2033
|
+
applyOverdueInactivity();
|
|
2002
2034
|
cleanupListeners();
|
|
2003
2035
|
await flushHeartbeat(true);
|
|
2004
2036
|
if (activity.pauseStartTime !== null) {
|
package/dist/types.d.ts
CHANGED
|
@@ -401,6 +401,53 @@ interface QtiTestQuestionsResponse {
|
|
|
401
401
|
questions: QtiTestQuestionRef[];
|
|
402
402
|
}
|
|
403
403
|
|
|
404
|
+
/**
|
|
405
|
+
* Game-side learning metrics for a single student.
|
|
406
|
+
*
|
|
407
|
+
* Returned by opted-in game workers from their reserved
|
|
408
|
+
* `/__playcademy/metrics` route.
|
|
409
|
+
*/
|
|
410
|
+
interface GameMetricsResponse {
|
|
411
|
+
/** Student identifier (Playcademy user ID). */
|
|
412
|
+
studentId: string;
|
|
413
|
+
/** Student email, when available to the game. */
|
|
414
|
+
email?: string;
|
|
415
|
+
/** Per-course metrics grouped by TimeBack grade and subject. */
|
|
416
|
+
courses: GameCourseMetrics[];
|
|
417
|
+
}
|
|
418
|
+
interface GameCourseMetrics {
|
|
419
|
+
/** Grade level matching the game's TimeBack integration. */
|
|
420
|
+
grade: TimebackGrade;
|
|
421
|
+
/** Subject matching the game's TimeBack integration. */
|
|
422
|
+
subject: TimebackSubject;
|
|
423
|
+
/** Total XP the game has recorded for this student in this course. */
|
|
424
|
+
totalXp: number;
|
|
425
|
+
/** Mastered units the game has recorded for this course. */
|
|
426
|
+
masteredUnits: number;
|
|
427
|
+
/** Total active time in seconds the game has recorded for this course. */
|
|
428
|
+
activeTimeSeconds: number;
|
|
429
|
+
/** Optional per-activity breakdown. */
|
|
430
|
+
activities?: GameActivityMetrics[];
|
|
431
|
+
}
|
|
432
|
+
interface GameActivityMetrics {
|
|
433
|
+
activityId: string;
|
|
434
|
+
activityName?: string;
|
|
435
|
+
totalXp: number;
|
|
436
|
+
masteredUnits: number;
|
|
437
|
+
activeTimeSeconds: number;
|
|
438
|
+
completionCount: number;
|
|
439
|
+
lastCompletedAt?: string;
|
|
440
|
+
}
|
|
441
|
+
type GameMetricsUnsupportedReason = 'no_timeback_integration' | 'no_active_deployment' | 'route_not_implemented' | 'fetch_failed' | 'invalid_response';
|
|
442
|
+
type GameMetricsProxyResponse = {
|
|
443
|
+
supported: true;
|
|
444
|
+
metrics: GameMetricsResponse;
|
|
445
|
+
} | {
|
|
446
|
+
supported: false;
|
|
447
|
+
reason: GameMetricsUnsupportedReason;
|
|
448
|
+
details?: string;
|
|
449
|
+
};
|
|
450
|
+
|
|
404
451
|
/**
|
|
405
452
|
* @fileoverview Server SDK Type Definitions
|
|
406
453
|
*
|
|
@@ -6103,4 +6150,4 @@ interface AssessmentBankStatus {
|
|
|
6103
6150
|
}
|
|
6104
6151
|
|
|
6105
6152
|
export { AchievementCompletionType, NotificationStatus, NotificationType, PlaycademyClient };
|
|
6106
|
-
export type { AchievementCurrent, AchievementHistoryEntry, AchievementProgressResponse, AchievementScopeType, AchievementWithStatus, AssessmentBankStatus, AssessmentRow, AssessmentSummary, AuthCallbackPayload, AuthOptions, AuthProviderType, AuthResult, AuthServerMessage, AuthStateChangePayload, AuthStateUpdate, AuthenticatedUser, BetterAuthApiKey, BetterAuthApiKeyResponse, BetterAuthSignInResponse, BucketFile, CharacterComponentRow as CharacterComponent, CharacterComponentType, CharacterComponentWithSpriteUrl, CharacterComponentsOptions, ClientConfig, ClientEvents, ConnectionStatePayload, CourseXp, CreateCharacterData, CreateMapObjectData, CurrencyRow as Currency, DemoEndOptions, DemoEndPayload, DevUploadEvent, DevUploadHooks, DeveloperStatusEnumType, DeveloperStatusResponse, DeveloperStatusValue, DisconnectContext, DisconnectHandler, DisplayAlertPayload, EventListeners, ExternalGame, FetchedGame, Game, GameContextPayload, GameCustomHostname, GameInitUser, GameLeaderboardEntry, MapRow as GameMap, GamePlatform, GameRow as GameRecord, GameSessionRow as GameSession, GameTimebackIntegration, GameTokenResponse, GameType, GameUser, GetXpOptions, HostedGame, InitErrorPayload, InitPayload, InsertCurrencyInput, InsertItemInput, InsertShopListingInput, InteractionType, InventoryItemRow as InventoryItem, InventoryItemWithItem, InventoryMutationResponse, ItemRow as Item, ItemType, KVKeyEntry, KVKeyMetadata, KVSeedEntry, KVStatsResponse, KeyEventPayload, LeaderboardEntry, LeaderboardOptions, LeaderboardTimeframe, LevelConfigRow as LevelConfig, LevelProgressResponse, LevelUpCheckResult, LoginResponse, ManifestV1, MapData, MapElementRow as MapElement, MapElementMetadata, MapElementWithGame, MapObjectRow as MapObject, MapObjectWithItem, NotificationRow as Notification, NotificationStats, PlaceableItemMetadata, PlatformTimebackUser, PlatformTimebackUserContext, PlaycademyMode, PlaycademyServerClientConfig, PlaycademyServerClientState, PlayerCharacterRow as PlayerCharacter, PlayerCharacterAccessoryRow as PlayerCharacterAccessory, PlayerCurrency, PlayerInventoryItem, PlayerProfile, PlayerSessionPayload, PopulateStudentResponse, QtiTestQuestionRef, QtiTestQuestionsResponse, RealtimeTokenResponse, ScoreSubmission, ShopCurrency, ShopDisplayItem, ShopListingRow as ShopListing, ShopViewResponse, SpriteAnimationFrame, SpriteConfigWithDimensions, SpriteTemplateRow as SpriteTemplate, SpriteTemplateData, StartSessionResponse, TelemetryPayload, TimebackEnrollment, TimebackInitContext, TimebackOrganization, TimebackUser, TimebackUserContext, TimebackUserXp, TodayXpResponse, TokenRefreshPayload, TokenType, TotalXpResponse, UpdateCharacterData, UpdateCurrencyInput, UpdateItemInput, UpdateShopListingInput, UpsertGameMetadataInput, UserRow as User, UserEnrollment, UserInfo, UserLevelRow as UserLevel, UserLevelWithConfig, UserOrganization, UserRank, UserRankResponse, UserRoleEnumType, UserScore, UserTimebackData, XPAddResult, XpHistoryResponse, XpResponse, XpSummaryResponse };
|
|
6153
|
+
export type { AchievementCurrent, AchievementHistoryEntry, AchievementProgressResponse, AchievementScopeType, AchievementWithStatus, AssessmentBankStatus, AssessmentRow, AssessmentSummary, AuthCallbackPayload, AuthOptions, AuthProviderType, AuthResult, AuthServerMessage, AuthStateChangePayload, AuthStateUpdate, AuthenticatedUser, BetterAuthApiKey, BetterAuthApiKeyResponse, BetterAuthSignInResponse, BucketFile, CharacterComponentRow as CharacterComponent, CharacterComponentType, CharacterComponentWithSpriteUrl, CharacterComponentsOptions, ClientConfig, ClientEvents, ConnectionStatePayload, CourseXp, CreateCharacterData, CreateMapObjectData, CurrencyRow as Currency, DemoEndOptions, DemoEndPayload, DevUploadEvent, DevUploadHooks, DeveloperStatusEnumType, DeveloperStatusResponse, DeveloperStatusValue, DisconnectContext, DisconnectHandler, DisplayAlertPayload, EventListeners, ExternalGame, FetchedGame, Game, GameActivityMetrics, GameContextPayload, GameCourseMetrics, GameCustomHostname, GameInitUser, GameLeaderboardEntry, MapRow as GameMap, GameMetricsProxyResponse, GameMetricsResponse, GameMetricsUnsupportedReason, GamePlatform, GameRow as GameRecord, GameSessionRow as GameSession, GameTimebackIntegration, GameTokenResponse, GameType, GameUser, GetXpOptions, HostedGame, InitErrorPayload, InitPayload, InsertCurrencyInput, InsertItemInput, InsertShopListingInput, InteractionType, InventoryItemRow as InventoryItem, InventoryItemWithItem, InventoryMutationResponse, ItemRow as Item, ItemType, KVKeyEntry, KVKeyMetadata, KVSeedEntry, KVStatsResponse, KeyEventPayload, LeaderboardEntry, LeaderboardOptions, LeaderboardTimeframe, LevelConfigRow as LevelConfig, LevelProgressResponse, LevelUpCheckResult, LoginResponse, ManifestV1, MapData, MapElementRow as MapElement, MapElementMetadata, MapElementWithGame, MapObjectRow as MapObject, MapObjectWithItem, NotificationRow as Notification, NotificationStats, PlaceableItemMetadata, PlatformTimebackUser, PlatformTimebackUserContext, PlaycademyMode, PlaycademyServerClientConfig, PlaycademyServerClientState, PlayerCharacterRow as PlayerCharacter, PlayerCharacterAccessoryRow as PlayerCharacterAccessory, PlayerCurrency, PlayerInventoryItem, PlayerProfile, PlayerSessionPayload, PopulateStudentResponse, QtiTestQuestionRef, QtiTestQuestionsResponse, RealtimeTokenResponse, ScoreSubmission, ShopCurrency, ShopDisplayItem, ShopListingRow as ShopListing, ShopViewResponse, SpriteAnimationFrame, SpriteConfigWithDimensions, SpriteTemplateRow as SpriteTemplate, SpriteTemplateData, StartSessionResponse, TelemetryPayload, TimebackEnrollment, TimebackInitContext, TimebackOrganization, TimebackUser, TimebackUserContext, TimebackUserXp, TodayXpResponse, TokenRefreshPayload, TokenType, TotalXpResponse, UpdateCharacterData, UpdateCurrencyInput, UpdateItemInput, UpdateShopListingInput, UpsertGameMetadataInput, UserRow as User, UserEnrollment, UserInfo, UserLevelRow as UserLevel, UserLevelWithConfig, UserOrganization, UserRank, UserRankResponse, UserRoleEnumType, UserScore, UserTimebackData, XPAddResult, XpHistoryResponse, XpResponse, XpSummaryResponse };
|