@aslaluroba/help-center-react 3.2.16 → 3.2.18

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.
Files changed (102) hide show
  1. package/dist/components/shared/Button/button.d.ts +1 -1
  2. package/dist/components/shared/Card/card.d.ts +1 -4
  3. package/dist/components/ui/agent-response/agent-response.d.ts +2 -1
  4. package/dist/index.css +1424 -1
  5. package/dist/index.d.ts +3 -3
  6. package/dist/index.esm.js +19248 -38614
  7. package/dist/index.esm.js.map +1 -1
  8. package/dist/index.js +19269 -38635
  9. package/dist/index.js.map +1 -1
  10. package/dist/lib/LanguageContext.d.ts +1 -1
  11. package/dist/lib/custom-hooks/useAblyConnection.d.ts +25 -0
  12. package/dist/lib/custom-hooks/useActionHandler.d.ts +1 -7
  13. package/dist/lib/custom-hooks/useChatSession.d.ts +37 -0
  14. package/dist/lib/custom-hooks/useMessageQueue.d.ts +16 -0
  15. package/dist/lib/custom-hooks/useReview.d.ts +14 -0
  16. package/dist/lib/index.d.ts +1 -2
  17. package/dist/services.d.ts +9 -6
  18. package/dist/services.esm.js +1 -14195
  19. package/dist/services.esm.js.map +1 -1
  20. package/dist/services.js +19 -14191
  21. package/dist/services.js.map +1 -1
  22. package/dist/ui/chatbot-popup/chat-window-screen/footer.d.ts +1 -1
  23. package/dist/ui/chatbot-popup/chat-window-screen/in-chat-review.d.ts +1 -1
  24. package/dist/ui/chatbot-popup/chat-window-screen/index.d.ts +2 -2
  25. package/dist/ui/chatbot-popup/options-list-screen/helpscreen-list.d.ts +1 -1
  26. package/dist/ui/chatbot-popup/options-list-screen/helpscreen-option.d.ts +1 -1
  27. package/dist/ui/chatbot-popup/options-list-screen/index.d.ts +1 -1
  28. package/dist/ui/help-center.d.ts +1 -1
  29. package/dist/ui/help-popup.d.ts +4 -27
  30. package/dist/ui/review-dialog/index.d.ts +1 -1
  31. package/package.json +31 -45
  32. package/postcss.config.js +5 -0
  33. package/rollup.config.mjs +34 -0
  34. package/tsconfig.json +5 -6
  35. package/dist/core/AblyService.d.ts +0 -16
  36. package/dist/core/ApiService.d.ts +0 -16
  37. package/dist/core/api.d.ts +0 -10
  38. package/dist/core/token-service.d.ts +0 -10
  39. package/dist/i18n.d.ts +0 -3
  40. package/dist/lib/config.d.ts +0 -18
  41. package/dist/lib/theme-utils.d.ts +0 -10
  42. package/dist/lib/types.d.ts +0 -145
  43. package/dist/lib/utils.d.ts +0 -2
  44. package/src/assets/animatedLogo.gif +0 -0
  45. package/src/assets/logo.svg +0 -5
  46. package/src/assets/seperator.svg +0 -5
  47. package/src/components/index.ts +0 -1
  48. package/src/components/shared/Button/button.tsx +0 -38
  49. package/src/components/shared/Button/index.ts +0 -1
  50. package/src/components/shared/Card/card.tsx +0 -44
  51. package/src/components/shared/Card/index.ts +0 -1
  52. package/src/components/shared/index.ts +0 -2
  53. package/src/components/ui/agent-response/agent-response.tsx +0 -57
  54. package/src/components/ui/agent-response/doc.md +0 -88
  55. package/src/components/ui/image-attachment.tsx +0 -119
  56. package/src/components/ui/image-preview-dialog.tsx +0 -400
  57. package/src/components/ui/index.ts +0 -3
  58. package/src/core/AblyService.ts +0 -243
  59. package/src/core/ApiService.ts +0 -116
  60. package/src/core/api.ts +0 -278
  61. package/src/core/token-service.ts +0 -35
  62. package/src/globals.css +0 -268
  63. package/src/i18n.ts +0 -21
  64. package/src/index.ts +0 -19
  65. package/src/lib/LanguageContext.tsx +0 -28
  66. package/src/lib/config.ts +0 -52
  67. package/src/lib/custom-hooks/useActionHandler.ts +0 -102
  68. package/src/lib/custom-hooks/useTypewriter.ts +0 -26
  69. package/src/lib/index.ts +0 -4
  70. package/src/lib/theme-utils.ts +0 -56
  71. package/src/lib/types.ts +0 -158
  72. package/src/lib/utils.ts +0 -6
  73. package/src/locales/ar.json +0 -45
  74. package/src/locales/en.json +0 -45
  75. package/src/services.ts +0 -14
  76. package/src/types/icons.d.ts +0 -6
  77. package/src/types/svg.d.ts +0 -5
  78. package/src/types.d.ts +0 -9
  79. package/src/ui/chatbot-popup/active-chat-actions.tsx +0 -39
  80. package/src/ui/chatbot-popup/chat-window-screen/action-button.tsx +0 -37
  81. package/src/ui/chatbot-popup/chat-window-screen/footer.tsx +0 -313
  82. package/src/ui/chatbot-popup/chat-window-screen/header.tsx +0 -53
  83. package/src/ui/chatbot-popup/chat-window-screen/in-chat-review.tsx +0 -116
  84. package/src/ui/chatbot-popup/chat-window-screen/index.tsx +0 -366
  85. package/src/ui/chatbot-popup/chat-window-screen/typing-indicator.tsx +0 -31
  86. package/src/ui/chatbot-popup/error-screen/index.tsx +0 -22
  87. package/src/ui/chatbot-popup/loading-screen/index.tsx +0 -21
  88. package/src/ui/chatbot-popup/options-list-screen/company-card.tsx +0 -39
  89. package/src/ui/chatbot-popup/options-list-screen/header.tsx +0 -23
  90. package/src/ui/chatbot-popup/options-list-screen/helpscreen-intro.tsx +0 -32
  91. package/src/ui/chatbot-popup/options-list-screen/helpscreen-list.tsx +0 -57
  92. package/src/ui/chatbot-popup/options-list-screen/helpscreen-option.tsx +0 -56
  93. package/src/ui/chatbot-popup/options-list-screen/index.tsx +0 -70
  94. package/src/ui/confirmation-modal/index.tsx +0 -62
  95. package/src/ui/floating-message.tsx +0 -28
  96. package/src/ui/help-button.tsx +0 -24
  97. package/src/ui/help-center.tsx +0 -448
  98. package/src/ui/help-popup.tsx +0 -367
  99. package/src/ui/powered-by.tsx +0 -62
  100. package/src/ui/review-dialog/index.tsx +0 -149
  101. package/src/ui/review-dialog/rating.tsx +0 -79
  102. package/src/useLocalTranslation.ts +0 -15
