-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Fix] Refactor to playstreak sync state (#19)
* refactor to playstreak sync state, add final sync * fix lint * export state * pass in query client * run pnpm i * refactor to shared fxn for get query keys * export query key fxns * tech(quest-state): add appQueryClient (#21) * tech(quest-state): add appQueryClient * tech(quest-state): make appQueryClient optional * pretty --------- Co-authored-by: Elio Briceño <[email protected]>
- Loading branch information
1 parent
9c325d9
commit 648cf4c
Showing
10 changed files
with
1,952 additions
and
1,579 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
export function getGetQuestQueryKey(questId: number | null) { | ||
return ['getQuest', questId] | ||
} | ||
|
||
export function getGetUserPlayStreakQueryKey(questId: number | null) { | ||
return ['getUserPlayStreak', questId] | ||
} | ||
|
||
export function getSyncPlaysessionQueryKey(projectId: string) { | ||
return ['syncPlaysession', projectId] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
import { Quest, Runner, UserPlayStreak } from '@hyperplay/utils' | ||
import { makeAutoObservable } from 'mobx' | ||
import { QueryClient } from '@tanstack/query-core' | ||
import { resetSessionStartedTime } from '@/helpers/getPlaystreakArgsFromQuestData' | ||
import { | ||
getGetQuestQueryKey, | ||
getGetUserPlayStreakQueryKey, | ||
getSyncPlaysessionQueryKey | ||
} from '@/helpers/getQueryKeys' | ||
|
||
class QuestPlayStreakSyncState { | ||
// @ts-expect-error not assigned in constructor since this is a singleton | ||
getQuests: (projectId?: string | undefined) => Promise<Quest[]> | ||
// @ts-expect-error not assigned in constructor since this is a singleton | ||
getQuest: (questId: number) => Promise<Quest> | ||
// @ts-expect-error not assigned in constructor since this is a singleton | ||
getUserPlayStreak: (questId: number) => Promise<UserPlayStreak> | ||
// @ts-expect-error not assigned in constructor since this is a singleton | ||
syncPlaySession: (appName: string, runner: Runner) => Promise<void> | ||
|
||
appQueryClient?: QueryClient | ||
|
||
projectSyncData: Record< | ||
string, | ||
{ | ||
syncTimers: NodeJS.Timeout[] | ||
intervalTimers: NodeJS.Timer[] | ||
} | ||
> = {} | ||
|
||
queryClient: QueryClient | undefined = undefined | ||
|
||
intervalSyncTick = 60000 | ||
|
||
constructor() { | ||
this.queryClient = new QueryClient({ | ||
defaultOptions: { | ||
queries: { | ||
staleTime: 1000 * 60 * 5, // Cache the data for 5 minutes | ||
retry: 1 // Retry failed request once | ||
} | ||
} | ||
}) | ||
makeAutoObservable(this) | ||
} | ||
|
||
init({ | ||
getQuests, | ||
getQuest, | ||
getUserPlayStreak, | ||
syncPlaySession, | ||
appQueryClient | ||
}: { | ||
getQuests: (projectId?: string | undefined) => Promise<Quest[]> | ||
getQuest: (questId: number) => Promise<Quest> | ||
getUserPlayStreak: (questId: number) => Promise<UserPlayStreak> | ||
syncPlaySession: (appName: string, runner: Runner) => Promise<void> | ||
appQueryClient?: QueryClient | ||
}) { | ||
this.getQuests = getQuests | ||
this.getQuest = getQuest | ||
this.getUserPlayStreak = getUserPlayStreak | ||
this.syncPlaySession = syncPlaySession | ||
this.appQueryClient = appQueryClient | ||
} | ||
|
||
async keepProjectQuestsInSync(projectId: string) { | ||
if (this.queryClient === undefined) { | ||
throw 'must call init on QuestPlayStreakSyncState first' | ||
} | ||
const quests = await this.getQuests(projectId) | ||
for (const quest of quests) { | ||
try { | ||
// get quest | ||
const questMeta = await this.queryClient.fetchQuery({ | ||
queryKey: getGetQuestQueryKey(quest.id), | ||
queryFn: async () => this.getQuest(quest.id) | ||
}) | ||
// get user playstreak | ||
const userPlayStreakData = await this.queryClient.fetchQuery({ | ||
queryKey: getGetUserPlayStreakQueryKey(quest.id), | ||
queryFn: async () => this.getUserPlayStreak(quest.id) | ||
}) | ||
|
||
if (!Object.hasOwn(this.projectSyncData, projectId)) { | ||
this.projectSyncData[projectId] = { | ||
syncTimers: [], | ||
intervalTimers: [] | ||
} | ||
} | ||
|
||
const syncThisProjectMutation = async () => | ||
this.queryClient?.fetchQuery({ | ||
queryKey: getSyncPlaysessionQueryKey(projectId), | ||
queryFn: async () => { | ||
this.syncPlaySession( | ||
projectId, | ||
questMeta.quest_external_game?.runner ?? 'hyperplay' | ||
) | ||
resetSessionStartedTime() | ||
// all quest user playstreak data needs to be refetched after playsession sync | ||
for (const questToInvalidate of quests) { | ||
const queryKey = getGetUserPlayStreakQueryKey( | ||
questToInvalidate.id | ||
) | ||
this.appQueryClient?.invalidateQueries({ queryKey }) | ||
} | ||
} | ||
}) | ||
|
||
// set timeout for when we meet the min time | ||
const currentPlayTimeInSeconds = | ||
userPlayStreakData.accumulated_playtime_today_in_seconds | ||
const minimumRequiredPlayTimeInSeconds = | ||
questMeta?.eligibility?.play_streak.minimum_session_time_in_seconds | ||
if ( | ||
minimumRequiredPlayTimeInSeconds && | ||
currentPlayTimeInSeconds && | ||
currentPlayTimeInSeconds < minimumRequiredPlayTimeInSeconds | ||
) { | ||
console.log('setting timeout for post mutation') | ||
const finalSyncTimer = setTimeout( | ||
syncThisProjectMutation, | ||
minimumRequiredPlayTimeInSeconds - currentPlayTimeInSeconds | ||
) | ||
this.projectSyncData[projectId].syncTimers.push(finalSyncTimer) | ||
} | ||
|
||
const intervalId = setInterval( | ||
syncThisProjectMutation, | ||
this.intervalSyncTick | ||
) | ||
this.projectSyncData[projectId].syncTimers.push(intervalId) | ||
} catch (err) { | ||
console.error(`Error while setting up playstreak sync: ${err}`) | ||
} | ||
} | ||
} | ||
|
||
clearAllTimers() { | ||
for (const projectId of Object.keys(this.projectSyncData)) { | ||
for (const syncTimer of this.projectSyncData[projectId].syncTimers) { | ||
clearTimeout(syncTimer) | ||
} | ||
|
||
for (const intervalTimer of this.projectSyncData[projectId] | ||
.intervalTimers) { | ||
clearInterval(intervalTimer) | ||
} | ||
} | ||
} | ||
} | ||
|
||
export default new QuestPlayStreakSyncState() |