app-tutor-ai-consumer 1.32.1 → 1.32.3

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/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## [1.32.3](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.32.2...v1.32.3) (2025-10-13)
2
+
3
+ ## [1.32.2](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.32.1...v1.32.2) (2025-10-08)
4
+
5
+ ### Bug Fixes
6
+
7
+ - missing fields on datahub events ([0f0508a](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/0f0508ab7ce113027dea9bf34b45bc431823b3a3))
8
+
1
9
  ## [1.32.1](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.32.0...v1.32.1) (2025-10-06)
2
10
 
3
11
  # [1.32.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.31.1...v1.32.0) (2025-10-03)
@@ -4,7 +4,7 @@ import {
4
4
  SparkieMessageServiceMock,
5
5
  SparkieCursorServiceMock
6
6
  } from '@/src/modules/sparkie/__tests__/sparkie.mock'
7
- import MessageService from '@hotmart/sparkie/dist/MessageService'
7
+ import MessageService from '@hotmart-org-ca/sparkie/dist/MessageService'
8
8
 
9
9
  vi.mock('@hotmart/sparkie', () => ({ default: SparkieMock }))
10
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "app-tutor-ai-consumer",
3
- "version": "1.32.1",
3
+ "version": "1.32.3",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "dev": "rspack serve --env=development --config config/rspack/rspack.config.js",
@@ -108,8 +108,8 @@
108
108
  "dependencies": {
109
109
  "@hot-observability-js/react": "~1.1.0",
110
110
  "@hotmart-org-ca/hot-observability-js": "~1.1.0",
111
+ "@hotmart-org-ca/sparkie": "~5.1.4",
111
112
  "@hotmart/event-agent-js": "~1.1.2",
112
- "@hotmart/sparkie": "~5.1.0",
113
113
  "@optimizely/react-sdk": "~3.2.4",
114
114
  "@tanstack/query-sync-storage-persister": "~5.80.7",
115
115
  "@tanstack/react-query": "~5.80.6",
@@ -1,4 +1,4 @@
1
- import type { StartTutorWidgetProps } from '@/src/types'
1
+ import type { StartTutorWidgetProps, WidgetInstance } from '@/src/types'
2
2
 
3
3
  export {}
4
4
 
@@ -13,7 +13,8 @@ declare global {
13
13
  elementId: StartTutorWidgetProps['elementId'],
14
14
  settings: StartTutorWidgetProps['settings']
15
15
  ) => Promise<void>
16
- closeChatWidget: () => void
16
+ closeChatWidget: () => Promise<void>
17
+ __CHAT_WIDGET_INSTANCE__?: WidgetInstance
17
18
  TOKEN: string
18
19
  }
19
20
  }
@@ -1,9 +1,9 @@
1
1
  import { HttpCodes } from '@/src/lib/utils'
2
- import { Result } from '../constants'
2
+ import { Result, ScreenNames } from '../constants'
3
3
  import { DataHubEntities } from '../entities'
4
- import type { DataHubEntityTypes, ResultType } from '../types'
4
+ import type { DataHubEntityTypes, ResultType, ScreenNamesType } from '../types'
5
5
 
6
- import BaseSchema from './base-schema'
6
+ import BaseProductSchema from './base-product-schema'
7
7
 
