@sudobility/sudojo_lib 0.0.84 → 0.0.87

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.
@@ -1 +1 @@
1
- {"version":3,"file":"useGamePlay.d.ts","sourceRoot":"","sources":["../../src/hooks/useGamePlay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,OAAO,KAAK,EACV,WAAW,EACX,eAAe,EACf,QAAQ,EACR,UAAU,EACX,MAAM,sBAAsB,CAAC;AAE9B,MAAM,WAAW,kBAAkB;IACjC,+CAA+C;IAC/C,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,yDAAyD;IACzD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,wCAAwC;IACxC,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;IAChC,qCAAqC;IACrC,cAAc,EAAE,OAAO,CAAC;IACxB,uBAAuB;IACvB,SAAS,EAAE,CACT,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,eAAe,KACnB,IAAI,CAAC;IACV,6CAA6C;IAC7C,cAAc,EAAE,CACd,WAAW,EAAE,MAAM,EACnB,iBAAiB,EAAE,MAAM,EACzB,YAAY,EAAE,OAAO,EACrB,eAAe,EAAE,OAAO,EACxB,WAAW,EAAE,MAAM,KAChB,IAAI,CAAC;IACV,8CAA8C;IAC9C,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,wBAAgB,WAAW,CACzB,OAAO,GAAE,kBAAuB,GAC/B,iBAAiB,CAiGnB"}
1
+ {"version":3,"file":"useGamePlay.d.ts","sourceRoot":"","sources":["../../src/hooks/useGamePlay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,OAAO,KAAK,EACV,WAAW,EACX,eAAe,EACf,QAAQ,EACR,UAAU,EACX,MAAM,sBAAsB,CAAC;AAE9B,MAAM,WAAW,kBAAkB;IACjC,+CAA+C;IAC/C,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,yDAAyD;IACzD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,wCAAwC;IACxC,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;IAChC,qCAAqC;IACrC,cAAc,EAAE,OAAO,CAAC;IACxB,uBAAuB;IACvB,SAAS,EAAE,CACT,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,eAAe,KACnB,IAAI,CAAC;IACV,6CAA6C;IAC7C,cAAc,EAAE,CACd,WAAW,EAAE,MAAM,EACnB,iBAAiB,EAAE,MAAM,EACzB,YAAY,EAAE,OAAO,EACrB,eAAe,EAAE,OAAO,EACxB,WAAW,EAAE,MAAM,KAChB,IAAI,CAAC;IACV,8CAA8C;IAC9C,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,wBAAgB,WAAW,CACzB,OAAO,GAAE,kBAAuB,GAC/B,iBAAiB,CA2GnB"}
@@ -75,9 +75,15 @@ export function useGamePlay(options = {}) {
75
75
  pendingUpdate.current = null;
76
76
  }
77
77
  };
78
- window.addEventListener('beforeunload', flush);
78
+ if (typeof window !== 'undefined' &&
79
+ typeof window.addEventListener === 'function') {
80
+ window.addEventListener('beforeunload', flush);
81
+ }
79
82
  return () => {
80
- window.removeEventListener('beforeunload', flush);
83
+ if (typeof window !== 'undefined' &&
84
+ typeof window.removeEventListener === 'function') {
85
+ window.removeEventListener('beforeunload', flush);
86
+ }
81
87
  flush();
82
88
  };
83
89
  }, [slot]);
