@playcademy/sdk 0.9.1-beta.1 → 0.9.1-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.d.ts CHANGED
@@ -384,6 +384,125 @@ interface RetryPolicy {
384
384
  retryableMethods?: readonly Method[];
385
385
  }
386
386
 
387
+ /**
388
+ * User Types
389
+ *
390
+ * Enums, DTOs and API response types. Database row types are in @playcademy/data/types.
391
+ *
392
+ * @module types/user
393
+ */
394
+
395
+ type UserRoleEnumType = UserRole;
396
+ type DeveloperStatusEnumType = 'none' | 'pending' | 'approved';
397
+ type TimebackUserRole = 'administrator' | 'aide' | 'guardian' | 'parent' | 'proctor' | 'relative' | 'student' | 'teacher';
398
+ type TimebackOrgType = 'department' | 'school' | 'district' | 'local' | 'state' | 'national';
399
+ interface UserEnrollment {
400
+ gameId?: string;
401
+ courseId: string;
402
+ enrollmentIds?: {
403
+ active: string;
404
+ inactive?: string[];
405
+ };
406
+ grade: number;
407
+ subject: string;
408
+ orgId?: string;
409
+ }
410
+ interface UserOrganization {
411
+ id: string;
412
+ name: string | null;
413
+ type: TimebackOrgType | string;
414
+ isPrimary: boolean;
415
+ }
416
+ interface TimebackStudentProfile {
417
+ role: TimebackUserRole;
418
+ organizations: UserOrganization[];
419
+ }
420
+ interface UserTimebackData extends TimebackStudentProfile {
421
+ id: string;
422
+ enrollments: UserEnrollment[];
423
+ }
424
+ /**
425
+ * OpenID Connect UserInfo claims (NOT a database row).
426
+ */
427
+ interface UserInfo {
428
+ sub: string;
429
+ email: string;
430
+ name: string | null;
431
+ email_verified?: boolean;
432
+ given_name?: string;
433
+ family_name?: string;
434
+ issuer?: string;
435
+ lti_roles?: unknown;
436
+ lti_context?: unknown;
437
+ lti_resource_link?: unknown;
438
+ timeback_id?: string;
439
+ }
440
+ interface DemoProfile {
441
+ displayName: string;
442
+ isDefault: boolean;
443
+ }
444
+ /**
445
+ * Update shape for `client.demo.profile.update(...)`.
446
+ *
447
+ * Kept as a named type so callers typed against it pick up new fields
448
+ * automatically, but `displayName` is the only updatable field today and
449
+ * the server's `DemoProfileSchema` requires it — a no-field payload would
450
+ * 400 at runtime, so we model that at the type level too. When additional
451
+ * fields land, make them required/optional individually based on server
452
+ * validation, rather than blanket-optional.
453
+ */
454
+ interface DemoProfileUpdate {
455
+ displayName: string;
456
+ }
457
+ /**
458
+ * Authenticated user for API responses.
459
+ * Differs from UserRow: omits timebackId, adds hasTimebackAccount and timeback.
460
+ */
461
+ interface AuthenticatedUser {
462
+ id: string;
463
+ email: string;
464
+ emailVerified: boolean;
465
+ name: string | null;
466
+ image: string | null;
467
+ username: string | null;
468
+ role: UserRoleEnumType;
469
+ developerStatus: DeveloperStatusEnumType;
470
+ characterCreated: boolean;
471
+ createdAt: Date;
472
+ updatedAt: Date;
473
+ hasTimebackAccount: boolean;
474
+ timeback?: UserTimebackData;
475
+ }
476
+
477
+ /**
478
+ * Leaderboard Types
479
+ *
480
+ * @module types/leaderboard
481
+ */
482
+ type LeaderboardTimeframe = 'all_time' | 'monthly' | 'weekly' | 'daily';
483
+ interface LeaderboardOptions {
484
+ timeframe?: LeaderboardTimeframe;
485
+ limit?: number;
486
+ offset?: number;
487
+ gameId?: string;
488
+ }
489
+ /**
490
+ * Leaderboard entry with required game context.
491
+ * Used when fetching leaderboards for a specific game.
492
+ */
493
+ interface GameLeaderboardEntry {
494
+ rank: number;
495
+ userId: string;
496
+ username: string;
497
+ userImage?: string | null;
498
+ score: number;
499
+ achievedAt: Date;
500
+ metadata?: Record<string, unknown>;
501
+ gameId: string;
502
+ gameTitle: string;
503
+ gameSlug: string;
504
+ }
505
+
387
506
  /**
388
507
  * TimeBack Enums & Literal Types
389
508
  *
@@ -513,125 +632,6 @@ interface TTLCacheConfig {
513
632
  skipCache?: boolean;
514
633
  }
515
634
 
516
- /**
517
- * User Types
518
- *
519
- * Enums, DTOs and API response types. Database row types are in @playcademy/data/types.
520
- *
521
- * @module types/user
522
- */
523
-
524
- type UserRoleEnumType = UserRole;
525
- type DeveloperStatusEnumType = 'none' | 'pending' | 'approved';
526
- type TimebackUserRole = 'administrator' | 'aide' | 'guardian' | 'parent' | 'proctor' | 'relative' | 'student' | 'teacher';
527
- type TimebackOrgType = 'department' | 'school' | 'district' | 'local' | 'state' | 'national';
528
- interface UserEnrollment {
529
- gameId?: string;
530
- courseId: string;
531
- enrollmentIds?: {
532
- active: string;
533
- inactive?: string[];
534
- };
535
- grade: number;
536
- subject: string;
537
- orgId?: string;
538
- }
539
- interface UserOrganization {
540
- id: string;
541
- name: string | null;
542
- type: TimebackOrgType | string;
543
- isPrimary: boolean;
544
- }
545
- interface TimebackStudentProfile {
546
- role: TimebackUserRole;
547
- organizations: UserOrganization[];
548
- }
549
- interface UserTimebackData extends TimebackStudentProfile {
550
- id: string;
551
- enrollments: UserEnrollment[];
552
- }
553
- /**
554
- * OpenID Connect UserInfo claims (NOT a database row).
555
- */
556
- interface UserInfo {
557
- sub: string;
558
- email: string;
559
- name: string | null;
560
- email_verified?: boolean;
561
- given_name?: string;
562
- family_name?: string;
563
- issuer?: string;
564
- lti_roles?: unknown;
565
- lti_context?: unknown;
566
- lti_resource_link?: unknown;
567
- timeback_id?: string;
568
- }
569
- interface DemoProfile {
570
- displayName: string;
571
- isDefault: boolean;
572
- }
573
- /**
574
- * Update shape for `client.demo.profile.update(...)`.
575
- *
576
- * Kept as a named type so callers typed against it pick up new fields
577
- * automatically, but `displayName` is the only updatable field today and
578
- * the server's `DemoProfileSchema` requires it — a no-field payload would
579
- * 400 at runtime, so we model that at the type level too. When additional
580
- * fields land, make them required/optional individually based on server
581
- * validation, rather than blanket-optional.
582
- */
583
- interface DemoProfileUpdate {
584
- displayName: string;
585
- }
586
- /**
587
- * Authenticated user for API responses.
588
- * Differs from UserRow: omits timebackId, adds hasTimebackAccount and timeback.
589
- */
590
- interface AuthenticatedUser {
591
- id: string;
592
- email: string;
593
- emailVerified: boolean;
594
- name: string | null;
595
- image: string | null;
596
- username: string | null;
597
- role: UserRoleEnumType;
598
- developerStatus: DeveloperStatusEnumType;
599
- characterCreated: boolean;
600
- createdAt: Date;
601
- updatedAt: Date;
602
- hasTimebackAccount: boolean;
603
- timeback?: UserTimebackData;
604
- }
605
-
606
- /**
607
- * Leaderboard Types
608
- *
609
- * @module types/leaderboard
610
- */
611
- type LeaderboardTimeframe = 'all_time' | 'monthly' | 'weekly' | 'daily';
612
- interface LeaderboardOptions {
613
- timeframe?: LeaderboardTimeframe;
614
- limit?: number;
615
- offset?: number;
616
- gameId?: string;
617
- }
618
- /**
619
- * Leaderboard entry with required game context.
620
- * Used when fetching leaderboards for a specific game.
621
- */
622
- interface GameLeaderboardEntry {
623
- rank: number;
624
- userId: string;
625
- username: string;
626
- userImage?: string | null;
627
- score: number;
628
- achievedAt: Date;
629
- metadata?: Record<string, unknown>;
630
- gameId: string;
631
- gameTitle: string;
632
- gameSlug: string;
633
- }
634
-
635
635
  declare const items: drizzle_orm_pg_core.PgTableWithColumns<{
636
636
  name: "items";
637
637
  schema: undefined;
@@ -909,24 +909,195 @@ type InventoryItemWithItem = InventoryItemRow & {
909
909
  };
910
910
 
911
911
  /**
912
- * Auto-initializes a PlaycademyClient with context from the environment.
913
- * Works in both iframe mode (production/development) and standalone mode (local dev).
914
- *
915
- * This is the recommended way to initialize the SDK as it automatically:
916
- * - Detects the runtime environment (iframe vs standalone)
917
- * - Configures the client with the appropriate context
918
- * - Sets up event listeners for token refresh
919
- * - Exposes the client for debugging in development mode
920
- *
921
- * @param options - Optional configuration overrides
922
- * @param options.baseUrl - Override the base URL for API requests
923
- * @returns Promise resolving to a fully initialized PlaycademyClient
924
- * @throws Error if not running in a browser context
912
+ * @fileoverview Authentication Strategy Pattern
925
913
  *
926
- * @example
927
- * ```typescript
928
- * // Default initialization
929
- * const client = await PlaycademyClient.init()
914
+ * Provides different authentication strategies for the Playcademy SDK.
915
+ * Each strategy knows how to add its authentication headers to requests.
916
+ */
917
+
918
+ /**
919
+ * Base interface for authentication strategies
920
+ */
921
+ interface AuthStrategy {
922
+ /** Get the token value */
923
+ getToken(): string | null;
924
+ /** Get the token type */
925
+ getType(): TokenType;
926
+ /** Get authentication headers for a request */
927
+ getHeaders(): Record<string, string>;
928
+ }
929
+
930
+ /**
931
+ * Base Playcademy SDK client with shared infrastructure.
932
+ * Provides authentication, HTTP requests, events, connection monitoring,
933
+ * and fundamental namespaces used by all clients.
934
+ *
935
+ * Extended by PlaycademyClient (game SDK) and PlaycademyInternalClient (platform SDK).
936
+ */
937
+ declare abstract class PlaycademyBaseClient {
938
+ baseUrl: string;
939
+ gameUrl?: string;
940
+ mode: PlaycademyMode;
941
+ protected authStrategy: AuthStrategy;
942
+ protected gameId?: string;
943
+ protected config: Partial<ClientConfig>;
944
+ protected listeners: EventListeners;
945
+ protected internalClientSessionId?: string;
946
+ protected authContext?: {
947
+ isInIframe: boolean;
948
+ };
949
+ protected initPayload?: InitPayload;
950
+ protected connectionManager?: ConnectionManager;
951
+ protected launchId?: string;
952
+ /**
953
+ * Internal session manager for automatic session lifecycle.
954
+ * @private
955
+ * @internal
956
+ */
957
+ protected _sessionManager: {
958
+ startSession: (gameId: string) => Promise<{
959
+ sessionId: string;
960
+ }>;
961
+ endSession: (sessionId: string, gameId: string) => Promise<void>;
962
+ };
963
+ constructor(config?: Partial<ClientConfig>);
964
+ /**
965
+ * Gets the effective base URL for API requests.
966
+ */
967
+ getBaseUrl(): string;
968
+ /**
969
+ * Gets the effective game backend URL for integration requests.
970
+ */
971
+ protected getGameBackendUrl(): string;
972
+ /**
973
+ * Simple ping method for testing connectivity.
974
+ */
975
+ ping(): string;
976
+ /**
977
+ * Sets the authentication token for API requests.
978
+ */
979
+ setToken(token: string | null, tokenType?: TokenType): void;
980
+ setLaunchId(launchId: string | null | undefined): void;
981
+ /**
982
+ * Gets the current token type.
983
+ */
984
+ getTokenType(): TokenType;
985
+ /**
986
+ * Gets the current authentication token.
987
+ */
988
+ getToken(): string | null;
989
+ /**
990
+ * Checks if the client has a valid API token.
991
+ */
992
+ isAuthenticated(): boolean;
993
+ /**
994
+ * Registers a callback to be called when authentication state changes.
995
+ */
996
+ onAuthChange(callback: (token: string | null) => void): void;
997
+ /**
998
+ * Registers a callback to be called when connection issues are detected.
999
+ */
1000
+ onDisconnect(callback: (context: DisconnectContext) => void | Promise<void>): () => void;
1001
+ /**
1002
+ * Gets the current connection state.
1003
+ */
1004
+ getConnectionState(): ConnectionState | 'unknown';
1005
+ /**
1006
+ * Manually triggers a connection check immediately.
1007
+ */
1008
+ checkConnection(): Promise<ConnectionState | 'unknown'>;
1009
+ /**
1010
+ * Sets the authentication context for the client.
1011
+ * @internal
1012
+ */
1013
+ _setAuthContext(context: {
1014
+ isInIframe: boolean;
1015
+ }): void;
1016
+ /**
1017
+ * Registers an event listener for client events.
1018
+ *
1019
+ * @param event - The event name to listen for.
1020
+ * @param callback - The handler invoked when the event fires.
1021
+ * @returns A cleanup function that removes this specific listener.
1022
+ */
1023
+ on<E extends keyof ClientEvents>(event: E, callback: (payload: ClientEvents[E]) => void): () => void;
1024
+ /**
1025
+ * Removes a previously registered event listener.
1026
+ *
1027
+ * @param event - The event name to stop listening for.
1028
+ * @param callback - The exact function reference passed to {@link on}.
1029
+ */
1030
+ off<E extends keyof ClientEvents>(event: E, callback: (payload: ClientEvents[E]) => void): void;
1031
+ /**
1032
+ * Emits an event to all registered listeners.
1033
+ */
1034
+ protected emit<E extends keyof ClientEvents>(event: E, payload: ClientEvents[E]): void;
1035
+ /**
1036
+ * Makes an authenticated HTTP request to the platform API.
1037
+ */
1038
+ protected request<T>(path: string, method: Method, options?: {
1039
+ body?: unknown;
1040
+ headers?: Record<string, string>;
1041
+ raw?: boolean;
1042
+ retryPolicy?: RetryPolicy;
1043
+ }): Promise<T>;
1044
+ /**
1045
+ * Makes an authenticated HTTP request to the game's backend Worker.
1046
+ */
1047
+ protected requestGameBackend<T>(path: string, method: Method, body?: unknown, headers?: Record<string, string>, options?: {
1048
+ raw?: boolean;
1049
+ retryPolicy?: RetryPolicy;
1050
+ }): Promise<T>;
1051
+ /**
1052
+ * Ensures a gameId is available, throwing an error if not.
1053
+ */
1054
+ protected _ensureGameId(): string;
1055
+ /**
1056
+ * Detects and sets the authentication context (iframe vs standalone).
1057
+ */
1058
+ private _detectAuthContext;
1059
+ /**
1060
+ * Initializes connection monitoring if enabled.
1061
+ */
1062
+ private _initializeConnectionMonitor;
1063
+ private _initializeInternalSession;
1064
+ /**
1065
+ * Current user data and inventory management.
1066
+ * - `me()` - Get authenticated user profile
1067
+ * - `inventory.get()` - List user's items
1068
+ * - `inventory.add(slug, qty)` - Award items to user
1069
+ */
1070
+ users: {
1071
+ me: () => Promise<AuthenticatedUser>;
1072
+ inventory: {
1073
+ get: () => Promise<InventoryItemWithItem[]>;
1074
+ add: (identifier: string, qty: number) => Promise<InventoryMutationResponse>;
1075
+ remove: (identifier: string, qty: number) => Promise<InventoryMutationResponse>;
1076
+ quantity: (identifier: string) => Promise<number>;
1077
+ has: (identifier: string, minQuantity?: number) => Promise<boolean>;
1078
+ };
1079
+ };
1080
+ }
1081
+
1082
+ /**
1083
+ * Auto-initializes a PlaycademyClient with context from the environment.
1084
+ * Works in both iframe mode (production/development) and standalone mode (local dev).
1085
+ *
1086
+ * This is the recommended way to initialize the SDK as it automatically:
1087
+ * - Detects the runtime environment (iframe vs standalone)
1088
+ * - Configures the client with the appropriate context
1089
+ * - Sets up event listeners for token refresh
1090
+ * - Exposes the client for debugging in development mode
1091
+ *
1092
+ * @param options - Optional configuration overrides
1093
+ * @param options.baseUrl - Override the base URL for API requests
1094
+ * @returns Promise resolving to a fully initialized PlaycademyClient
1095
+ * @throws Error if not running in a browser context
1096
+ *
1097
+ * @example
1098
+ * ```typescript
1099
+ * // Default initialization
1100
+ * const client = await PlaycademyClient.init()
930
1101
  *
931
1102
  * // With custom base URL
932
1103
  * const client = await PlaycademyClient.init({ baseUrl: 'https://custom.api.com' })
@@ -1558,225 +1729,6 @@ declare class PlaycademyMessaging {
1558
1729
  */
1559
1730
  declare const messaging: PlaycademyMessaging;
1560
1731
 
1561
- /**
1562
- * @fileoverview Authentication Strategy Pattern
1563
- *
1564
- * Provides different authentication strategies for the Playcademy SDK.
1565
- * Each strategy knows how to add its authentication headers to requests.
1566
- */
1567
-
1568
- /**
1569
- * Base interface for authentication strategies
1570
- */
1571
- interface AuthStrategy {
1572
- /** Get the token value */
1573
- getToken(): string | null;
1574
- /** Get the token type */
1575
- getType(): TokenType;
1576
- /** Get authentication headers for a request */
1577
- getHeaders(): Record<string, string>;
1578
- }
1579
-
1580
- /**
1581
- * Base Playcademy SDK client with shared infrastructure.
1582
- * Provides authentication, HTTP requests, events, connection monitoring,
1583
- * and fundamental namespaces used by all clients.
1584
- *
1585
- * Extended by PlaycademyClient (game SDK) and PlaycademyInternalClient (platform SDK).
1586
- */
1587
- declare abstract class PlaycademyBaseClient {
1588
- baseUrl: string;
1589
- gameUrl?: string;
1590
- mode: PlaycademyMode;
1591
- protected authStrategy: AuthStrategy;
1592
- protected gameId?: string;
1593
- protected config: Partial<ClientConfig>;
1594
- protected listeners: EventListeners;
1595
- protected internalClientSessionId?: string;
1596
- protected authContext?: {
1597
- isInIframe: boolean;
1598
- };
1599
- protected initPayload?: InitPayload;
1600
- protected connectionManager?: ConnectionManager;
1601
- protected launchId?: string;
1602
- /**
1603
- * Internal session manager for automatic session lifecycle.
1604
- * @private
1605
- * @internal
1606
- */
1607
- protected _sessionManager: {
1608
- startSession: (gameId: string) => Promise<{
1609
- sessionId: string;
1610
- }>;
1611
- endSession: (sessionId: string, gameId: string) => Promise<void>;
1612
- };
1613
- constructor(config?: Partial<ClientConfig>);
1614
- /**
1615
- * Gets the effective base URL for API requests.
1616
- */
1617
- getBaseUrl(): string;
1618
- /**
1619
- * Gets the effective game backend URL for integration requests.
1620
- */
1621
- protected getGameBackendUrl(): string;
1622
- /**
1623
- * Simple ping method for testing connectivity.
1624
- */
1625
- ping(): string;
1626
- /**
1627
- * Sets the authentication token for API requests.
1628
- */
1629
- setToken(token: string | null, tokenType?: TokenType): void;
1630
- setLaunchId(launchId: string | null | undefined): void;
1631
- /**
1632
- * Gets the current token type.
1633
- */
1634
- getTokenType(): TokenType;
1635
- /**
1636
- * Gets the current authentication token.
1637
- */
1638
- getToken(): string | null;
1639
- /**
1640
- * Checks if the client has a valid API token.
1641
- */
1642
- isAuthenticated(): boolean;
1643
- /**
1644
- * Registers a callback to be called when authentication state changes.
1645
- */
1646
- onAuthChange(callback: (token: string | null) => void): void;
1647
- /**
1648
- * Registers a callback to be called when connection issues are detected.
1649
- */
1650
- onDisconnect(callback: (context: DisconnectContext) => void | Promise<void>): () => void;
1651
- /**
1652
- * Gets the current connection state.
1653
- */
1654
- getConnectionState(): ConnectionState | 'unknown';
1655
- /**
1656
- * Manually triggers a connection check immediately.
1657
- */
1658
- checkConnection(): Promise<ConnectionState | 'unknown'>;
1659
- /**
1660
- * Sets the authentication context for the client.
1661
- * @internal
1662
- */
1663
- _setAuthContext(context: {
1664
- isInIframe: boolean;
1665
- }): void;
1666
- /**
1667
- * Registers an event listener for client events.
1668
- *
1669
- * @param event - The event name to listen for.
1670
- * @param callback - The handler invoked when the event fires.
1671
- * @returns A cleanup function that removes this specific listener.
1672
- */
1673
- on<E extends keyof ClientEvents>(event: E, callback: (payload: ClientEvents[E]) => void): () => void;
1674
- /**
1675
- * Removes a previously registered event listener.
1676
- *
1677
- * @param event - The event name to stop listening for.
1678
- * @param callback - The exact function reference passed to {@link on}.
1679
- */
1680
- off<E extends keyof ClientEvents>(event: E, callback: (payload: ClientEvents[E]) => void): void;
1681
- /**
1682
- * Emits an event to all registered listeners.
1683
- */
1684
- protected emit<E extends keyof ClientEvents>(event: E, payload: ClientEvents[E]): void;
1685
- /**
1686
- * Makes an authenticated HTTP request to the platform API.
1687
- */
1688
- protected request<T>(path: string, method: Method, options?: {
1689
- body?: unknown;
1690
- headers?: Record<string, string>;
1691
- raw?: boolean;
1692
- retryPolicy?: RetryPolicy;
1693
- }): Promise<T>;
1694
- /**
1695
- * Makes an authenticated HTTP request to the game's backend Worker.
1696
- */
1697
- protected requestGameBackend<T>(path: string, method: Method, body?: unknown, headers?: Record<string, string>, options?: {
1698
- raw?: boolean;
1699
- retryPolicy?: RetryPolicy;
1700
- }): Promise<T>;
1701
- /**
1702
- * Ensures a gameId is available, throwing an error if not.
1703
- */
1704
- protected _ensureGameId(): string;
1705
- /**
1706
- * Detects and sets the authentication context (iframe vs standalone).
1707
- */
1708
- private _detectAuthContext;
1709
- /**
1710
- * Initializes connection monitoring if enabled.
1711
- */
1712
- private _initializeConnectionMonitor;
1713
- private _initializeInternalSession;
1714
- /**
1715
- * Current user data and inventory management.
1716
- * - `me()` - Get authenticated user profile
1717
- * - `inventory.get()` - List user's items
1718
- * - `inventory.add(slug, qty)` - Award items to user
1719
- */
1720
- users: {
1721
- me: () => Promise<AuthenticatedUser>;
1722
- inventory: {
1723
- get: () => Promise<InventoryItemWithItem[]>;
1724
- add: (identifier: string, qty: number) => Promise<InventoryMutationResponse>;
1725
- remove: (identifier: string, qty: number) => Promise<InventoryMutationResponse>;
1726
- quantity: (identifier: string) => Promise<number>;
1727
- has: (identifier: string, minQuantity?: number) => Promise<boolean>;
1728
- };
1729
- };
1730
- }
1731
-
1732
- /**
1733
- * Options for configuring activity tracking behavior.
1734
- */
1735
- interface StartActivityOptions {
1736
- /**
1737
- * How long heartbeats continue after the activity is automatically paused
1738
- * because the tab is hidden or the player is inactive while visible.
1739
- * Defaults to 10 minutes. Set to `Infinity` to keep heartbeats running
1740
- * indefinitely during automatic pauses. Invalid values fall back to the
1741
- * 10-minute default.
1742
- */
1743
- pausedHeartbeatTimeoutMs?: number;
1744
- /**
1745
- * @deprecated Use `pausedHeartbeatTimeoutMs` instead.
1746
- *
1747
- * Backward-compatible alias for callers that still use the old option
1748
- * name from earlier SDK releases.
1749
- */
1750
- hiddenTimeoutMs?: number;
1751
- /**
1752
- * How often to flush periodic heartbeats with accumulated time data.
1753
- * Defaults to 15 seconds. Set to `Infinity` to disable the interval;
1754
- * final unload/endActivity flushes still run. Values must be greater than
1755
- * 0 or `Infinity`; invalid values fall back to the 15-second default.
1756
- */
1757
- heartbeatIntervalMs?: number;
1758
- /**
1759
- * How long the tab can remain visible without keyboard or mouse activity
1760
- * before the activity is marked inactive. Defaults to 10 minutes. Set to
1761
- * `Infinity` to disable keyboard/mouse inactivity tracking. Invalid values
1762
- * fall back to the 10-minute default.
1763
- */
1764
- inactivityTimeoutMs?: number;
1765
- /**
1766
- * Stable identifier for this activity run. When provided, it is used on
1767
- * every heartbeat and on endActivity instead of a freshly-generated UUID.
1768
- *
1769
- * Pass the same `runId` across multiple `startActivity()` calls (for
1770
- * example, after the player closes and reopens a resumable activity) so
1771
- * downstream systems can correlate related sessions into a single run.
1772
- *
1773
- * Must be a UUID (the backend validates it as such) and unique per
1774
- * logical run. If omitted, the SDK generates a new UUID on each call,
1775
- * which means every session is treated as its own run.
1776
- */
1777
- runId?: string;
1778
- }
1779
-
1780
1732
  /**
1781
1733
  * Playcademy SDK client for game developers.
1782
1734
  * Provides namespaced access to platform features for games running inside Cademy.
@@ -1844,15 +1796,17 @@ declare class PlaycademyClient extends PlaycademyBaseClient {
1844
1796
  * - `user.fetch()` - Refresh user context from server
1845
1797
  *
1846
1798
  * Activity tracking:
1847
- * - `startActivity(metadata)` - Begin tracking an activity with automatic
1848
- * hidden-tab and visible-tab inactivity handling, plus configurable
1849
- * paused-heartbeat timeout behavior
1799
+ * - `currentRunId` - Current activity run ID, or undefined when inactive
1800
+ * - `startActivity(metadata)` - Begin tracking an activity, return its run
1801
+ * ID, and automatically handle hidden-tab and visible-tab inactivity
1802
+ * with configurable paused-heartbeat timeout behavior
1850
1803
  * - `pauseActivity()` / `resumeActivity()` - Pause/resume timer
1851
1804
  * - `endActivity(scoreData)` - Submit activity results to TimeBack
1852
1805
  */
1853
1806
  timeback: {
1854
1807
  readonly user: TimebackUser;
1855
- startActivity: (metadata: ActivityData, options?: StartActivityOptions) => void;
1808
+ readonly currentRunId: string | undefined;
1809
+ startActivity: (metadata: ActivityData, options?: StartActivityOptions) => StartActivityResult;
1856
1810
  pauseActivity: () => void;
1857
1811
  resumeActivity: () => void;
1858
1812
  endActivity: (data: EndActivityScoreData) => Promise<EndActivityResponse>;
@@ -1932,6 +1886,57 @@ declare class PlaycademyClient extends PlaycademyBaseClient {
1932
1886
  };
1933
1887
  }
1934
1888
 
1889
+ /**
1890
+ * Options for configuring activity tracking behavior.
1891
+ */
1892
+ interface StartActivityOptions {
1893
+ /**
1894
+ * How long heartbeats continue after the activity is automatically paused
1895
+ * because the tab is hidden or the player is inactive while visible.
1896
+ * Defaults to 10 minutes. Set to `Infinity` to keep heartbeats running
1897
+ * indefinitely during automatic pauses. Invalid values fall back to the
1898
+ * 10-minute default.
1899
+ */
1900
+ pausedHeartbeatTimeoutMs?: number;
1901
+ /**
1902
+ * @deprecated Use `pausedHeartbeatTimeoutMs` instead.
1903
+ *
1904
+ * Backward-compatible alias for callers that still use the old option
1905
+ * name from earlier SDK releases.
1906
+ */
1907
+ hiddenTimeoutMs?: number;
1908
+ /**
1909
+ * How often to flush periodic heartbeats with accumulated time data.
1910
+ * Defaults to 15 seconds. Set to `Infinity` to disable the interval;
1911
+ * final unload/endActivity flushes still run. Values must be greater than
1912
+ * 0 or `Infinity`; invalid values fall back to the 15-second default.
1913
+ */
1914
+ heartbeatIntervalMs?: number;
1915
+ /**
1916
+ * How long the tab can remain visible without keyboard or mouse activity
1917
+ * before the activity is marked inactive. Defaults to 10 minutes. Set to
1918
+ * `Infinity` to disable keyboard/mouse inactivity tracking. Invalid values
1919
+ * fall back to the 10-minute default.
1920
+ */
1921
+ inactivityTimeoutMs?: number;
1922
+ /**
1923
+ * Stable identifier for this activity run. When provided, it is used on
1924
+ * every heartbeat and on endActivity instead of a freshly-generated UUID.
1925
+ *
1926
+ * Pass the same `runId` across multiple `startActivity()` calls (for
1927
+ * example, after the player closes and reopens a resumable activity) so
1928
+ * downstream systems can correlate related sessions into a single run.
1929
+ *
1930
+ * Must be a UUID (the backend validates it as such) and unique per
1931
+ * logical run. If omitted, the SDK generates a new UUID on each call,
1932
+ * which means every session is treated as its own run.
1933
+ */
1934
+ runId?: string;
1935
+ }
1936
+ interface StartActivityResult {
1937
+ runId: string;
1938
+ }
1939
+
1935
1940
  /**
1936
1941
  * Type definitions for the game timeback namespace.
1937
1942
  *