8
8
  export type BaseTrySchemaConstructorArgs = {
9
9
  componentName: string
@@ -13,14 +13,16 @@ export type BaseTrySchemaConstructorArgs = {
13
13
  result?: ResultType
14
14
  statusCode?: number
15
15
  failureDescription?: string
16
+ screenName?: ScreenNamesType
16
17
  }
17
18
 
18
- abstract class BaseTrySchema extends BaseSchema {
19
+ abstract class BaseTrySchema extends BaseProductSchema {
19
20
  private componentName: string
20
21
  private componentSource: string
21
22
  private failureDescription: string
22
23
  private result: ResultType
23
24
  private statusCode: number
25
+ private screenName: ScreenNamesType
24
26
 
25
27
  entity: DataHubEntityTypes
26
28
  isLogged: boolean
@@ -33,7 +35,8 @@ abstract class BaseTrySchema extends BaseSchema {
33
35
  failureDescription = Result.FAILURE_DESCRIPTION,
34
36
  isLogged = true,
35
37
  result = Result.SUCCESS,
36
- statusCode = HttpCodes.OK
38
+ statusCode = HttpCodes.OK,
39
+ screenName = ScreenNames.HOME_CONSUMER
37
40
  } = args
38
41
 
39
42
  super()
@@ -45,9 +48,10 @@ abstract class BaseTrySchema extends BaseSchema {
45
48
  this.isLogged = isLogged
46
49
  this.result = result
47
50
  this.statusCode = statusCode
51
+ this.screenName = screenName
48
52
  }
49
53
 
50
- prepare(): Record<string, unknown> {
54
+ prepare() {
51
55
  return {
52
56
  ...super.prepare(),
53
57
  componentName: this.componentName,
@@ -56,7 +60,8 @@ abstract class BaseTrySchema extends BaseSchema {
56
60
  failureDescription: this.failureDescription,
57
61
  isLogged: this.isLogged,
58
62
  result: this.result,
59
- statusCode: this.statusCode
63
+ statusCode: this.statusCode,
64
+ screenName: this.screenName
60
65
  }
61
66
  }
62
67
  }
@@ -1,7 +1,13 @@
1
+ import { ScreenNames } from '../constants'
1
2
  import { DataHubEntities } from '../entities'
2
- import type { ComponentNamesType, ComponentSourceType, DataHubEntityTypes } from '../types'
3
+ import type {
4
+ ComponentNamesType,
5
+ ComponentSourceType,
6
+ DataHubEntityTypes,
7
+ ScreenNamesType
8
+ } from '../types'
3
9
 
4
- import BaseSchema from './base-schema'
10
+ import BaseProductSchema from './base-product-schema'
5
11
  import { ProductCategories } from './constants'
6
12
  import type { ProductCategoriesType } from './types'
7
13
 
@@ -12,13 +18,17 @@ export type BaseViewSchemaConstructorArgs = {
12
18
  entity?: DataHubEntityTypes
13
19
  isLogged?: boolean
14
20
  productType?: ProductCategoriesType
21
+ pushKind?: string
22
+ screenName?: ScreenNamesType
15
23
  }
16
24
 
17
- abstract class BaseViewSchema extends BaseSchema {
25
+ abstract class BaseViewSchema extends BaseProductSchema {
18
26
  private arrivedFrom: string
19
27
  private componentName: string
20
28
  private componentSource: string
21
29
  private productType: ProductCategoriesType
30
+ private pushKind: string
31
+ private screenName: ScreenNamesType
22
32
 
23
33
  entity: DataHubEntityTypes
24
34
  isLogged: boolean
@@ -30,7 +40,9 @@ abstract class BaseViewSchema extends BaseSchema {
30
40
  arrivedFrom = 'HOME_CLASS_CONSUMER',
31
41
  entity = DataHubEntities.PRODUCT_CONSUME,
32
42
  isLogged = true,
33
- productType = ProductCategories.OnlineServices
43
+ productType = ProductCategories.OnlineServices,
44
+ pushKind = 'NOT_PUSH_EVENT',
45
+ screenName = ScreenNames.HOME_CONSUMER
34
46
  } = args
35
47
 
36
48
  super()
@@ -41,9 +53,11 @@ abstract class BaseViewSchema extends BaseSchema {
41
53
  this.entity = entity
42
54
  this.isLogged = isLogged
43
55
  this.productType = productType
56
+ this.pushKind = pushKind
57
+ this.screenName = screenName
44
58
  }
45
59
 
46
- prepare(): Record<string, unknown> {
60
+ prepare() {
47
61
  return {
48
62
  ...super.prepare(),
49
63
  arrivedFrom: this.arrivedFrom,
@@ -51,7 +65,9 @@ abstract class BaseViewSchema extends BaseSchema {
51
65
  componentSource: this.componentSource,
52
66
  entity: this.entity,
53
67
  isLogged: this.isLogged,
54
- productType: this.productType
68
+ productType: this.productType,
69
+ pushKind: this.pushKind,
70
+ screenName: this.screenName
55
71
  }
56
72
  }
57
73
  }
@@ -1,12 +1,16 @@
1
+ import type { QueryClient } from '@tanstack/react-query'
1
2
  import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
2
3
  import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client'
3
4
  import type { PropsWithChildren } from 'react'
4
5
 
5
- import { persister, queryClient } from './query-client'
6
+ import { persister } from './query-client'
6
7
 
7
- export type QueryProviderProps = PropsWithChildren<{ showDevTools?: boolean }>
8
+ export type QueryProviderProps = PropsWithChildren<{
9
+ showDevTools?: boolean
10
+ queryClient: QueryClient
11
+ }>
8
12
 
9
- function QueryProvider({ children, showDevTools = true }: QueryProviderProps) {
13
+ function QueryProvider({ children, queryClient, showDevTools = true }: QueryProviderProps) {
10
14
  return (
11
15
  <PersistQueryClientProvider client={queryClient} persistOptions={{ persister }}>
12
16
  {children}
@@ -0,0 +1,61 @@
1
+ import './config/styles/global.css'
2
+ import './config/styles/index.css'
3
+
4
+ import { StrictMode } from 'react'
5
+ import { createRoot } from 'react-dom/client'
6
+
7
+ import { initTheme } from '@/src/config/theme'
8
+ import { version } from '../package.json'
9
+
10
+ import { initLanguage } from './config/i18n'
11
+ import { devMode, productionMode } from './lib/utils'
12
+ import { Main } from './main'
13
+ import { TutorWidgetEvents } from './modules/widget'
14
+ import type { Theme, WidgetSettingProps } from './types'
15
+
16
+ const loadMainStyles = () => {
17
+ const isProduction = productionMode
18
+ const bundlePath = !isProduction
19
+ ? `${process.env.BUNDLE_PATH}/`
20
+ : `${process.env.BUNDLE_PATH}/${process.env.APP_NAME}/_current/`
21
+
22
+ const cssPath = `${bundlePath}app-tutor-ai-consumer.css?v=${version}`
23
+
24
+ if (!document.querySelector(`link[href="${cssPath}"]`)) {
25
+ const linkElement = document.createElement('link')
26
+ linkElement.rel = 'stylesheet'
27
+ linkElement.href = cssPath
28
+ document.head.appendChild(linkElement)
29
+ }
30
+ }
31
+
32
+ window.startChatWidget = async (
33
+ elementId = 'tutor-chat-app-widget',
34
+ settings: WidgetSettingProps
35
+ ) => {
36
+ if (!devMode) {
37
+ loadMainStyles()
38
+ }
39
+
40
+ const rootElement = document.getElementById(elementId) as HTMLElement
41
+
42
+ if (!rootElement) return
43
+
44
+ rootElement.setAttribute('id', 'hotmart-app-tutor-ai-consumer-root')
45
+ const theme = (rootElement.getAttribute('data-theme') ?? 'dark') as Theme
46
+ const root = createRoot(rootElement)
47
+
48
+ await initLanguage(settings.locale)
49
+ initTheme(theme)
50
+
51
+ if (root) {
52
+ root.render(
53
+ <StrictMode>
54
+ <Main settings={{ ...settings, config: { ...settings.config, theme } }} />
55
+ </StrictMode>
56
+ )
57
+ }
58
+ }
59
+
60
+ window.closeChatWidget = () =>
61
+ Promise.resolve(TutorWidgetEvents['c3po-app-widget-close'].dispatch())
package/src/index.tsx CHANGED
@@ -4,13 +4,14 @@ import './config/styles/index.css'
4
4
  import { StrictMode } from 'react'
5
5
  import { createRoot } from 'react-dom/client'
6
6
 
7
+ import { queryClient, QueryProvider } from '@/src/config/tanstack'
7
8
  import { initTheme } from '@/src/config/theme'
8
9
  import { version } from '../package.json'
9
10
 
10
11
  import { initLanguage } from './config/i18n'
11
12
  import { devMode, productionMode } from './lib/utils'
12
13
  import { Main } from './main'
13
- import { TutorWidgetEvents } from './modules/widget'
14
+ import { SparkieService } from './modules/sparkie'
14
15
  import type { Theme, WidgetSettingProps } from './types'
15
16
 
16
17
  const loadMainStyles = () => {
@@ -33,28 +34,80 @@ window.startChatWidget = async (
33
34
  elementId = 'tutor-chat-app-widget',
34
35
  settings: WidgetSettingProps
35
36
  ) => {
37
+ if (window.__CHAT_WIDGET_INSTANCE__) {
38
+ await window.closeChatWidget()
39
+ }
40
+
36
41
  if (!devMode) {
37
42
  loadMainStyles()
38
43
  }
39
44
 
40
- const rootElement = document.getElementById(elementId) as HTMLElement
45
+ const container = document.getElementById(elementId) as HTMLElement
41
46
 
42
- if (!rootElement) return
47
+ if (!container) return
43
48
 
44
- rootElement.setAttribute('id', 'hotmart-app-tutor-ai-consumer-root')
45
- const theme = (rootElement.getAttribute('data-theme') ?? 'dark') as Theme
46
- const root = createRoot(rootElement)
49
+ container.setAttribute('id', 'hotmart-app-tutor-ai-consumer-root')
50
+ const theme = (container.getAttribute('data-theme') ?? 'dark') as Theme
47
51
 
48
- await initLanguage(settings.locale)
49
- initTheme(theme)
52
+ const root = createRoot(container)
50
53
 
51
54
  if (root) {
55
+ initTheme(theme)
56
+ await initLanguage(settings.locale)
57
+
52
58
  root.render(
53
59
  <StrictMode>
54
- <Main settings={{ ...settings, config: { ...settings.config, theme } }} />
60
+ <QueryProvider queryClient={queryClient} showDevTools={false}>
61
+ <Main settings={{ ...settings, config: { ...settings.config, theme } }} />
62
+ </QueryProvider>
55
63
  </StrictMode>
56
64
  )
65
+
66
+ window.__CHAT_WIDGET_INSTANCE__ = { root, container, queryClient }
57
67
  }
58
68
  }
59
69
 
60
- window.closeChatWidget = () => TutorWidgetEvents['c3po-app-widget-close'].dispatch()
70
+ window.closeChatWidget = async () => {
71
+ const chatWidgetInstance = window.__CHAT_WIDGET_INSTANCE__
72
+
73
+ if (!chatWidgetInstance) return
74
+
75
+ const { root, container, queryClient } = chatWidgetInstance
76
+
77
+ try {
78
+ await queryClient.cancelQueries()
79
+ } catch (err) {
80
+ console.error('Error cancelling queries on widget close', err)
81
+ }
82
+
83
+ try {
84
+ root.unmount()
85
+ } catch (err) {
86
+ console.warn('Error unmounting widget root', err)
87
+ }
88
+
89
+ if (typeof queryClient.clear === 'function') {
90
+ try {
91
+ queryClient.clear()
92
+ } catch {
93
+ queryClient.getQueryCache().clear()
94
+ queryClient.getMutationCache().clear()
95
+ }
96
+ } else {
97
+ queryClient.getQueryCache().clear()
98
+ queryClient.getMutationCache().clear()
99
+ }
100
+
101
+ try {
102
+ container.remove()
103
+ } catch {
104
+ // ignore
105
+ }
106
+
107
+ try {
108
+ await SparkieService.destroySparkie()
109
+ window.__CHAT_WIDGET_INSTANCE__ = undefined
110
+ } catch (err) {
111
+ console.error('Error destroying Sparkie instance', err)
112
+ }
113
+ }
@@ -0,0 +1 @@
1
+ export { default as useResponseTimeout } from './use-response-timeout'
@@ -0,0 +1,42 @@
1
+ import { useCallback, useRef } from 'react'
2
+
3
+ const FIVE_MINUTES = 5 * 60 * 1000
4
+
5
+ function useResponseTimeout() {
6
+ const timeoutRef = useRef<NodeJS.Timeout>(null)
7
+ const isWaitingRef = useRef(false)
8
+
9
+ const startTimeout = useCallback((callback: () => void) => {
10
+ if (timeoutRef.current) clearTimeout(timeoutRef.current)
11
+
12
+ isWaitingRef.current = true
13
+
14
+ timeoutRef.current = setTimeout(() => {
15
+ callback()
16
+ isWaitingRef.current = false
17
+ }, FIVE_MINUTES)
18
+ }, [])
19
+
20
+ const reset = useCallback(() => {
21
+ if (timeoutRef.current) {
22
+ clearTimeout(timeoutRef.current)
23
+ timeoutRef.current = null
24
+ }
25
+ isWaitingRef.current = false
26
+ }, [])
27
+
28
+ const cleanup = useCallback(() => {
29
+ if (timeoutRef.current) {
30
+ clearTimeout(timeoutRef.current)
31
+ }
32
+ }, [])
33
+
34
+ return {
35
+ startTimeout,
36
+ cleanup,
37
+ reset,
38
+ isWaiting: isWaitingRef.current
39
+ }
40
+ }
41
+
42
+ export default useResponseTimeout
@@ -2,7 +2,6 @@ import { type PropsWithChildren, useEffect } from 'react'
2
2
  import { v4 } from 'uuid'
3
3
 
4
4
  import { OptimizelyProvider } from '@/src/config/optimizely'
5
- import { QueryProvider } from '@/src/config/tanstack'
6
5
  import { useWidgetSettingsAtom } from '@/src/modules/widget'
7
6
  import type { WidgetSettingProps } from '@/src/types'
8
7
 
@@ -21,11 +20,7 @@ function GlobalProviders({ children, settings }: GlobalProvidersProps) {
21
20
  setWidgetSettings(settings)
22
21
  }, [setWidgetSettings, settings])
23
22
 
24
- return (
25
- <OptimizelyProvider settings={settings}>
26
- <QueryProvider>{children}</QueryProvider>
27
- </OptimizelyProvider>
28
- )
23
+ return <OptimizelyProvider settings={settings}>{children}</OptimizelyProvider>
29
24
  }
30
25
 
31
26
  export default GlobalProviders
@@ -1,4 +1,4 @@
1
- import type { MessageContent } from '@hotmart/sparkie/dist/MessageService'
1
+ import type { MessageContent } from '@hotmart-org-ca/sparkie/dist/MessageService'
2
2
 
3
3
  import { chance } from '@/src/config/tests'
4
4
  import type { IMessageWithSenderData } from '../types'
@@ -1,10 +1,11 @@
1
1
  import { useCallback, useEffect, useMemo } from 'react'
2
- import type { InfiniteData } from '@tanstack/react-query'
2
+ import type { InfiniteData, QueryClient } from '@tanstack/react-query'
3
3
  import { useQueryClient } from '@tanstack/react-query'
4
4
  import { produce } from 'immer'
5
5
 
6
6
  import { ComponentSource, DataHubService } from '@/src/config/datahub'
7
7
  import { ViewTutorAnswerMessageSchema } from '@/src/config/datahub/schemas/tutor'
8
+ import { useResponseTimeout } from '@/src/lib/hooks/use-response-timeout'
8
9
  import { useUpdateCursor } from '@/src/modules/cursor/hooks'
9
10
  import { useGetProfile } from '@/src/modules/profile'
10
11
  import { SparkieService } from '@/src/modules/sparkie'
@@ -17,6 +18,59 @@ import { useMessagesMaxCount, useUnreadMessagesSetAtom } from '../../store'
17
18
  import type { FetchMessagesResponse, IMessageWithSenderData } from '../../types'
18
19
  import { getAllMessagesQuery } from '../use-infinite-get-messages'
19
20
 
21
+ export type MessageReceivedProps = {
22
+ data: IMessageWithSenderData
23
+ queryClient: QueryClient
24
+ conversationId: string
25
+ profileId: string
26
+ limit: number
27
+ messageIsMineCallback?: (data: IMessageWithSenderData) => void
28
+ messageIsNotMineCallback?: (data: IMessageWithSenderData) => void
29
+ errorCallback?: (error: unknown) => void
30
+ }
31
+
32
+ const messageReceivedUtil = ({
33
+ queryClient,
34
+ data,
35
+ conversationId,
36
+ profileId,
37
+ limit,
38
+ messageIsMineCallback,
39
+ messageIsNotMineCallback,
40
+ errorCallback
41
+ }: MessageReceivedProps) => {
42
+ try {
43
+ const messagesQueryConfig = getAllMessagesQuery({ conversationId, profileId, limit })
44
+ const queryData = queryClient.getQueryData<InfiniteData<FetchMessagesResponse>>(
45
+ messagesQueryConfig.queryKey
46
+ )
47
+
48
+ const idsList = new Set(
49
+ queryData?.pages.flatMap((items) => items.messages.map((msg) => msg.id))
50
+ )
51
+
52
+ if (idsList.has(data.id)) return
53
+
54
+ queryClient.setQueryData<InfiniteData<FetchMessagesResponse>>(
55
+ messagesQueryConfig.queryKey,
56
+ (oldData) =>
57
+ produce(oldData, (draft) => {
58
+ draft?.pages.at(-1)?.messages?.push(data)
59
+ return draft
60
+ })
61
+ )
62
+
63
+ const isMine = data.contactId === profileId
64
+
65
+ if (isMine) {
66
+ return messageIsMineCallback?.(data)
67
+ }
68
+ messageIsNotMineCallback?.(data)
69
+ } catch (error) {
70
+ errorCallback?.(error)
71
+ }
72
+ }
73
+
20
74
  const useSubscribeMessageReceivedEvent = () => {
21
75
  const [settings] = useWidgetSettingsAtom()
22
76
  const [, setWidgetLoading] = useWidgetLoadingAtom()
@@ -26,75 +80,78 @@ const useSubscribeMessageReceivedEvent = () => {
26
80
  const useUpdateCursorMutation = useUpdateCursor()
27
81
  const limit = useMessagesMaxCount()
28
82
  const isAgentMode = useIsAgentParentAtomValue()
83
+ const { reset: resetLoadingTimeout, startTimeout } = useResponseTimeout()
29
84
 
30
85
  const conversationId = useMemo(() => String(settings?.conversationId), [settings?.conversationId])
31
86
  const profileId = useMemo(() => String(profileQuery?.data?.id), [profileQuery?.data?.id])
32
- const messagesQueryConfig = useMemo(
33
- () =>
34
- getAllMessagesQuery({
35
- conversationId,
36
- profileId,
37
- limit
38
- }),
39
- [conversationId, limit, profileId]
87
+
88
+ const messageIsMineCallback = useCallback(
89
+ (data: IMessageWithSenderData) => {
90
+ // The cursor should update only with my messages
91
+ useUpdateCursorMutation.mutate(data.conversationId)
92
+ },
93
+ [useUpdateCursorMutation]
40
94
  )
41
95
 
42
- const messageReceived = useCallback(
96
+ const messageIsNotMineCallback = useCallback(
43
97
  (data: IMessageWithSenderData) => {
44
- if (data.conversationId !== conversationId) return
45
-
46
- const queryData = queryClient.getQueryData<InfiniteData<FetchMessagesResponse>>(
47
- messagesQueryConfig.queryKey
48
- )
49
-
50
- const idsList = new Set(
51
- queryData?.pages.flatMap((items) => items.messages.map((msg) => msg.id))
52
- )
53
-
54
- if (idsList.has(data.id)) return
55
-
56
- queryClient.setQueryData<InfiniteData<FetchMessagesResponse>>(
57
- messagesQueryConfig.queryKey,
58
- (oldData) => {
59
- return produce(oldData, (draft) => {
60
- draft?.pages.at(-1)?.messages?.push(data)
61
- return draft
62
- })
63
- }
64
- )
65
-
66
- const isMine = data.contactId === profileId
67
-
68
- if (!isMine) {
69
- DataHubService.sendEvent({
70
- schema: new ViewTutorAnswerMessageSchema({
71
- correlationId: data.metadata.correlationId,
72
- messageId: data.id,
73
- componentSource: isAgentMode
74
- ? ComponentSource.PRODUCT_AGENT
75
- : ComponentSource.HOTMART_TUTOR
76
- })
98
+ setWidgetLoading(false)
99
+
100
+ DataHubService.sendEvent({
101
+ schema: new ViewTutorAnswerMessageSchema({
102
+ correlationId: data.metadata.correlationId,
103
+ messageId: data.id,
104
+ componentSource: isAgentMode
105
+ ? ComponentSource.PRODUCT_AGENT
106
+ : ComponentSource.HOTMART_TUTOR
77
107
  })
108
+ })
78
109
 
79
- addUnreadMessagesToSet({ itemId: data.id })
80
- setTimeout(() => setWidgetLoading(false), 100)
81
- } else {
82
- // The cursor should update only with my messages
83
- useUpdateCursorMutation.mutate(data.conversationId)
110
+ addUnreadMessagesToSet({ itemId: data.id })
111
+ },
112
+ [addUnreadMessagesToSet, isAgentMode, setWidgetLoading]
113
+ )
114
+
115
+ const errorCallback = useCallback(
116
+ (error: unknown) => {
117
+ if (error) {
118
+ setWidgetLoading(false)
119
+ console.error(error)
120
+ throw new Error('Error getting realtime messages')
84
121
  }
85
122
  },
123
+ [setWidgetLoading]
124
+ )
125
+
126
+ const messageReceived = useCallback(
127
+ (data: IMessageWithSenderData) => {
128
+ if (data.conversationId === conversationId)
129
+ messageReceivedUtil({
130
+ queryClient,
131
+ data,
132
+ conversationId,
133
+ profileId,
134
+ limit,
135
+ messageIsMineCallback,
136
+ messageIsNotMineCallback,
137
+ errorCallback
138
+ })
139
+ },
86
140
  [
87
- addUnreadMessagesToSet,
88
141
  conversationId,
89
- isAgentMode,
90
- messagesQueryConfig.queryKey,
142
+ errorCallback,
143
+ limit,
144
+ messageIsMineCallback,
145
+ messageIsNotMineCallback,
91
146
  profileId,
92
- queryClient,
93
- setWidgetLoading,
94
- useUpdateCursorMutation
147
+ queryClient
95
148
  ]
96
149
  )
97
150
 
151
+ const startLoadingTimeout = useCallback(() => {
152
+ startTimeout(() => setWidgetLoading(false))
153
+ }, [setWidgetLoading, startTimeout])
154
+
98
155
  useEffect(() => {
99
156
  SparkieService.subscribeEvents({ messageReceived })
100
157
 
@@ -102,6 +159,14 @@ const useSubscribeMessageReceivedEvent = () => {
102
159
  SparkieService.removeEventSubscription({ messageReceived })
103
160
  }
104
161
  }, [messageReceived])
162
+
163
+ useEffect(() => {
164
+ startLoadingTimeout()
165
+
166
+ return () => {
167
+ resetLoadingTimeout()
168
+ }
169
+ }, [resetLoadingTimeout, startLoadingTimeout])
105
170
  }
106
171
 
107
172
  export default useSubscribeMessageReceivedEvent
@@ -1,4 +1,4 @@
1
- import type { Message } from '@hotmart/sparkie/dist/MessageService'
1
+ import type { Message } from '@hotmart-org-ca/sparkie/dist/MessageService'
2
2
 
3
3
  import { api } from '@/src/config/request'
4
4
 
@@ -1,4 +1,5 @@
1
- import type { Message } from '@hotmart/sparkie/dist/MessageService'
1
+ import type { Message } from '@hotmart-org-ca/sparkie/dist/MessageService'
2
+ import type MessageService from '@hotmart-org-ca/sparkie/dist/MessageService'
2
3
 
3
4
  import { ApiError } from '@/src/config/request'
4
5
  import { HttpCodes } from '@/src/lib/utils'
@@ -15,7 +16,7 @@ import type {
15
16
  } from './types'
16
17
 
17
18
  class MessagesService {
18
- async getSparkieMessageService() {
19
+ async getSparkieMessageService(): Promise<MessageService> {
19
20
  try {
20
21
  const messageService = await SparkieService.getMessageService()
21
22
 
@@ -2,7 +2,7 @@ import type {
2
2
  Message as SparkieMsg,
3
3
  MessageContent,
4
4
  SenderData
5
- } from '@hotmart/sparkie/dist/MessageService'
5
+ } from '@hotmart-org-ca/sparkie/dist/MessageService'
6
6
 
7
7
  export type IMessage = SparkieMsg & {
8
8
  metadata: {
@@ -1,4 +1,4 @@
1
- import type { Message } from '@hotmart/sparkie/dist/MessageService'
1
+ import type { Message } from '@hotmart-org-ca/sparkie/dist/MessageService'
2
2
  import type { InfiniteData, QueryClient } from '@tanstack/react-query'
3
3
  import { produce } from 'immer'
4
4
 
@@ -1,4 +1,4 @@
1
- import Sparkie from '@hotmart/sparkie'
1
+ import Sparkie from '@hotmart-org-ca/sparkie'
2
2
 
3
3
  import { MSG_MAX_COUNT } from '../messages'
4
4
 
@@ -110,8 +110,7 @@ class SparkieService {
110
110
 
111
111
  while (attempt <= maxRetries) {
112
112
  try {
113
- if (!token || !token.trim())
114
- throw new Error('Invalid or missing token for Sparkie initialization')
113
+ if (!token?.trim()) throw new Error('Invalid or missing token for Sparkie initialization')
115
114
 
116
115
  const sparkie = this.sparkieInstance
117
116
  const service = await sparkie.init(token, { skipPresenceSetup })
@@ -1,21 +1,9 @@
1
- import { useEffect, useState } from 'react'
2
-
3
1
  import { useSubscribeMessageReceivedEvent } from '@/src/modules/messages/hooks'
4
2
  import { useSubscribeThreadClosedEvent } from '@/src/modules/thread/hooks'
5
3
  import { useListenToVisibilityEvents } from '../../hooks'
6
4
  import { useWidgetTabsAtom } from '../../store'
7
5
  import { WIDGET_TABS } from '../constants'
8
6
 
9
- // TODO: REMOVE
10
- const hotmartRumKey = 'app-tutor-ai-consumer::hotmart-rum::activate'
11
- const useSentryDebugger = () => {
12
- const [logError] = useState(() => Boolean(window?.localStorage?.getItem?.(hotmartRumKey)))
13
-
14
- useEffect(() => {
15
- if (logError) throw new Error(hotmartRumKey)
16
- }, [logError])
17
- }
18
-
19
7
  function WidgetContainer() {
20
8
  const [widgetTabs] = useWidgetTabsAtom()
21
9
 
@@ -23,8 +11,6 @@ function WidgetContainer() {
23
11
  useSubscribeThreadClosedEvent()
24
12
  useListenToVisibilityEvents()
25
13
 
26
- useSentryDebugger()
27
-
28
14
  return (
29
15
  <div className='flex h-full flex-col items-center justify-stretch overflow-hidden'>
30
16
  {WIDGET_TABS[widgetTabs.currentTab]}
package/src/types.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import type { QueryClient } from '@tanstack/react-query'
2
+ import type { Root } from 'react-dom/client'
3
+
1
4
  import type { ILanguages } from './config/i18n'
2
5
 
3
6
  export type StartTutorWidgetProps = {
@@ -68,3 +71,9 @@ export interface ICustomEvent<T = object> {
68
71
  handler: (listener: EventListenerOrEventListenerObject) => () => void | Promise<void>
69
72
  dispatch: <D = unknown>(detail?: D) => void
70
73
  }
74
+
75
+ export type WidgetInstance = {
76
+ root: Root
77
+ container: HTMLElement
78
+ queryClient: QueryClient
79
+ }