@@ -1,243 +0,0 @@
1
- import * as Ably from 'ably';
2
-
3
- type ActionHandlerCallback = (actionType: string | undefined | null, messageData: any) => void | Promise<void>;
4
-
5
- export class ClientAblyService {
6
- private static client: Ably.Realtime | null = null;
7
- private static channel: Ably.RealtimeChannel | null = null;
8
- private static isConnected: boolean = false;
9
- private static sessionId: string | null = null;
10
- private static messageUnsubscribe: (() => void) | null = null;
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
- ) {
20
- // Prevent multiple connections
21
- if (this.isConnected && this.sessionId === sessionId) {
22
- return;
23
- }
24
-
25
- // Close existing connection if connecting to a different session
26
- if (this.isConnected && this.sessionId !== sessionId) {
27
- await this.stopConnection();
28
- }
29
-
30
- try {
31
- // Initialize Ably client with the token
32
- this.client = new Ably.Realtime({
33
- authUrl: undefined,
34
- token: ablyToken,
35
- autoConnect: true,
36
- });
37
-
38
- this.client.connection.on('failed', (stateChange) => {
39
- console.error('[AblyService] Connection state: failed', {
40
- reason: stateChange.reason?.message,
41
- error: stateChange.reason,
42
- });
43
- });
44
-
45
- // Wait for connection to be established
46
- await new Promise<void>((resolve, reject) => {
47
- if (!this.client) {
48
- const error = new Error('Failed to initialize Ably client');
49
- console.error('[AblyService]', error);
50
- reject(error);
51
- return;
52
- }
53
-
54
- this.client.connection.once('connected', () => {
55
- this.isConnected = true;
56
- this.sessionId = sessionId;
57
- resolve();
58
- });
59
-
60
- this.client.connection.once('failed', (stateChange) => {
61
- const error = new Error(`Ably connection failed: ${stateChange.reason?.message || 'Unknown error'}`);
62
- console.error('[AblyService] Connection failed', {
63
- reason: stateChange.reason?.message,
64
- error: stateChange.reason,
65
- });
66
- reject(error);
67
- });
68
-
69
- this.client.connection.once('disconnected', (stateChange) => {
70
- const error = new Error(`Ably connection disconnected: ${stateChange.reason?.message || 'Unknown error'}`);
71
- console.error('[AblyService] Connection disconnected', { reason: stateChange.reason?.message });
72
- reject(error);
73
- });
74
-
75
- // Set a timeout for connection
76
- setTimeout(() => {
77
- if (!this.isConnected) {
78
- const error = new Error('Ably connection timeout');
79
- console.error('[AblyService] Connection timeout after 10 seconds');
80
- reject(error);
81
- }
82
- }, 10000);
83
- });
84
-
85
- // Store optional action handler for this connection
86
- this.onActionReceived = onActionReceived ?? null;
87
-
88
- // Subscribe to the session room
89
- await this.joinChannel(sessionId, onMessageReceived, tenantId);
90
- } catch (error) {
91
- console.error('[AblyService] Error in startConnection', { error, sessionId });
92
- this.isConnected = false;
93
- this.sessionId = null;
94
- throw error;
95
- }
96
- }
97
-
98
- private static async joinChannel(sessionId: string, onMessageReceived: Function, tenantId: string) {
99
- if (!this.client) {
100
- const error = new Error('Chat client not initialized');
101
- console.error('[AblyService] joinChannel error:', error);
102
- throw error;
103
- }
104
-
105
- const roomName = `session:${tenantId}:${sessionId}`;
106
-
107
- // Set up raw channel subscription for server messages
108
- if (this.client) {
109
- this.channel = this.client.channels.get(roomName);
110
-
111
- this.channel.on('failed', (stateChange) => {
112
- console.error('[AblyService] Channel failed', {
113
- roomName,
114
- reason: stateChange.reason?.message,
115
- error: stateChange.reason,
116
- });
117
- });
118
-
119
- // Subscribe to assistant/system responses
120
- this.channel.subscribe('ReceiveMessage', (message) => {
121
- try {
122
- // Ensure messageContent is always a string (default to empty string if undefined)
123
- const rawData = message.data;
124
-
125
- const messageContent =
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;
134
-
135
- // Extract downloadUrl from attachments (Ably now returns downloadUrl directly)
136
- // Attachments can be: strings (URLs), objects with downloadUrl, url, or id only
137
- const attachmentUrls: string[] = [];
138
- const attachmentIdsFromAttachments: string[] = [];
139
- for (const attachment of attachments) {
140
- if (typeof attachment === 'string') {
141
- if (attachment.startsWith('http://') || attachment.startsWith('https://')) {
142
- attachmentUrls.push(attachment);
143
- } else {
144
- attachmentIdsFromAttachments.push(attachment);
145
- }
146
- } else if (attachment?.downloadUrl) {
147
- attachmentUrls.push(attachment.downloadUrl);
148
- } else if (attachment?.url) {
149
- attachmentUrls.push(attachment.url);
150
- } else if (attachment?.id && typeof attachment.id === 'string') {
151
- attachmentIdsFromAttachments.push(attachment.id);
152
- }
153
- }
154
- const attachmentIds =
155
- attachmentIdsFromAttachments.length > 0
156
- ? attachmentIdsFromAttachments
157
- : (rawData?.attachmentIds as string[] | undefined) || [];
158
-
159
- // Invoke optional action handler first (non-blocking for message processing)
160
- if (this.onActionReceived && actionType !== undefined) {
161
- try {
162
- void this.onActionReceived(actionType, rawData);
163
- } catch (actionError) {
164
- console.error('[AblyService] Error in action handler callback', {
165
- error: actionError,
166
- actionType,
167
- rawData,
168
- });
169
- }
170
- }
171
-
172
- onMessageReceived(messageContent, senderType, needsAgent, attachmentUrls, attachmentIds);
173
- } catch (error) {
174
- console.error('[AblyService] Error processing message', { error, message });
175
- }
176
- });
177
-
178
- await this.channel.attach();
179
- }
180
- }
181
-
182
- static async stopConnection() {
183
- try {
184
- // Unsubscribe from room messages
185
- if (this.messageUnsubscribe) {
186
- this.messageUnsubscribe();
187
- this.messageUnsubscribe = null;
188
- }
189
-
190
- // Unsubscribe and detach from raw channel
191
- if (this.channel) {
192
- this.channel.unsubscribe();
193
- await this.channel.detach();
194
- this.channel = null;
195
- }
196
-
197
- // Close Ably connection
198
- if (this.client) {
199
- this.client.close();
200
- this.client = null;
201
- }
202
-
203
- this.isConnected = false;
204
- this.sessionId = null;
205
- this.onActionReceived = null;
206
- } catch (error) {
207
- console.error('[AblyService] Error in stopConnection', { error });
208
- // Reset state even if there's an error
209
- this.isConnected = false;
210
- this.sessionId = null;
211
- this.client = null;
212
- this.channel = null;
213
- this.messageUnsubscribe = null;
214
- }
215
- }
216
-
217
- static isConnectionActive(): boolean {
218
- return this.isConnected && this.client?.connection.state === 'connected';
219
- }
220
-
221
- static getConnectionState(): string {
222
- return this.client?.connection.state || 'disconnected';
223
- }
224
-
225
- // Method to manually send a message (if needed for debugging or direct messaging)
226
- static async sendMessage(messageContent: string, senderType: number = 1) {
227
- if (!this.channel || !this.isConnected) {
228
- throw new Error('Connection not active');
229
- }
230
-
231
- try {
232
- await this.channel.publish('message', {
233
- text: messageContent,
234
- metadata: {
235
- senderType,
236
- sentAt: new Date().toISOString(),
237
- },
238
- });
239
- } catch (error) {
240
- throw error;
241
- }
242
- }
243
- }
@@ -1,116 +0,0 @@
1
- import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
2
- import { HelpCenterConfig } from '../lib/types';
3
-
4
- export class ApiService {
5
- private axiosInstance: AxiosInstance;
6
- private config: HelpCenterConfig;
7
- private tokenExpiryTime: number = 0;
8
- private currentToken: string | null = null;
9
-
10
- constructor(config: HelpCenterConfig) {
11
- this.config = config;
12
- this.axiosInstance = axios.create({
13
- baseURL: config.baseUrl,
14
- headers: {
15
- 'Content-Type': 'application/json',
16
- },
17
- });
18
-
19
- this.setupInterceptors();
20
- }
21
-
22
- private setupInterceptors() {
23
- this.axiosInstance.interceptors.request.use(
24
- async (config) => {
25
- const token = await this.getValidToken();
26
- if (token && config.headers) {
27
- config.headers.Authorization = `Bearer ${token}`;
28
- }
29
- return config;
30
- },
31
- (error) => Promise.reject(error)
32
- );
33
-
34
- this.axiosInstance.interceptors.response.use(
35
- (response) => response,
36
- async (error) => {
37
- if (error.response?.status === 401) {
38
- // Token might be expired, try to refresh
39
- const token = await this.getValidToken(true);
40
- if (token && error.config) {
41
- error.config.headers.Authorization = `Bearer ${token}`;
42
- return this.axiosInstance.request(error.config);
43
- }
44
- }
45
- return Promise.reject(error);
46
- }
47
- );
48
- }
49
-
50
- private async getValidToken(forceRefresh = false): Promise<string> {
51
- const currentTime = Math.floor(Date.now() / 1000);
52
-
53
- if (forceRefresh || !this.currentToken || currentTime >= this.tokenExpiryTime) {
54
- try {
55
- const response = await this.config.getToken();
56
- if (!response || !response.token || !response.expiresIn) {
57
- throw new Error('Invalid token response');
58
- }
59
-
60
- this.currentToken = response.token;
61
- this.tokenExpiryTime = currentTime + response.expiresIn;
62
- return this.currentToken;
63
- } catch (error) {
64
- throw error;
65
- }
66
- }
67
-
68
- return this.currentToken;
69
- }
70
-
71
- async get<T>(endpoint: string, config?: AxiosRequestConfig): Promise<T> {
72
- try {
73
- const response = await this.axiosInstance.get<T>(endpoint, config);
74
- return response.data;
75
- } catch (error) {
76
- this.handleError(error);
77
- throw error;
78
- }
79
- }
80
-
81
- async post<T>(endpoint: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {
82
- try {
83
- const response = await this.axiosInstance.post<T>(endpoint, data, config);
84
- return response.data;
85
- } catch (error) {
86
- this.handleError(error);
87
- throw error;
88
- }
89
- }
90
-
91
- async put<T>(endpoint: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {
92
- try {
93
- const response = await this.axiosInstance.put<T>(endpoint, data, config);
94
- return response.data;
95
- } catch (error) {
96
- this.handleError(error);
97
- throw error;
98
- }
99
- }
100
-
101
- async delete<T>(endpoint: string, config?: AxiosRequestConfig): Promise<T> {
102
- try {
103
- const response = await this.axiosInstance.delete<T>(endpoint, config);
104
- return response.data;
105
- } catch (error) {
106
- this.handleError(error);
107
- throw error;
108
- }
109
- }
110
-
111
- private handleError(error: unknown) {
112
- if (this.config.onError) {
113
- this.config.onError(error instanceof Error ? error : new Error('Unknown error occurred'));
114
- }
115
- }
116
- }
package/src/core/api.ts DELETED
@@ -1,278 +0,0 @@
1
- import { defaultLanguage } from '@/i18n';
2
- import { TokenResponse, PresignUploadRequestDto, PresignUploadResponse, PresignDownloadResponse } from '@/lib/types';
3
-
4
- let getTokenFunction: (() => Promise<TokenResponse>) | undefined = undefined;
5
- let baseUrl: string | null = null;
6
-
7
- // Add request cache and connection optimization
8
- const requestCache = new Map<string, { data: any; timestamp: number; ttl: number }>();
9
- const CACHE_TTL = 30000; // 30 seconds cache for non-critical requests
10
- const pendingRequests = new Map<string, Promise<any>>();
11
-
12
- export function initializeAPI(url: string, getToken: () => Promise<TokenResponse>) {
13
- getTokenFunction = getToken;
14
- baseUrl = url;
15
- }
16
-
17
- export async function getValidToken(forceRefresh = false): Promise<string> {
18
- if (!getTokenFunction) {
19
- throw new Error('API module not initialized. Call initializeAPI(getToken) first.');
20
- }
21
-
22
- let storedToken = localStorage.getItem('chatbot-token');
23
- let storedExpiry = localStorage.getItem('chatbot-token-expiry');
24
- const currentTime = Math.floor(Date.now() / 1000);
25
-
26
- // Add buffer time to prevent token expiry during request
27
- const bufferTime = 60; // 1 minute buffer
28
- const isTokenExpiring = storedExpiry && currentTime >= Number(storedExpiry) - bufferTime;
29
-
30
- if (!storedToken || !storedExpiry || isTokenExpiring || forceRefresh) {
31
- try {
32
- const tokenResponse = await getTokenFunction();
33
- storedToken = tokenResponse.token;
34
- storedExpiry = String(currentTime + (tokenResponse.expiresIn ?? 900));
35
-
36
- localStorage.setItem('chatbot-token', storedToken);
37
- localStorage.setItem('chatbot-token-expiry', storedExpiry);
38
- } catch (error) {
39
- throw error;
40
- }
41
- }
42
-
43
- return storedToken;
44
- }
45
-
46
- // Optimized fetch with retry logic and connection pooling
47
- async function fetchWithAuth(url: string, options: RequestInit, retry = true): Promise<Response> {
48
- const headers = new Headers(options.headers);
49
-
50
- try {
51
- const token = await getValidToken();
52
- headers.set('Authorization', `Bearer ${token}`);
53
-
54
- // Add performance optimizations
55
- headers.set('Accept', 'application/json');
56
- headers.set('Accept-Encoding', 'gzip, deflate, br');
57
- headers.set('Connection', 'keep-alive');
58
-
59
- options.headers = headers;
60
-
61
- // Add timeout to prevent hanging requests
62
- const controller = new AbortController();
63
- const timeoutId = setTimeout(() => controller.abort(), 30000); // 30 second timeout
64
-
65
- try {
66
- const response = await fetch(url, {
67
- ...options,
68
- signal: controller.signal,
69
- // Add HTTP/2 optimization hints
70
- cache: 'no-cache',
71
- mode: 'cors',
72
- });
73
-
74
- clearTimeout(timeoutId);
75
-
76
- // Handle 401/403 with token refresh
77
- if ((response.status === 401 || response.status === 403) && retry) {
78
- const newToken = await getValidToken(true);
79
- headers.set('Authorization', `Bearer ${newToken}`);
80
- options.headers = headers;
81
-
82
- // Retry the request with new token
83
- return fetchWithAuth(url, options, false);
84
- }
85
-
86
- return response;
87
- } catch (error) {
88
- clearTimeout(timeoutId);
89
-
90
- if (error instanceof Error && error.name === 'AbortError') {
91
- throw new Error('Request timeout - please try again');
92
- }
93
- throw error;
94
- }
95
- } catch (error) {
96
- throw error;
97
- }
98
- }
99
-
100
- // Cache management functions
101
- function getCachedResponse(cacheKey: string): any | null {
102
- const cached = requestCache.get(cacheKey);
103
- if (cached && Date.now() < cached.timestamp + cached.ttl) {
104
- return cached.data;
105
- }
106
-
107
- if (cached) {
108
- requestCache.delete(cacheKey);
109
- }
110
-
111
- return null;
112
- }
113
-
114
- function setCachedResponse(cacheKey: string, data: any, ttl: number = CACHE_TTL): void {
115
- requestCache.set(cacheKey, {
116
- data,
117
- timestamp: Date.now(),
118
- ttl,
119
- });
120
- }
121
-
122
- // Deduplicate concurrent requests
123
- function getDuplicateRequest(requestKey: string): Promise<any> | null {
124
- return pendingRequests.get(requestKey) || null;
125
- }
126
-
127
- function setPendingRequest(requestKey: string, promise: Promise<any>): void {
128
- pendingRequests.set(requestKey, promise);
129
-
130
- // Clean up after request completes
131
- promise.finally(() => {
132
- pendingRequests.delete(requestKey);
133
- });
134
- }
135
-
136
- export async function apiRequest(
137
- endpoint: string,
138
- method = 'GET',
139
- body: any = null,
140
- options: { cache?: boolean; timeout?: number; language?: 'ar' | 'en' }
141
- ) {
142
- if (!baseUrl) throw new Error('API not initialized');
143
-
144
- const url = `${baseUrl}/${endpoint}`;
145
- const requestKey = `${method}:${endpoint}:${JSON.stringify(body)}`;
146
-
147
- // Check for duplicate in-flight requests (skip for GET: callers receive Response and call .json();
148
- // sharing the same Response causes "body already consumed" when the second caller parses)
149
- const duplicateRequest = getDuplicateRequest(requestKey);
150
- if (duplicateRequest && method !== 'GET') {
151
- return duplicateRequest;
152
- }
153
-
154
- // Check cache for GET requests (except real-time endpoints)
155
- if (method === 'GET' && options.cache !== false && !endpoint.includes('/send-message')) {
156
- const cached = getCachedResponse(requestKey);
157
- if (cached) {
158
- return Promise.resolve(cached);
159
- }
160
- }
161
-
162
- const requestOptions: RequestInit = {
163
- method,
164
- headers: {
165
- 'Content-Type': 'application/json',
166
- 'Cache-Control': method === 'GET' ? 'max-age=30' : 'no-cache',
167
- 'Accept-Language': options.language || defaultLanguage,
168
- },
169
- body: body ? JSON.stringify(body) : null,
170
- };
171
-
172
- const requestPromise = (async () => {
173
- try {
174
- const response = await fetchWithAuth(url, requestOptions);
175
-
176
- if (!response.ok) {
177
- let errorMessage = 'API request failed';
178
-
179
- try {
180
- // Clone response before reading to avoid consuming the body
181
- const errorResponse = response.clone();
182
- const errorData = await errorResponse.json();
183
- errorMessage = errorData.message || errorData.error || errorMessage;
184
- } catch (parseError) {
185
- // If JSON parsing fails, try to get text
186
- try {
187
- const errorResponse = response.clone();
188
- const errorText = await errorResponse.text();
189
- errorMessage = errorText || `HTTP ${response.status}: ${response.statusText}`;
190
- } catch {
191
- errorMessage = `HTTP ${response.status}: ${response.statusText}`;
192
- }
193
- }
194
-
195
- throw new Error(errorMessage);
196
- }
197
-
198
- // Cache successful GET responses
199
- // Note: We clone before caching to avoid consuming the original response body
200
- if (method === 'GET' && options.cache !== false) {
201
- const responseData = response.clone();
202
- const data = await responseData.json();
203
- setCachedResponse(requestKey, { json: () => Promise.resolve(data) });
204
- }
205
-
206
- // Return the original response - it's body hasn't been consumed yet
207
- // (we only cloned for caching, and the clone was consumed)
208
- return response;
209
- } catch (error) {
210
- throw error;
211
- }
212
- })();
213
-
214
- // Track pending request
215
- setPendingRequest(requestKey, requestPromise);
216
-
217
- return requestPromise;
218
- }
219
-
220
- export async function presignUpload(
221
- chatSessionId: string,
222
- file: File,
223
- language: 'ar' | 'en' = defaultLanguage
224
- ): Promise<PresignUploadResponse> {
225
- const requestBody: PresignUploadRequestDto = {
226
- name: file.name,
227
- contentType: file.type,
228
- sizeBytes: file.size,
229
- pathData: {
230
- type: 1,
231
- chatSessionId: chatSessionId,
232
- },
233
- };
234
-
235
- const response = await apiRequest('NewFile/presign-upload', 'POST', requestBody, { language });
236
-
237
- if (!response.ok) {
238
- throw new Error('Failed to get presigned upload URL');
239
- }
240
-
241
- return await response.json();
242
- }
243
- export async function presignDownload(fileId: string, language: 'ar' | 'en' = defaultLanguage): Promise<PresignDownloadResponse> {
244
- try {
245
- const response = await apiRequest(`NewFile/${fileId}/presign-download`, 'GET', null, {
246
- language,
247
- cache: false, // Don't cache presigned URLs as they have expiration times
248
- });
249
-
250
- // If response is not ok, apiRequest would have thrown an error already
251
- // So we can safely read the JSON here
252
- try {
253
- return await response.json();
254
- } catch (jsonError) {
255
- // If JSON parsing fails, the response body might have been consumed
256
- // or the response might not be valid JSON
257
- if (jsonError instanceof Error) {
258
- if (jsonError.message.includes('already read')) {
259
- throw new Error(`Failed to parse response for file ${fileId}: Response body was already consumed`);
260
- }
261
- throw new Error(`Failed to parse response for file ${fileId}: ${jsonError.message}`);
262
- }
263
- throw new Error(`Failed to parse response for file ${fileId}`);
264
- }
265
- } catch (error) {
266
- // Handle all types of errors
267
- if (error instanceof Error) {
268
- // If it's already a descriptive error from apiRequest, re-throw it
269
- if (error.message.includes('API request failed') || error.message.includes('HTTP')) {
270
- throw new Error(`Failed to get presigned download URL for file ${fileId}: ${error.message}`);
271
- }
272
- // Otherwise, re-throw with context
273
- throw error;
274
- }
275
- // Handle non-Error types
276
- throw new Error(`Failed to get presigned download URL for file ${fileId}: ${String(error)}`);
277
- }
278
- }
@@ -1,35 +0,0 @@
1
- type TokenResponse = {
2
- token: string
3
- expiresIn: number
4
- }
5
-
6
- export class TokenService {
7
- private baseUrl: string
8
-
9
- constructor(baseUrl: string) {
10
- this.baseUrl = baseUrl
11
- }
12
-
13
- async getToken(): Promise<TokenResponse> {
14
- try {
15
- const response = await fetch(`${this.baseUrl}/Auth/client/get-babylai-token`, {
16
- method: 'POST',
17
- headers: {
18
- 'Content-Type': 'application/json'
19
- }
20
- })
21
-
22
- if (!response.ok) {
23
- throw new Error('Failed to fetch token')
24
- }
25
-
26
- const data = await response.json()
27
- return {
28
- token: data.token,
29
- expiresIn: data.expiresIn || 3600 // Default to 1 hour if not provided
30
- }
31
- } catch (error) {
32
- throw new Error('Failed to get authentication token')
33
- }
34
- }
35
- }