l-min-components 1.6.1265 → 1.6.1267
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
|
@@ -326,6 +326,9 @@ const useMessageKit = (/*affiliatesActive*/) => {
|
|
|
326
326
|
isProcessingQueue: false,
|
|
327
327
|
});
|
|
328
328
|
|
|
329
|
+
// Ref for synchronous queue processing lock
|
|
330
|
+
const isCurrentlyProcessingRef = useRef(false);
|
|
331
|
+
|
|
329
332
|
// State for rooms that should be automatically marked as read
|
|
330
333
|
const [autoReadRoomIds, setAutoReadRoomIds] = useState(() => new Set());
|
|
331
334
|
|
|
@@ -828,28 +831,45 @@ const useMessageKit = (/*affiliatesActive*/) => {
|
|
|
828
831
|
[setState]
|
|
829
832
|
); // Dependency: setState
|
|
830
833
|
|
|
831
|
-
// Effect to process the message queue sequentially
|
|
834
|
+
// Effect to process the message queue sequentially using a Ref lock
|
|
832
835
|
useEffect(() => {
|
|
833
|
-
//
|
|
834
|
-
|
|
836
|
+
// **Synchronous Check + Claim:**
|
|
837
|
+
// Use the ref for an immediate check. If already processing, bail out.
|
|
838
|
+
// Also check if the queue has items.
|
|
839
|
+
if (isCurrentlyProcessingRef.current || state.messageQueue.length === 0) {
|
|
835
840
|
return;
|
|
836
841
|
}
|
|
837
842
|
|
|
838
|
-
//
|
|
843
|
+
// **Claim the processing slot synchronously**
|
|
844
|
+
isCurrentlyProcessingRef.current = true;
|
|
845
|
+
|
|
846
|
+
// Set the state flag as well (useful for potential UI indicators or triggering re-renders upon completion)
|
|
847
|
+
// Note: This state update is still async, but the ref prevents the race condition.
|
|
839
848
|
setState((prevState) => ({ ...prevState, isProcessingQueue: true }));
|
|
840
849
|
|
|
841
|
-
// Get the next message from the front of the queue
|
|
850
|
+
// Get the next message details from the front of the queue.
|
|
851
|
+
// Reading state here is fine, as we've already claimed the slot.
|
|
842
852
|
const messageToSend = state.messageQueue[0];
|
|
853
|
+
|
|
854
|
+
// Safety check: Ensure messageToSend exists (queue might have emptied)
|
|
855
|
+
if (!messageToSend) {
|
|
856
|
+
isCurrentlyProcessingRef.current = false; // Release the claim
|
|
857
|
+
setState((prevState) => ({ ...prevState, isProcessingQueue: false }));
|
|
858
|
+
console.warn(
|
|
859
|
+
"Queue Processor: Attempted to process but queue was empty."
|
|
860
|
+
);
|
|
861
|
+
return; // Nothing to process
|
|
862
|
+
}
|
|
863
|
+
|
|
843
864
|
const { tempId, courseId, accountId, text, media } = messageToSend;
|
|
844
|
-
const pendingKey = `pending_${courseId}_${accountId}`;
|
|
865
|
+
const pendingKey = `pending_${courseId}_${accountId}`;
|
|
845
866
|
|
|
846
|
-
// Define the async task for sending this message
|
|
867
|
+
// Define the async task for sending this specific message.
|
|
847
868
|
const processMessage = async () => {
|
|
848
869
|
try {
|
|
849
870
|
// --- Make API Call ---
|
|
850
871
|
let response;
|
|
851
872
|
// TODO: Handle media preparation (e.g., check if 'media' is File, create FormData)
|
|
852
|
-
// This example assumes 'media' is directly usable or requires minimal processing
|
|
853
873
|
const requestData = { text, media };
|
|
854
874
|
const requestParams = {
|
|
855
875
|
account_id: accountId,
|
|
@@ -857,7 +877,6 @@ const useMessageKit = (/*affiliatesActive*/) => {
|
|
|
857
877
|
_account: selectedAccount.id,
|
|
858
878
|
};
|
|
859
879
|
|
|
860
|
-
// (Use the same API call logic as before)
|
|
861
880
|
if (selectedAccount.type.toLowerCase() === "enterprise") {
|
|
862
881
|
response = await request({
|
|
863
882
|
url: "/notify/v1/enterprise/chats/",
|
|
@@ -916,11 +935,23 @@ const useMessageKit = (/*affiliatesActive*/) => {
|
|
|
916
935
|
is_delivered: sentMessageData.is_delivered,
|
|
917
936
|
created_at: sentMessageData.created_at,
|
|
918
937
|
user_message: sentMessageData.user_message,
|
|
919
|
-
date: getLocalDateString(sentMessageData.created_at), //
|
|
938
|
+
date: getLocalDateString(sentMessageData.created_at), // Use existing helper
|
|
920
939
|
};
|
|
921
940
|
|
|
922
941
|
// Update state atomically after success
|
|
923
942
|
setState((prevState) => {
|
|
943
|
+
// Safety check: Ensure the message we processed is still the first one
|
|
944
|
+
if (
|
|
945
|
+
prevState.messageQueue.length === 0 ||
|
|
946
|
+
prevState.messageQueue[0].tempId !== tempId
|
|
947
|
+
) {
|
|
948
|
+
console.warn(
|
|
949
|
+
`Queue Processor: Mismatch processing successful message ${tempId}. Queue changed unexpectedly.`
|
|
950
|
+
);
|
|
951
|
+
// Don't modify state if the queue front doesn't match
|
|
952
|
+
return prevState;
|
|
953
|
+
}
|
|
954
|
+
|
|
924
955
|
// 1. Remove optimistic message
|
|
925
956
|
let updatedOptimisticMessages = { ...prevState.optimisticMessages };
|
|
926
957
|
if (updatedOptimisticMessages[pendingKey]) {
|
|
@@ -965,12 +996,11 @@ const useMessageKit = (/*affiliatesActive*/) => {
|
|
|
965
996
|
date: messageDateLabel,
|
|
966
997
|
messages: [newChatMessage],
|
|
967
998
|
});
|
|
968
|
-
// Sort groups using helper function
|
|
969
999
|
updatedChatHistory.sort(
|
|
970
1000
|
(a, b) =>
|
|
971
1001
|
getSortableTimestampForGroup(b) -
|
|
972
1002
|
getSortableTimestampForGroup(a)
|
|
973
|
-
);
|
|
1003
|
+
); // Use helper
|
|
974
1004
|
}
|
|
975
1005
|
updatedChats[confirmedRoomId] = updatedChatHistory;
|
|
976
1006
|
|
|
@@ -982,7 +1012,6 @@ const useMessageKit = (/*affiliatesActive*/) => {
|
|
|
982
1012
|
const roomIdx = cg.rooms.findIndex((r) => r.id === confirmedRoomId);
|
|
983
1013
|
if (roomIdx !== -1) {
|
|
984
1014
|
roomExists = true;
|
|
985
|
-
// Create a new room object to ensure immutability
|
|
986
1015
|
const updatedRoom = {
|
|
987
1016
|
...cg.rooms[roomIdx],
|
|
988
1017
|
last_message: {
|
|
@@ -992,22 +1021,22 @@ const useMessageKit = (/*affiliatesActive*/) => {
|
|
|
992
1021
|
created_at: newChatMessage.created_at,
|
|
993
1022
|
},
|
|
994
1023
|
};
|
|
995
|
-
// Create a new rooms array for the course group
|
|
996
1024
|
const updatedRooms = [...cg.rooms];
|
|
997
1025
|
updatedRooms[roomIdx] = updatedRoom;
|
|
998
|
-
// Return a new course group object
|
|
999
1026
|
return { ...cg, rooms: updatedRooms };
|
|
1000
1027
|
}
|
|
1001
1028
|
return cg;
|
|
1002
1029
|
});
|
|
1003
|
-
// Trigger refetch if room metadata wasn't found (handled outside setState)
|
|
1004
|
-
if (!roomExists) {
|
|
1005
|
-
setTimeout(() => getMessageRoomsByCourses(null, true), 0);
|
|
1006
|
-
}
|
|
1007
1030
|
|
|
1008
1031
|
// 4. Remove processed item from queue (immutable update)
|
|
1009
1032
|
const updatedQueue = prevState.messageQueue.slice(1);
|
|
1010
1033
|
|
|
1034
|
+
// Trigger refetch if room metadata wasn't found (handled outside setState)
|
|
1035
|
+
if (!roomExists && typeof getMessageRoomsByCourses === "function") {
|
|
1036
|
+
// Check if function exists
|
|
1037
|
+
setTimeout(() => getMessageRoomsByCourses(null, true), 0);
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1011
1040
|
// Return the combined state updates
|
|
1012
1041
|
return {
|
|
1013
1042
|
...prevState,
|
|
@@ -1015,7 +1044,7 @@ const useMessageKit = (/*affiliatesActive*/) => {
|
|
|
1015
1044
|
chats: updatedChats,
|
|
1016
1045
|
roomsByCourses: updatedRoomsByCourses,
|
|
1017
1046
|
messageQueue: updatedQueue,
|
|
1018
|
-
//
|
|
1047
|
+
isProcessingQueue: true, // Keep true until finally block resets it below
|
|
1019
1048
|
};
|
|
1020
1049
|
});
|
|
1021
1050
|
} catch (error) {
|
|
@@ -1025,25 +1054,33 @@ const useMessageKit = (/*affiliatesActive*/) => {
|
|
|
1025
1054
|
error
|
|
1026
1055
|
);
|
|
1027
1056
|
setState((prevState) => {
|
|
1057
|
+
// Safety check for queue consistency
|
|
1058
|
+
if (
|
|
1059
|
+
prevState.messageQueue.length === 0 ||
|
|
1060
|
+
prevState.messageQueue[0].tempId !== tempId
|
|
1061
|
+
) {
|
|
1062
|
+
console.warn(
|
|
1063
|
+
`Queue Processor: Mismatch processing failed message ${tempId}. Queue changed unexpectedly.`
|
|
1064
|
+
);
|
|
1065
|
+
return prevState;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1028
1068
|
// 1. Update optimistic message status to 'failed'
|
|
1029
1069
|
let updatedOptimisticMessages = { ...prevState.optimisticMessages };
|
|
1030
1070
|
if (updatedOptimisticMessages[pendingKey]) {
|
|
1031
1071
|
const messageIndex = updatedOptimisticMessages[
|
|
1032
1072
|
pendingKey
|
|
1033
1073
|
].findIndex((msg) => msg.tempId === tempId);
|
|
1034
|
-
// Only update if found and still pending (might have been handled differently elsewhere?)
|
|
1035
1074
|
if (
|
|
1036
1075
|
messageIndex !== -1 &&
|
|
1037
1076
|
updatedOptimisticMessages[pendingKey][messageIndex].status ===
|
|
1038
1077
|
"pending"
|
|
1039
1078
|
) {
|
|
1040
|
-
// Create new message object for immutability
|
|
1041
1079
|
const failedMessage = {
|
|
1042
1080
|
...updatedOptimisticMessages[pendingKey][messageIndex],
|
|
1043
1081
|
status: "failed",
|
|
1044
1082
|
error: error?.message || "Unknown error",
|
|
1045
1083
|
};
|
|
1046
|
-
// Create new array for the pending key
|
|
1047
1084
|
const updatedPendingList = [
|
|
1048
1085
|
...updatedOptimisticMessages[pendingKey],
|
|
1049
1086
|
];
|
|
@@ -1062,33 +1099,39 @@ const useMessageKit = (/*affiliatesActive*/) => {
|
|
|
1062
1099
|
|
|
1063
1100
|
// 2. Remove failed item from queue (immutable update)
|
|
1064
1101
|
const updatedQueue = prevState.messageQueue.slice(1);
|
|
1065
|
-
|
|
1102
|
+
// Return the combined state updates
|
|
1066
1103
|
return {
|
|
1067
1104
|
...prevState,
|
|
1068
1105
|
optimisticMessages: updatedOptimisticMessages,
|
|
1069
1106
|
messageQueue: updatedQueue,
|
|
1070
|
-
//
|
|
1107
|
+
isProcessingQueue: true, // Keep true until finally block resets it below
|
|
1071
1108
|
};
|
|
1072
1109
|
});
|
|
1073
1110
|
} finally {
|
|
1074
1111
|
// --- Finish Processing ---
|
|
1075
|
-
//
|
|
1112
|
+
// **Crucial:** Release the synchronous lock first.
|
|
1113
|
+
isCurrentlyProcessingRef.current = false;
|
|
1114
|
+
// Then update the state flag. This state update will trigger a re-render.
|
|
1115
|
+
// If the queue still has items, the effect will run again, see the ref is false,
|
|
1116
|
+
// claim the lock, and start processing the next item.
|
|
1076
1117
|
setState((prevState) => ({ ...prevState, isProcessingQueue: false }));
|
|
1077
1118
|
}
|
|
1078
1119
|
};
|
|
1079
1120
|
|
|
1080
|
-
// Execute the async sending logic
|
|
1121
|
+
// Execute the async sending logic for the current item.
|
|
1081
1122
|
processMessage();
|
|
1082
1123
|
|
|
1083
|
-
// Dependencies
|
|
1084
|
-
//
|
|
1124
|
+
// Dependencies:
|
|
1125
|
+
// - `state.isProcessingQueue`: Triggers check when processing finishes.
|
|
1126
|
+
// - `state.messageQueue`: Triggers check when queue content changes.
|
|
1127
|
+
// - `request`, `selectedAccount`, `affiliateAccount`, `getMessageRoomsByCourses`:
|
|
1128
|
+
// Required by exhaustive-deps for use within the effect's scope.
|
|
1085
1129
|
}, [
|
|
1086
1130
|
state.isProcessingQueue,
|
|
1087
1131
|
state.messageQueue,
|
|
1088
1132
|
// request,
|
|
1089
1133
|
selectedAccount,
|
|
1090
1134
|
affiliateAccount,
|
|
1091
|
-
// getMessageRoomsByCourses,
|
|
1092
1135
|
]);
|
|
1093
1136
|
|
|
1094
1137
|
/**
|