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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "l-min-components",
3
- "version": "1.6.1265",
3
+ "version": "1.6.1267",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src/assets",
@@ -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
- // Exit if already processing or queue is empty
834
- if (state.isProcessingQueue || state.messageQueue.length === 0) {
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
- // --- Start Processing ---
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}`; // Reconstruct key
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), // Still useful for basic date info if needed
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
- // isProcessingQueue reset happens in 'finally' block
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
- // isProcessingQueue reset happens in 'finally' block
1107
+ isProcessingQueue: true, // Keep true until finally block resets it below
1071
1108
  };
1072
1109
  });
1073
1110
  } finally {
1074
1111
  // --- Finish Processing ---
1075
- // Always ensure processing flag is reset, even on error
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 for the effect: react when queue or processing status changes
1084
- // Also depend on selectedAccount and affiliateAccount for API calls within the processor
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
  /**