@aslaluroba/help-center-react 3.2.4 → 3.2.5

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/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "main": "dist/index.js",
4
4
  "module": "dist/index.esm.js",
5
5
  "types": "dist/index.d.ts",
6
- "version": "3.2.4",
6
+ "version": "3.2.5",
7
7
  "description": "BabylAI Help Center Widget for React and Next.js",
8
8
  "private": false,
9
9
  "exports": {
@@ -1,13 +1,22 @@
1
1
  import * as Ably from 'ably';
2
2
 
3
+ type ActionHandlerCallback = (actionType: string | undefined | null, messageData: any) => void | Promise<void>;
4
+
3
5
  export class ClientAblyService {
4
6
  private static client: Ably.Realtime | null = null;
5
7
  private static channel: Ably.RealtimeChannel | null = null;
6
8
  private static isConnected: boolean = false;
7
9
  private static sessionId: string | null = null;
8
10
  private static messageUnsubscribe: (() => void) | null = null;
9
-
10
- static async startConnection(sessionId: string, ablyToken: string, onMessageReceived: Function, tenantId: string) {
11
+ private static onActionReceived: ActionHandlerCallback | null = null;
12
+
13
+ static async startConnection(
14
+ sessionId: string,
15
+ ablyToken: string,
16
+ onMessageReceived: Function,
17
+ tenantId: string,
18
+ onActionReceived?: ActionHandlerCallback
19
+ ) {
11
20
  // Prevent multiple connections
12
21
  if (this.isConnected && this.sessionId === sessionId) {
13
22
  return;
@@ -73,6 +82,9 @@ export class ClientAblyService {
73
82
  }, 10000);
74
83
  });
75
84
 
85
+ // Store optional action handler for this connection
86
+ this.onActionReceived = onActionReceived ?? null;
87
+
76
88
  // Subscribe to the session room
77
89
  await this.joinChannel(sessionId, onMessageReceived, tenantId);
78
90
  } catch (error) {
@@ -108,11 +120,17 @@ export class ClientAblyService {
108
120
  this.channel.subscribe('ReceiveMessage', (message) => {
109
121
  try {
110
122
  // Ensure messageContent is always a string (default to empty string if undefined)
123
+ const rawData = message.data;
124
+
111
125
  const messageContent =
112
- typeof message.data === 'string' ? message.data : message.data?.content ?? message.data?.message ?? '';
113
- const senderType = message.data?.senderType || 3; // Assistant
114
- const needsAgent = message.data?.needsAgent || message.data?.actionType == 'needs_agent' || false;
115
- const attachments = message.data?.attachments || [];
126
+ typeof rawData === 'string' ? rawData : rawData?.content ?? rawData?.message ?? '';
127
+ const senderType = rawData?.senderType || 3; // Assistant
128
+ const needsAgent = rawData?.needsAgent || rawData?.actionType == 'needs_agent' || false;
129
+ const attachments = rawData?.attachments || [];
130
+ const actionType = (rawData && typeof rawData.actionType === 'string' ? rawData.actionType : '') as
131
+ | string
132
+ | undefined
133
+ | null;
116
134
 
117
135
  // Extract downloadUrl from attachments (Ably now returns downloadUrl directly)
118
136
  // Attachments can be: strings (URLs), objects with downloadUrl, or objects with id
@@ -133,6 +151,19 @@ export class ClientAblyService {
133
151
  })
134
152
  .filter((url: string | null): url is string => url !== null);
135
153
 
154
+ // Invoke optional action handler first (non-blocking for message processing)
155
+ if (this.onActionReceived && actionType !== undefined) {
156
+ try {
157
+ void this.onActionReceived(actionType, rawData);
158
+ } catch (actionError) {
159
+ console.error('[AblyService] Error in action handler callback', {
160
+ error: actionError,
161
+ actionType,
162
+ rawData,
163
+ });
164
+ }
165
+ }
166
+
136
167
  onMessageReceived(messageContent, senderType, needsAgent, attachmentUrls);
137
168
  } catch (error) {
138
169
  console.error('[AblyService] Error processing message', { error, message });
@@ -166,6 +197,7 @@ export class ClientAblyService {
166
197
 
167
198
  this.isConnected = false;
168
199
  this.sessionId = null;
200
+ this.onActionReceived = null;
169
201
  } catch (error) {
170
202
  console.error('[AblyService] Error in stopConnection', { error });
171
203
  // Reset state even if there's an error
@@ -0,0 +1,102 @@
1
+ import { useCallback, useRef } from 'react';
2
+
3
+ export type ActionHandler = (messageData: unknown) => void | Promise<void>;
4
+
5
+ export interface ActionHandlerService {
6
+ registerHandler(actionType: string, handler: ActionHandler): void;
7
+ unregisterHandler(actionType: string): void;
8
+ handleAction(actionType: string | undefined | null, messageData: unknown): Promise<void>;
9
+ getRegisteredActions(): string[];
10
+ }
11
+
12
+ export const useActionHandler = (): ActionHandlerService => {
13
+ const handlersRef = useRef<Map<string, ActionHandler>>(new Map());
14
+
15
+ const defaultHandlerRef = useRef<ActionHandler>((messageData) => {
16
+ const message = (messageData as { actionType?: string } | undefined) ?? {};
17
+ const actionType = message.actionType || '';
18
+
19
+ if (actionType) {
20
+ // Log unknown actions for debugging but do not crash
21
+ // eslint-disable-next-line no-console
22
+ console.warn(
23
+ `[ActionHandler] Unknown action type received: "${actionType}". Message data:`,
24
+ messageData
25
+ );
26
+ }
27
+ });
28
+
29
+ const registerHandler = useCallback((actionType: string, handler: ActionHandler) => {
30
+ if (!actionType) {
31
+ // eslint-disable-next-line no-console
32
+ console.warn('[ActionHandler] Attempted to register handler with empty action type. Ignored.');
33
+ return;
34
+ }
35
+
36
+ if (handlersRef.current.has(actionType)) {
37
+ // eslint-disable-next-line no-console
38
+ console.warn(
39
+ `[ActionHandler] Handler for action type "${actionType}" already exists. Overwriting with new handler.`
40
+ );
41
+ }
42
+
43
+ handlersRef.current.set(actionType, handler);
44
+ }, []);
45
+
46
+ const unregisterHandler = useCallback((actionType: string) => {
47
+ if (!actionType) {
48
+ return;
49
+ }
50
+ handlersRef.current.delete(actionType);
51
+ }, []);
52
+
53
+ const handleAction = useCallback(
54
+ async (actionType: string | undefined | null, messageData: unknown) => {
55
+ // Ignore empty, null, or undefined action types (no-op)
56
+ if (!actionType || actionType === '') {
57
+ return;
58
+ }
59
+
60
+ const handler = handlersRef.current.get(actionType);
61
+
62
+ if (handler) {
63
+ try {
64
+ await handler(messageData);
65
+ } catch (error) {
66
+ // eslint-disable-next-line no-console
67
+ console.error(
68
+ `[ActionHandler] Error executing handler for action type "${actionType}":`,
69
+ error
70
+ );
71
+ }
72
+ return;
73
+ }
74
+
75
+ const defaultHandler = defaultHandlerRef.current;
76
+ if (defaultHandler) {
77
+ try {
78
+ await Promise.resolve(defaultHandler(messageData));
79
+ } catch (error) {
80
+ // eslint-disable-next-line no-console
81
+ console.error(
82
+ `[ActionHandler] Error executing default handler for unknown action type "${actionType}":`,
83
+ error
84
+ );
85
+ }
86
+ }
87
+ },
88
+ []
89
+ );
90
+
91
+ const getRegisteredActions = useCallback(() => {
92
+ return Array.from(handlersRef.current.keys());
93
+ }, []);
94
+
95
+ return {
96
+ registerHandler,
97
+ unregisterHandler,
98
+ handleAction,
99
+ getRegisteredActions,
100
+ };
101
+ };
102
+
package/src/lib/index.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from "./LanguageContext";
2
2
  export * from "./utils";
3
3
  export * from "./theme-utils";
4
+ export * from "./custom-hooks/useActionHandler";
@@ -1,5 +1,5 @@
1
1
  import ReviewDialog from '@/ui/review-dialog';
2
- import { useEffect, useState } from 'react';
2
+ import { useCallback, useEffect, useState } from 'react';
3
3
  import { apiRequest } from '../core/api';
4
4
  import { ClientAblyService } from '../core/AblyService';
5
5
  import { useLocalTranslation } from '../useLocalTranslation';
@@ -11,6 +11,7 @@ import HelpPopup from './help-popup';
11
11
  import { defaultLanguage } from '@/i18n';
12
12
  import { LanguageProvider } from '@/lib/LanguageContext';
13
13
  import { getThemeStyles } from '@/lib/theme-utils';
14
+ import { useActionHandler } from '@/lib/custom-hooks/useActionHandler';
14
15
 
15
16
  interface HelpCenterProps {
16
17
  helpScreenId: string;
@@ -57,6 +58,8 @@ const HelpCenterContent = ({
57
58
  const [assistantStatus, setAssistantStatus] = useState('idle');
58
59
  const [isReviewDialogOpen, setIsReviewDialogOpen] = useState(false);
59
60
 
61
+ const actionHandler = useActionHandler();
62
+
60
63
  const handleTogglePopup = () => {
61
64
  setIsOpen(!isOpen);
62
65
  setShowArrowAnimation(isOpen);
@@ -93,7 +96,7 @@ const HelpCenterContent = ({
93
96
  setAssistantStatus('idle');
94
97
  };
95
98
 
96
- const handleEndChat = async () => {
99
+ const handleEndChat = useCallback(async () => {
97
100
  if (!sessionId || !selectedOption) return;
98
101
 
99
102
  try {
@@ -123,7 +126,7 @@ const HelpCenterContent = ({
123
126
  setSessionId(null);
124
127
  setSelectedOption(null);
125
128
  }
126
- };
129
+ }, [language, selectedOption, sessionId]);
127
130
 
128
131
  const handleSendChatReview = async ({ comment, rating }: ReviewProps) => {
129
132
  if (!reviewSessionId) return;
@@ -195,7 +198,10 @@ const HelpCenterContent = ({
195
198
  newSessionId,
196
199
  responseData.ablyToken,
197
200
  handleReceiveMessage,
198
- responseData.chatSession.tenantId
201
+ responseData.chatSession.tenantId,
202
+ (actionType, messageData) => {
203
+ void actionHandler.handleAction(actionType, messageData);
204
+ }
199
205
  );
200
206
 
201
207
  // Verify the connection is actually active
@@ -297,6 +303,25 @@ const HelpCenterContent = ({
297
303
  }
298
304
  };
299
305
 
306
+ // Register known action handlers for realtime messages
307
+ useEffect(() => {
308
+ // "needs_agent" → trigger handoff / needs agent UI flow
309
+ actionHandler.registerHandler('needs_agent', () => {
310
+ setNeedsAgent(true);
311
+ });
312
+
313
+ // "end_session" → gracefully close the chat session and show review dialog
314
+ actionHandler.registerHandler('end_session', () => {
315
+ // Reuse existing end chat logic
316
+ void handleEndChat();
317
+ });
318
+
319
+ return () => {
320
+ actionHandler.unregisterHandler('needs_agent');
321
+ actionHandler.unregisterHandler('end_session');
322
+ };
323
+ }, [actionHandler, handleEndChat]);
324
+
300
325
  useEffect(() => {
301
326
  if (isOpen && helpScreenId) {
302
327
  setStatus('loading');
package/src/.DS_Store DELETED
Binary file