@@ -1 +1 @@
1
- {"version":3,"file":"useGamePlay.js","sourceRoot":"","sources":["../../src/hooks/useGamePlay.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Hook for managing the current game\n *\n * Provides a clean API for starting, updating, and clearing the current game.\n * The `slot` option selects which independent store slot to use ('daily' or 'play'),\n * keeping the returned function signatures identical to before.\n *\n * @example\n * ```tsx\n * const { currentGame, startGame, updateProgress, clearGame } = useGamePlay({ slot: 'daily' });\n *\n * // Start a new game\n * startGame('daily', puzzle, solution, { dailyDate: '2024-01-15' });\n *\n * // Update progress (debounced)\n * updateProgress(inputString, pencilmarksString, isPencilMode, autoPencilmarks, elapsedTime);\n *\n * // Clear on completion\n * clearGame();\n * ```\n */\n\nimport { useCallback, useEffect, useRef } from 'react';\nimport { useGamePlayStore } from '../stores/gamePlayStore';\nimport type {\n CurrentGame,\n CurrentGameMeta,\n GameSlot,\n GameSource,\n} from '../types/currentGame';\n\nexport interface UseGamePlayOptions {\n /** Which game slot to use (default: 'play') */\n slot?: GameSlot;\n /** Debounce delay for auto-save in ms (default: 2000) */\n autoSaveDelay?: number;\n}\n\nexport interface UseGamePlayResult {\n /** Current game state (null if none) */\n currentGame: CurrentGame | null;\n /** Whether there's an active game */\n hasCurrentGame: boolean;\n /** Start a new game */\n startGame: (\n source: GameSource,\n puzzle: string,\n solution: string,\n meta?: CurrentGameMeta\n ) => void;\n /** Update progress (debounced internally) */\n updateProgress: (\n inputString: string,\n pencilmarksString: string,\n isPencilMode: boolean,\n autoPencilmarks: boolean,\n elapsedTime: number\n ) => void;\n /** Clear current game (call on completion) */\n clearGame: () => void;\n}\n\nexport function useGamePlay(\n options: UseGamePlayOptions = {}\n): UseGamePlayResult {\n const { slot = 'play', autoSaveDelay = 2000 } = options;\n\n // Select the correct slot from the store\n const currentGame = useGamePlayStore(s =>\n slot === 'daily' ? s.dailyGame : s.playGame\n );\n\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const pendingUpdate = useRef<\n [string, string, boolean, boolean, number] | null\n >(null);\n\n // Curry slot into startGame so callers keep the same signature\n const startGame = useCallback(\n (\n source: GameSource,\n puzzle: string,\n solution: string,\n meta?: CurrentGameMeta\n ) => {\n useGamePlayStore\n .getState()\n .startGame(slot, source, puzzle, solution, meta);\n },\n [slot]\n );\n\n // Curry slot into clearGame\n const clearGame = useCallback(() => {\n useGamePlayStore.getState().clearGame(slot);\n }, [slot]);\n\n // Throttled update — saves at most every autoSaveDelay ms.\n // Unlike debounce, throttle ensures periodic saves even when called\n // continuously (e.g. every second from the game timer).\n const updateProgress = useCallback(\n (\n inputString: string,\n pencilmarksString: string,\n isPencilMode: boolean,\n autoPencilmarks: boolean,\n elapsedTime: number\n ) => {\n pendingUpdate.current = [\n inputString,\n pencilmarksString,\n isPencilMode,\n autoPencilmarks,\n elapsedTime,\n ];\n\n // Only start a new timer if one isn't already running (throttle)\n if (!timeoutRef.current) {\n timeoutRef.current = setTimeout(() => {\n if (pendingUpdate.current) {\n useGamePlayStore\n .getState()\n .updateProgress(slot, ...pendingUpdate.current);\n pendingUpdate.current = null;\n }\n timeoutRef.current = null;\n }, autoSaveDelay);\n }\n },\n [slot, autoSaveDelay]\n );\n\n // Flush pending updates on unmount and beforeunload\n useEffect(() => {\n const flush = () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n if (pendingUpdate.current) {\n useGamePlayStore\n .getState()\n .updateProgress(slot, ...pendingUpdate.current);\n pendingUpdate.current = null;\n }\n };\n\n window.addEventListener('beforeunload', flush);\n return () => {\n window.removeEventListener('beforeunload', flush);\n flush();\n };\n }, [slot]);\n\n return {\n currentGame,\n hasCurrentGame: currentGame !== null,\n startGame,\n updateProgress,\n clearGame,\n };\n}\n"]}
1
+ {"version":3,"file":"useGamePlay.js","sourceRoot":"","sources":["../../src/hooks/useGamePlay.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Hook for managing the current game\n *\n * Provides a clean API for starting, updating, and clearing the current game.\n * The `slot` option selects which independent store slot to use ('daily' or 'play'),\n * keeping the returned function signatures identical to before.\n *\n * @example\n * ```tsx\n * const { currentGame, startGame, updateProgress, clearGame } = useGamePlay({ slot: 'daily' });\n *\n * // Start a new game\n * startGame('daily', puzzle, solution, { dailyDate: '2024-01-15' });\n *\n * // Update progress (debounced)\n * updateProgress(inputString, pencilmarksString, isPencilMode, autoPencilmarks, elapsedTime);\n *\n * // Clear on completion\n * clearGame();\n * ```\n */\n\nimport { useCallback, useEffect, useRef } from 'react';\nimport { useGamePlayStore } from '../stores/gamePlayStore';\nimport type {\n CurrentGame,\n CurrentGameMeta,\n GameSlot,\n GameSource,\n} from '../types/currentGame';\n\nexport interface UseGamePlayOptions {\n /** Which game slot to use (default: 'play') */\n slot?: GameSlot;\n /** Debounce delay for auto-save in ms (default: 2000) */\n autoSaveDelay?: number;\n}\n\nexport interface UseGamePlayResult {\n /** Current game state (null if none) */\n currentGame: CurrentGame | null;\n /** Whether there's an active game */\n hasCurrentGame: boolean;\n /** Start a new game */\n startGame: (\n source: GameSource,\n puzzle: string,\n solution: string,\n meta?: CurrentGameMeta\n ) => void;\n /** Update progress (debounced internally) */\n updateProgress: (\n inputString: string,\n pencilmarksString: string,\n isPencilMode: boolean,\n autoPencilmarks: boolean,\n elapsedTime: number\n ) => void;\n /** Clear current game (call on completion) */\n clearGame: () => void;\n}\n\nexport function useGamePlay(\n options: UseGamePlayOptions = {}\n): UseGamePlayResult {\n const { slot = 'play', autoSaveDelay = 2000 } = options;\n\n // Select the correct slot from the store\n const currentGame = useGamePlayStore(s =>\n slot === 'daily' ? s.dailyGame : s.playGame\n );\n\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const pendingUpdate = useRef<\n [string, string, boolean, boolean, number] | null\n >(null);\n\n // Curry slot into startGame so callers keep the same signature\n const startGame = useCallback(\n (\n source: GameSource,\n puzzle: string,\n solution: string,\n meta?: CurrentGameMeta\n ) => {\n useGamePlayStore\n .getState()\n .startGame(slot, source, puzzle, solution, meta);\n },\n [slot]\n );\n\n // Curry slot into clearGame\n const clearGame = useCallback(() => {\n useGamePlayStore.getState().clearGame(slot);\n }, [slot]);\n\n // Throttled update — saves at most every autoSaveDelay ms.\n // Unlike debounce, throttle ensures periodic saves even when called\n // continuously (e.g. every second from the game timer).\n const updateProgress = useCallback(\n (\n inputString: string,\n pencilmarksString: string,\n isPencilMode: boolean,\n autoPencilmarks: boolean,\n elapsedTime: number\n ) => {\n pendingUpdate.current = [\n inputString,\n pencilmarksString,\n isPencilMode,\n autoPencilmarks,\n elapsedTime,\n ];\n\n // Only start a new timer if one isn't already running (throttle)\n if (!timeoutRef.current) {\n timeoutRef.current = setTimeout(() => {\n if (pendingUpdate.current) {\n useGamePlayStore\n .getState()\n .updateProgress(slot, ...pendingUpdate.current);\n pendingUpdate.current = null;\n }\n timeoutRef.current = null;\n }, autoSaveDelay);\n }\n },\n [slot, autoSaveDelay]\n );\n\n // Flush pending updates on unmount and beforeunload\n useEffect(() => {\n const flush = () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n if (pendingUpdate.current) {\n useGamePlayStore\n .getState()\n .updateProgress(slot, ...pendingUpdate.current);\n pendingUpdate.current = null;\n }\n };\n\n if (\n typeof window !== 'undefined' &&\n typeof window.addEventListener === 'function'\n ) {\n window.addEventListener('beforeunload', flush);\n }\n return () => {\n if (\n typeof window !== 'undefined' &&\n typeof window.removeEventListener === 'function'\n ) {\n window.removeEventListener('beforeunload', flush);\n }\n flush();\n };\n }, [slot]);\n\n return {\n currentGame,\n hasCurrentGame: currentGame !== null,\n startGame,\n updateProgress,\n clearGame,\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"useGameSession.d.ts","sourceRoot":"","sources":["../../src/hooks/useGameSession.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH,OAAO,KAAK,EACV,kBAAkB,EAClB,gBAAgB,EACjB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,MAAM,WAAW,qBAAqB;IACpC,aAAa,EAAE,aAAa,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,2CAA2C;IAC3C,cAAc,EAAE,OAAO,CAAC;IACxB,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,wCAAwC;IACxC,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,kCAAkC;IAClC,SAAS,EAAE,OAAO,CAAC;IACnB,wBAAwB;IACxB,SAAS,EAAE,KAAK,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;KAC5B,CAAC,CAAC;IACH,0BAA0B;IAC1B,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAgB,cAAc,CAAC,EAC7B,aAAa,EACb,OAAO,EACP,KAAK,EACL,MAAM,GACP,EAAE,qBAAqB;yBAaP,gBAAgB,KAAG,OAAO,CAAC,iBAAiB,CAAC;iCAqCtC,MAAM,KAAG,OAAO,CAAC,gBAAgB,CAAC;;;;;EA4DzD"}
1
+ {"version":3,"file":"useGameSession.d.ts","sourceRoot":"","sources":["../../src/hooks/useGameSession.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH,OAAO,KAAK,EACV,kBAAkB,EAClB,gBAAgB,EACjB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,MAAM,WAAW,qBAAqB;IACpC,aAAa,EAAE,aAAa,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,2CAA2C;IAC3C,cAAc,EAAE,OAAO,CAAC;IACxB,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,wCAAwC;IACxC,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,kCAAkC;IAClC,SAAS,EAAE,OAAO,CAAC;IACnB,wBAAwB;IACxB,SAAS,EAAE,KAAK,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;KAC5B,CAAC,CAAC;IACH,0BAA0B;IAC1B,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAgB,cAAc,CAAC,EAC7B,aAAa,EACb,OAAO,EACP,KAAK,EACL,MAAM,GACP,EAAE,qBAAqB;yBAyBP,gBAAgB,KAAG,OAAO,CAAC,iBAAiB,CAAC;iCAqCtC,MAAM,KAAG,OAAO,CAAC,gBAAgB,CAAC;;;;;EA4DzD"}
@@ -6,7 +6,7 @@
6
6
  * - Finishing game sessions and receiving rewards
7
7
  * - Tracking achievement results for display
8
8
  */
9
- import { useCallback, useRef, useState } from 'react';
9
+ import { useCallback, useEffect, useRef, useState } from 'react';
10
10
  import { useSudojoPlayFinish, useSudojoPlayStart, } from '@sudobility/sudojo_client';
11
11
  export function useGameSession({ networkClient, baseUrl, token, userId, }) {
12
12
  const [isStarting, setIsStarting] = useState(false);
@@ -14,19 +14,30 @@ export function useGameSession({ networkClient, baseUrl, token, userId, }) {
14
14
  const sessionIdRef = useRef(null);
15
15
  const playStartMutation = useSudojoPlayStart(networkClient, baseUrl);
16
16
  const playFinishMutation = useSudojoPlayFinish(networkClient, baseUrl);
17
+ // Use refs for mutations so callbacks have stable identity
18
+ const playStartRef = useRef(playStartMutation);
19
+ const playFinishRef = useRef(playFinishMutation);
20
+ const tokenRef = useRef(token);
21
+ const userIdRef = useRef(userId);
22
+ useEffect(() => {
23
+ playStartRef.current = playStartMutation;
24
+ playFinishRef.current = playFinishMutation;
25
+ tokenRef.current = token;
26
+ userIdRef.current = userId;
27
+ });
17
28
  /**
18
29
  * Start a game session on the server
19
30
  * Only works if user is authenticated
20
31
  */
21
32
  const startSession = useCallback(async (data) => {
22
33
  // Skip if not authenticated
23
- if (!userId || !token) {
34
+ if (!userIdRef.current || !tokenRef.current) {
24
35
  return { sessionStarted: false };
25
36
  }
26
37
  setIsStarting(true);
27
38
  try {
28
- const response = await playStartMutation.mutateAsync({
29
- token,
39
+ const response = await playStartRef.current.mutateAsync({
40
+ token: tokenRef.current,
30
41
  data,
31
42
  });
32
43
  if (response.success && response.data) {
@@ -45,14 +56,14 @@ export function useGameSession({ networkClient, baseUrl, token, userId, }) {
45
56
  finally {
46
57
  setIsStarting(false);
47
58
  }
48
- }, [userId, token, playStartMutation]);
59
+ }, []);
49
60
  /**
50
61
  * Finish the game session and get rewards
51
62
  * Only works if user is authenticated and session was started
52
63
  */
53
64
  const finishSession = useCallback(async (elapsedTime) => {
54
65
  // Skip if not authenticated
55
- if (!userId || !token) {
66
+ if (!userIdRef.current || !tokenRef.current) {
56
67
  return {
57
68
  leveledUp: false,
58
69
  newBadges: [],
@@ -61,8 +72,8 @@ export function useGameSession({ networkClient, baseUrl, token, userId, }) {
61
72
  }
62
73
  setIsFinishing(true);
63
74
  try {
64
- const response = await playFinishMutation.mutateAsync({
65
- token,
75
+ const response = await playFinishRef.current.mutateAsync({
76
+ token: tokenRef.current,
66
77
  data: { elapsedTime },
67
78
  });
68
79
  sessionIdRef.current = null;
@@ -91,7 +102,7 @@ export function useGameSession({ networkClient, baseUrl, token, userId, }) {
91
102
  finally {
92
103
  setIsFinishing(false);
93
104
  }
94
- }, [userId, token, playFinishMutation]);
105
+ }, []);
95
106
  /**
96
107
  * Check if user is authenticated (can use gamification)
97
108
  */
@@ -1 +1 @@
1
- {"version":3,"file":"useGameSession.js","sourceRoot":"","sources":["../../src/hooks/useGameSession.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Hook for managing game sessions with gamification\n *\n * Handles:\n * - Starting game sessions on the server\n * - Finishing game sessions and receiving rewards\n * - Tracking achievement results for display\n */\n\nimport { useCallback, useRef, useState } from 'react';\nimport {\n useSudojoPlayFinish,\n useSudojoPlayStart,\n} from '@sudobility/sudojo_client';\nimport type {\n GameFinishResponse,\n GameStartRequest,\n} from '@sudobility/sudojo_types';\nimport type { NetworkClient } from '@sudobility/types';\n\nexport interface UseGameSessionOptions {\n networkClient: NetworkClient;\n baseUrl: string;\n token: string | null;\n userId: string | null;\n}\n\nexport interface GameSessionResult {\n /** Whether a server session was started */\n sessionStarted: boolean;\n /** Session ID from server (if started) */\n sessionId?: string;\n}\n\nexport interface GameFinishResult {\n /** The full response from the server */\n response?: GameFinishResponse;\n /** Whether the user leveled up */\n leveledUp: boolean;\n /** New badges earned */\n newBadges: Array<{\n badgeKey: string;\n title: string;\n description: string | null;\n }>;\n /** Total points earned */\n totalPointsEarned: number;\n}\n\nexport function useGameSession({\n networkClient,\n baseUrl,\n token,\n userId,\n}: UseGameSessionOptions) {\n const [isStarting, setIsStarting] = useState(false);\n const [isFinishing, setIsFinishing] = useState(false);\n const sessionIdRef = useRef<string | null>(null);\n\n const playStartMutation = useSudojoPlayStart(networkClient, baseUrl);\n const playFinishMutation = useSudojoPlayFinish(networkClient, baseUrl);\n\n /**\n * Start a game session on the server\n * Only works if user is authenticated\n */\n const startSession = useCallback(\n async (data: GameStartRequest): Promise<GameSessionResult> => {\n // Skip if not authenticated\n if (!userId || !token) {\n return { sessionStarted: false };\n }\n\n setIsStarting(true);\n try {\n const response = await playStartMutation.mutateAsync({\n token,\n data,\n });\n\n if (response.success && response.data) {\n sessionIdRef.current = response.data.sessionId;\n return {\n sessionStarted: true,\n sessionId: response.data.sessionId,\n };\n }\n\n return { sessionStarted: false };\n } catch (error) {\n console.error('Failed to start game session:', error);\n return { sessionStarted: false };\n } finally {\n setIsStarting(false);\n }\n },\n [userId, token, playStartMutation]\n );\n\n /**\n * Finish the game session and get rewards\n * Only works if user is authenticated and session was started\n */\n const finishSession = useCallback(\n async (elapsedTime: number): Promise<GameFinishResult> => {\n // Skip if not authenticated\n if (!userId || !token) {\n return {\n leveledUp: false,\n newBadges: [],\n totalPointsEarned: 0,\n };\n }\n\n setIsFinishing(true);\n try {\n const response = await playFinishMutation.mutateAsync({\n token,\n data: { elapsedTime },\n });\n\n sessionIdRef.current = null;\n\n if (response.success && response.data) {\n return {\n response: response.data,\n leveledUp: !!response.data.level,\n newBadges: response.data.badges ?? [],\n totalPointsEarned: response.data.points.totalPoints,\n };\n }\n\n return {\n leveledUp: false,\n newBadges: [],\n totalPointsEarned: 0,\n };\n } catch (error) {\n console.error('Failed to finish game session:', error);\n return {\n leveledUp: false,\n newBadges: [],\n totalPointsEarned: 0,\n };\n } finally {\n setIsFinishing(false);\n }\n },\n [userId, token, playFinishMutation]\n );\n\n /**\n * Check if user is authenticated (can use gamification)\n */\n const isAuthenticated = !!userId && !!token;\n\n return {\n startSession,\n finishSession,\n isStarting,\n isFinishing,\n isAuthenticated,\n sessionId: sessionIdRef.current,\n };\n}\n"]}
1
+ {"version":3,"file":"useGameSession.js","sourceRoot":"","sources":["../../src/hooks/useGameSession.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Hook for managing game sessions with gamification\n *\n * Handles:\n * - Starting game sessions on the server\n * - Finishing game sessions and receiving rewards\n * - Tracking achievement results for display\n */\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport {\n useSudojoPlayFinish,\n useSudojoPlayStart,\n} from '@sudobility/sudojo_client';\nimport type {\n GameFinishResponse,\n GameStartRequest,\n} from '@sudobility/sudojo_types';\nimport type { NetworkClient } from '@sudobility/types';\n\nexport interface UseGameSessionOptions {\n networkClient: NetworkClient;\n baseUrl: string;\n token: string | null;\n userId: string | null;\n}\n\nexport interface GameSessionResult {\n /** Whether a server session was started */\n sessionStarted: boolean;\n /** Session ID from server (if started) */\n sessionId?: string;\n}\n\nexport interface GameFinishResult {\n /** The full response from the server */\n response?: GameFinishResponse;\n /** Whether the user leveled up */\n leveledUp: boolean;\n /** New badges earned */\n newBadges: Array<{\n badgeKey: string;\n title: string;\n description: string | null;\n }>;\n /** Total points earned */\n totalPointsEarned: number;\n}\n\nexport function useGameSession({\n networkClient,\n baseUrl,\n token,\n userId,\n}: UseGameSessionOptions) {\n const [isStarting, setIsStarting] = useState(false);\n const [isFinishing, setIsFinishing] = useState(false);\n const sessionIdRef = useRef<string | null>(null);\n\n const playStartMutation = useSudojoPlayStart(networkClient, baseUrl);\n const playFinishMutation = useSudojoPlayFinish(networkClient, baseUrl);\n\n // Use refs for mutations so callbacks have stable identity\n const playStartRef = useRef(playStartMutation);\n const playFinishRef = useRef(playFinishMutation);\n const tokenRef = useRef(token);\n const userIdRef = useRef(userId);\n useEffect(() => {\n playStartRef.current = playStartMutation;\n playFinishRef.current = playFinishMutation;\n tokenRef.current = token;\n userIdRef.current = userId;\n });\n\n /**\n * Start a game session on the server\n * Only works if user is authenticated\n */\n const startSession = useCallback(\n async (data: GameStartRequest): Promise<GameSessionResult> => {\n // Skip if not authenticated\n if (!userIdRef.current || !tokenRef.current) {\n return { sessionStarted: false };\n }\n\n setIsStarting(true);\n try {\n const response = await playStartRef.current.mutateAsync({\n token: tokenRef.current,\n data,\n });\n\n if (response.success && response.data) {\n sessionIdRef.current = response.data.sessionId;\n return {\n sessionStarted: true,\n sessionId: response.data.sessionId,\n };\n }\n\n return { sessionStarted: false };\n } catch (error) {\n console.error('Failed to start game session:', error);\n return { sessionStarted: false };\n } finally {\n setIsStarting(false);\n }\n },\n []\n );\n\n /**\n * Finish the game session and get rewards\n * Only works if user is authenticated and session was started\n */\n const finishSession = useCallback(\n async (elapsedTime: number): Promise<GameFinishResult> => {\n // Skip if not authenticated\n if (!userIdRef.current || !tokenRef.current) {\n return {\n leveledUp: false,\n newBadges: [],\n totalPointsEarned: 0,\n };\n }\n\n setIsFinishing(true);\n try {\n const response = await playFinishRef.current.mutateAsync({\n token: tokenRef.current,\n data: { elapsedTime },\n });\n\n sessionIdRef.current = null;\n\n if (response.success && response.data) {\n return {\n response: response.data,\n leveledUp: !!response.data.level,\n newBadges: response.data.badges ?? [],\n totalPointsEarned: response.data.points.totalPoints,\n };\n }\n\n return {\n leveledUp: false,\n newBadges: [],\n totalPointsEarned: 0,\n };\n } catch (error) {\n console.error('Failed to finish game session:', error);\n return {\n leveledUp: false,\n newBadges: [],\n totalPointsEarned: 0,\n };\n } finally {\n setIsFinishing(false);\n }\n },\n []\n );\n\n /**\n * Check if user is authenticated (can use gamification)\n */\n const isAuthenticated = !!userId && !!token;\n\n return {\n startSession,\n finishSession,\n isStarting,\n isFinishing,\n isAuthenticated,\n sessionId: sessionIdRef.current,\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sudobility/sudojo_lib",
3
- "version": "0.0.84",
3
+ "version": "0.0.87",
4
4
  "description": "Business logic library for Sudojo app",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",