Skip to content

Commit 9216c01

Browse files
authored
Merge pull request #6810 from FlowFuse/expert/fix-website-context-switching-hydration-race-condition
Fix context hydration race condition when ingesting expert context from the website
2 parents 4b1e342 + f22d388 commit 9216c01

3 files changed

Lines changed: 63 additions & 2 deletions

File tree

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
export default function useTimerHelper () {
2+
/**
3+
* Asynchronously waits while a condition remains true, checking at regular intervals.
4+
*
5+
* @param {boolean|Function} condition - A boolean value or a function that returns a boolean.
6+
* The waiting continues while this evaluates to true.
7+
* @param {Object} options - Configuration options for the waiting behavior.
8+
* @param {number} [options.cutoffTries=Infinity] - Maximum number of attempts before throwing an error.
9+
* Must be a non-negative finite number.
10+
* @param {number} [options.intervalMs=1000] - Time in milliseconds to wait between condition checks.
11+
* Must be a non-negative finite number.
12+
* @returns {Promise<void>} Resolves when the condition becomes false.
13+
* @throws {Error} If intervalMs or cutoffTries are invalid (negative or non-finite).
14+
* @throws {Error} If the maximum number of tries (cutoffTries) is reached before condition becomes false.
15+
*/
16+
async function waitWhile (condition, { cutoffTries = Infinity, intervalMs = 1000 } = {}) {
17+
const predicate = (typeof condition === 'function')
18+
? condition
19+
: () => Boolean(condition)
20+
21+
const delayMs = Number(intervalMs)
22+
if (!Number.isFinite(delayMs) || delayMs < 0) {
23+
throw new Error(`waitWhile: intervalMs must be a non-negative number, got ${intervalMs}`)
24+
}
25+
26+
const maxTries = Number(cutoffTries)
27+
if (!Number.isFinite(maxTries) || maxTries < 0) {
28+
throw new Error(`waitWhile: cutoffTries must be a non-negative number, got ${cutoffTries}`)
29+
}
30+
31+
let tries = 0
32+
while (predicate()) {
33+
if (tries >= maxTries) {
34+
throw new Error(`waitWhile: cutoffTries (${maxTries}) reached`)
35+
}
36+
tries++
37+
await new Promise(resolve => setTimeout(resolve, delayMs))
38+
}
39+
}
40+
41+
return {
42+
waitWhile
43+
}
44+
}

frontend/src/store/modules/product/expert/index.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { markRaw } from 'vue'
33

44
import expertApi from '../../../../api/expert.js'
55
import ExpertDrawer from '../../../../components/drawers/expert/ExpertDrawer.vue'
6+
import useTimerHelper from '../../../../composables/TimersHelper.js'
67

78
import { FF_AGENT, OPERATOR_AGENT } from './agents.js'
89

@@ -243,7 +244,7 @@ const actions = {
243244
}
244245
},
245246

246-
hydrateClient ({
247+
async hydrateClient ({
247248
dispatch,
248249
state,
249250
rootGetters
@@ -255,10 +256,18 @@ const actions = {
255256
return Promise.resolve()
256257
}
257258

259+
// TODO: this need to be removed when we have https://github.com/FlowFuse/flowfuse/issues/6520 part of
260+
// https://github.com/FlowFuse/flowfuse/issues/6519 as it's a hacky workaround to the expert drawer opening up
261+
// before we have a team loaded
262+
const { waitWhile } = useTimerHelper()
263+
await waitWhile(() => !rootGetters['account/team'], { cutoffTries: 60 })
264+
258265
return expertApi
259266
.chat({
260267
history: state[state.agentMode].context,
261-
context: {},
268+
context: {
269+
teamId: rootGetters['account/team'].id
270+
},
262271
sessionId: state[state.agentMode].sessionId
263272
})
264273
.then((response) => {

frontend/src/store/modules/product/expert/operator-agent/index.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import expertApi from '../../../../../api/expert.js'
2+
import useTimerHelper from '../../../../../composables/TimersHelper.js'
23

34
const initialState = () => ({
45
sessionId: null,
@@ -55,11 +56,18 @@ const actions = {
5556
commit('SET_SELECTED_CAPABILITIES', selectedCapabilities)
5657
},
5758
async getCapabilities ({ commit, rootGetters, state }) {
59+
// TODO: this need to be removed when we have https://github.com/FlowFuse/flowfuse/issues/6520 part of
60+
// https://github.com/FlowFuse/flowfuse/issues/6519 as it's a hacky workaround to the expert drawer opening up
61+
// before we have a team loaded
62+
const { waitWhile } = useTimerHelper()
63+
await waitWhile(() => !rootGetters['account/team'], { cutoffTries: 60 })
64+
5865
const payload = {
5966
context: {
6067
teamId: rootGetters['account/team'].id
6168
}
6269
}
70+
6371
return expertApi.getCapabilities(payload)
6472
.then(data => {
6573
commit('SET_CAPABILITIES', data.servers || [])

0 commit comments

Comments
 (0)