@planningcenter/chat-react-native 4.0.1-rc.3 → 4.2.0-rc.0
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/build/hooks/use_message_create_or_update.d.ts +99 -4
- package/build/hooks/use_message_create_or_update.d.ts.map +1 -1
- package/build/hooks/use_message_create_or_update.js +47 -6
- package/build/hooks/use_message_create_or_update.js.map +1 -1
- package/build/types/resources/message.d.ts +1 -0
- package/build/types/resources/message.d.ts.map +1 -1
- package/build/types/resources/message.js.map +1 -1
- package/build/utils/cache/messages_cache.d.ts.map +1 -1
- package/build/utils/cache/messages_cache.js +4 -6
- package/build/utils/cache/messages_cache.js.map +1 -1
- package/build/utils/cache/optimistically_create_message.d.ts +2 -1
- package/build/utils/cache/optimistically_create_message.d.ts.map +1 -1
- package/build/utils/cache/optimistically_create_message.js +4 -2
- package/build/utils/cache/optimistically_create_message.js.map +1 -1
- package/build/utils/jolt/transform_message_event_data_to_message_resource.d.ts.map +1 -1
- package/build/utils/jolt/transform_message_event_data_to_message_resource.js +1 -0
- package/build/utils/jolt/transform_message_event_data_to_message_resource.js.map +1 -1
- package/build/utils/request/messages_data_options.d.ts.map +1 -1
- package/build/utils/request/messages_data_options.js +1 -1
- package/build/utils/request/messages_data_options.js.map +1 -1
- package/package.json +2 -2
- package/src/hooks/__tests__/use_message_create_or_update.test.tsx +110 -0
- package/src/hooks/use_message_create_or_update.ts +29 -16
- package/src/types/resources/message.ts +1 -0
- package/src/utils/cache/__tests__/messages_cache.test.ts +89 -0
- package/src/utils/cache/messages_cache.ts +6 -10
- package/src/utils/cache/optimistically_create_message.ts +3 -0
- package/src/utils/jolt/transform_message_event_data_to_message_resource.ts +1 -0
- package/src/utils/request/messages_data_options.ts +1 -0
|
@@ -5,11 +5,106 @@ interface Props {
|
|
|
5
5
|
message?: MessageResource;
|
|
6
6
|
replyRootId?: string | null;
|
|
7
7
|
}
|
|
8
|
-
|
|
8
|
+
interface MessageMutationVariables {
|
|
9
9
|
text: string;
|
|
10
10
|
attachments?: DenormalizedAttachmentResourceForCreate[];
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
idempotentKey?: string;
|
|
12
|
+
}
|
|
13
|
+
type ResolvedMessageMutationVariables = MessageMutationVariables & {
|
|
14
|
+
idempotentKey: string;
|
|
15
|
+
};
|
|
16
|
+
export declare function useMessageCreateOrUpdate({ conversationId, message, replyRootId }: Props): {
|
|
17
|
+
mutate: (variables: MessageMutationVariables, options?: Parameters<import("@tanstack/react-query").UseMutateFunction<ApiResource<MessageResource>, Error, ResolvedMessageMutationVariables, {
|
|
18
|
+
message: MessageResource;
|
|
19
|
+
}>>[1]) => void;
|
|
20
|
+
mutateAsync: (variables: MessageMutationVariables, options?: Parameters<import("@tanstack/react-query").UseMutateAsyncFunction<ApiResource<MessageResource>, Error, ResolvedMessageMutationVariables, {
|
|
21
|
+
message: MessageResource;
|
|
22
|
+
}>>[1]) => Promise<ApiResource<MessageResource>>;
|
|
23
|
+
data: undefined;
|
|
24
|
+
variables: undefined;
|
|
25
|
+
error: null;
|
|
26
|
+
isError: false;
|
|
27
|
+
isIdle: true;
|
|
28
|
+
isPending: false;
|
|
29
|
+
isSuccess: false;
|
|
30
|
+
status: "idle";
|
|
31
|
+
reset: () => void;
|
|
32
|
+
context: {
|
|
33
|
+
message: MessageResource;
|
|
34
|
+
} | undefined;
|
|
35
|
+
failureCount: number;
|
|
36
|
+
failureReason: Error | null;
|
|
37
|
+
isPaused: boolean;
|
|
38
|
+
submittedAt: number;
|
|
39
|
+
} | {
|
|
40
|
+
mutate: (variables: MessageMutationVariables, options?: Parameters<import("@tanstack/react-query").UseMutateFunction<ApiResource<MessageResource>, Error, ResolvedMessageMutationVariables, {
|
|
41
|
+
message: MessageResource;
|
|
42
|
+
}>>[1]) => void;
|
|
43
|
+
mutateAsync: (variables: MessageMutationVariables, options?: Parameters<import("@tanstack/react-query").UseMutateAsyncFunction<ApiResource<MessageResource>, Error, ResolvedMessageMutationVariables, {
|
|
44
|
+
message: MessageResource;
|
|
45
|
+
}>>[1]) => Promise<ApiResource<MessageResource>>;
|
|
46
|
+
data: undefined;
|
|
47
|
+
variables: ResolvedMessageMutationVariables;
|
|
48
|
+
error: null;
|
|
49
|
+
isError: false;
|
|
50
|
+
isIdle: false;
|
|
51
|
+
isPending: true;
|
|
52
|
+
isSuccess: false;
|
|
53
|
+
status: "pending";
|
|
54
|
+
reset: () => void;
|
|
55
|
+
context: {
|
|
56
|
+
message: MessageResource;
|
|
57
|
+
} | undefined;
|
|
58
|
+
failureCount: number;
|
|
59
|
+
failureReason: Error | null;
|
|
60
|
+
isPaused: boolean;
|
|
61
|
+
submittedAt: number;
|
|
62
|
+
} | {
|
|
63
|
+
mutate: (variables: MessageMutationVariables, options?: Parameters<import("@tanstack/react-query").UseMutateFunction<ApiResource<MessageResource>, Error, ResolvedMessageMutationVariables, {
|
|
64
|
+
message: MessageResource;
|
|
65
|
+
}>>[1]) => void;
|
|
66
|
+
mutateAsync: (variables: MessageMutationVariables, options?: Parameters<import("@tanstack/react-query").UseMutateAsyncFunction<ApiResource<MessageResource>, Error, ResolvedMessageMutationVariables, {
|
|
67
|
+
message: MessageResource;
|
|
68
|
+
}>>[1]) => Promise<ApiResource<MessageResource>>;
|
|
69
|
+
data: undefined;
|
|
70
|
+
error: Error;
|
|
71
|
+
variables: ResolvedMessageMutationVariables;
|
|
72
|
+
isError: true;
|
|
73
|
+
isIdle: false;
|
|
74
|
+
isPending: false;
|
|
75
|
+
isSuccess: false;
|
|
76
|
+
status: "error";
|
|
77
|
+
reset: () => void;
|
|
78
|
+
context: {
|
|
79
|
+
message: MessageResource;
|
|
80
|
+
} | undefined;
|
|
81
|
+
failureCount: number;
|
|
82
|
+
failureReason: Error | null;
|
|
83
|
+
isPaused: boolean;
|
|
84
|
+
submittedAt: number;
|
|
85
|
+
} | {
|
|
86
|
+
mutate: (variables: MessageMutationVariables, options?: Parameters<import("@tanstack/react-query").UseMutateFunction<ApiResource<MessageResource>, Error, ResolvedMessageMutationVariables, {
|
|
87
|
+
message: MessageResource;
|
|
88
|
+
}>>[1]) => void;
|
|
89
|
+
mutateAsync: (variables: MessageMutationVariables, options?: Parameters<import("@tanstack/react-query").UseMutateAsyncFunction<ApiResource<MessageResource>, Error, ResolvedMessageMutationVariables, {
|
|
90
|
+
message: MessageResource;
|
|
91
|
+
}>>[1]) => Promise<ApiResource<MessageResource>>;
|
|
92
|
+
data: ApiResource<MessageResource>;
|
|
93
|
+
error: null;
|
|
94
|
+
variables: ResolvedMessageMutationVariables;
|
|
95
|
+
isError: false;
|
|
96
|
+
isIdle: false;
|
|
97
|
+
isPending: false;
|
|
98
|
+
isSuccess: true;
|
|
99
|
+
status: "success";
|
|
100
|
+
reset: () => void;
|
|
101
|
+
context: {
|
|
102
|
+
message: MessageResource;
|
|
103
|
+
} | undefined;
|
|
104
|
+
failureCount: number;
|
|
105
|
+
failureReason: Error | null;
|
|
106
|
+
isPaused: boolean;
|
|
107
|
+
submittedAt: number;
|
|
108
|
+
};
|
|
14
109
|
export {};
|
|
15
110
|
//# sourceMappingURL=use_message_create_or_update.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use_message_create_or_update.d.ts","sourceRoot":"","sources":["../../src/hooks/use_message_create_or_update.ts"],"names":[],"mappings":"AAEA,OAAO,EAAiB,WAAW,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AACtE,OAAO,EAAE,uCAAuC,EAAE,MAAM,gEAAgE,CAAA;AAexH,UAAU,KAAK;IACb,cAAc,EAAE,MAAM,CAAA;IACtB,OAAO,CAAC,EAAE,eAAe,CAAA;IACzB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5B;AAED,wBAAgB,wBAAwB,CAAC,EAAE,cAAc,EAAE,OAAO,EAAE,WAAW,EAAE,EAAE,KAAK;
|
|
1
|
+
{"version":3,"file":"use_message_create_or_update.d.ts","sourceRoot":"","sources":["../../src/hooks/use_message_create_or_update.ts"],"names":[],"mappings":"AAEA,OAAO,EAAiB,WAAW,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AACtE,OAAO,EAAE,uCAAuC,EAAE,MAAM,gEAAgE,CAAA;AAexH,UAAU,KAAK;IACb,cAAc,EAAE,MAAM,CAAA;IACtB,OAAO,CAAC,EAAE,eAAe,CAAA;IACzB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5B;AAED,UAAU,wBAAwB;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,CAAC,EAAE,uCAAuC,EAAE,CAAA;IACvD,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,KAAK,gCAAgC,GAAG,wBAAwB,GAAG;IAAE,aAAa,EAAE,MAAM,CAAA;CAAE,CAAA;AAE5F,wBAAgB,wBAAwB,CAAC,EAAE,cAAc,EAAE,OAAO,EAAE,WAAW,EAAE,EAAE,KAAK;wBAiIzE,wBAAwB,YACzB,UAAU;;OAAwB,CAAC,CAAC,CAAC;6BAIpC,wBAAwB,YACzB,UAAU;;OAA6B,CAAC,CAAC,CAAC;;;;;;;;;;;;;;;;;;wBANzC,wBAAwB,YACzB,UAAU;;OAAwB,CAAC,CAAC,CAAC;6BAIpC,wBAAwB,YACzB,UAAU;;OAA6B,CAAC,CAAC,CAAC;;;;;;;;;;;;;;;;;;wBANzC,wBAAwB,YACzB,UAAU;;OAAwB,CAAC,CAAC,CAAC;6BAIpC,wBAAwB,YACzB,UAAU;;OAA6B,CAAC,CAAC,CAAC;;;;;;;;;;;;;;;;;;wBANzC,wBAAwB,YACzB,UAAU;;OAAwB,CAAC,CAAC,CAAC;6BAIpC,wBAAwB,YACzB,UAAU;;OAA6B,CAAC,CAAC,CAAC;;;;;;;;;;;;;;;;;EAIvD"}
|
|
@@ -10,7 +10,7 @@ import { getMessagesQueryKey, getMessagesRequestArgs } from '../utils/request/ge
|
|
|
10
10
|
import { useApiClient } from './use_api_client';
|
|
11
11
|
import { useCurrentPerson } from './use_current_person';
|
|
12
12
|
export function useMessageCreateOrUpdate(t0) {
|
|
13
|
-
const $ = _c(
|
|
13
|
+
const $ = _c(33);
|
|
14
14
|
const {
|
|
15
15
|
conversationId,
|
|
16
16
|
message,
|
|
@@ -33,7 +33,8 @@ export function useMessageCreateOrUpdate(t0) {
|
|
|
33
33
|
t2 = t3 => {
|
|
34
34
|
const {
|
|
35
35
|
text,
|
|
36
|
-
attachments
|
|
36
|
+
attachments,
|
|
37
|
+
idempotentKey
|
|
37
38
|
} = t3;
|
|
38
39
|
const requestParams = getMessagesRequestArgs({
|
|
39
40
|
conversation_id: conversationId
|
|
@@ -51,7 +52,6 @@ export function useMessageCreateOrUpdate(t0) {
|
|
|
51
52
|
} : {})
|
|
52
53
|
};
|
|
53
54
|
if (!isEditing) {
|
|
54
|
-
const idempotentKey = insecureUUID();
|
|
55
55
|
attributes.idempotent_key = idempotentKey;
|
|
56
56
|
startMessageCreationTracking(idempotentKey);
|
|
57
57
|
}
|
|
@@ -89,7 +89,8 @@ export function useMessageCreateOrUpdate(t0) {
|
|
|
89
89
|
t3 = async t4 => {
|
|
90
90
|
const {
|
|
91
91
|
text: text_0,
|
|
92
|
-
attachments: attachments_0
|
|
92
|
+
attachments: attachments_0,
|
|
93
|
+
idempotentKey: idempotentKey_0
|
|
93
94
|
} = t4;
|
|
94
95
|
if (message && isEditing) {
|
|
95
96
|
const optimisticMessage = optimisticallyUpdateMessage({
|
|
@@ -108,7 +109,8 @@ export function useMessageCreateOrUpdate(t0) {
|
|
|
108
109
|
text: text_0,
|
|
109
110
|
attachments: attachments_0,
|
|
110
111
|
currentPerson,
|
|
111
|
-
replyRootId
|
|
112
|
+
replyRootId,
|
|
113
|
+
idempotentKey: idempotentKey_0
|
|
112
114
|
});
|
|
113
115
|
return {
|
|
114
116
|
message: optimisticMessage_0
|
|
@@ -193,7 +195,46 @@ export function useMessageCreateOrUpdate(t0) {
|
|
|
193
195
|
t6 = $[22];
|
|
194
196
|
}
|
|
195
197
|
const mutation = useMutation(t6);
|
|
196
|
-
|
|
198
|
+
let t7;
|
|
199
|
+
if ($[23] !== message?.idempotentKey) {
|
|
200
|
+
t7 = variables_1 => ({
|
|
201
|
+
...variables_1,
|
|
202
|
+
idempotentKey: variables_1.idempotentKey ?? message?.idempotentKey ?? insecureUUID()
|
|
203
|
+
});
|
|
204
|
+
$[23] = message?.idempotentKey;
|
|
205
|
+
$[24] = t7;
|
|
206
|
+
} else {
|
|
207
|
+
t7 = $[24];
|
|
208
|
+
}
|
|
209
|
+
const resolveVariables = t7;
|
|
210
|
+
let mutate;
|
|
211
|
+
let mutateAsync;
|
|
212
|
+
if ($[25] !== mutation || $[26] !== resolveVariables) {
|
|
213
|
+
mutate = (variables_2, options) => mutation.mutate(resolveVariables(variables_2), options);
|
|
214
|
+
mutateAsync = (variables_3, options_0) => mutation.mutateAsync(resolveVariables(variables_3), options_0);
|
|
215
|
+
$[25] = mutation;
|
|
216
|
+
$[26] = resolveVariables;
|
|
217
|
+
$[27] = mutate;
|
|
218
|
+
$[28] = mutateAsync;
|
|
219
|
+
} else {
|
|
220
|
+
mutate = $[27];
|
|
221
|
+
mutateAsync = $[28];
|
|
222
|
+
}
|
|
223
|
+
let t8;
|
|
224
|
+
if ($[29] !== mutate || $[30] !== mutateAsync || $[31] !== mutation) {
|
|
225
|
+
t8 = {
|
|
226
|
+
...mutation,
|
|
227
|
+
mutate,
|
|
228
|
+
mutateAsync
|
|
229
|
+
};
|
|
230
|
+
$[29] = mutate;
|
|
231
|
+
$[30] = mutateAsync;
|
|
232
|
+
$[31] = mutation;
|
|
233
|
+
$[32] = t8;
|
|
234
|
+
} else {
|
|
235
|
+
t8 = $[32];
|
|
236
|
+
}
|
|
237
|
+
return t8;
|
|
197
238
|
}
|
|
198
239
|
|
|
199
240
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use_message_create_or_update.js","names":["useMutation","chatQueryClient","deleteRecordInPagesData","Haptic","updateOrCreateRecordInPagesData","updateRecordInPagesData","isNewMessage","mergeMessageUpdate","optimisticallyCreateMessage","optimisticallyUpdateMessage","startMessageCreationTracking","getMessagesQueryKey","getMessagesRequestArgs","useApiClient","useCurrentPerson","useMessageCreateOrUpdate","t0","$","_c","conversationId","message","replyRootId","messageId","id","t1","isEditing","apiClient","currentPerson","t2","t3","text","attachments","requestParams","conversation_id","fieldsWithValueJoined","Object","fromEntries","entries","data","fields","map","_temp","attributes","reply_root","idempotentKey","insecureUUID","idempotent_key","type","chat","patch","url","post","t4","text_0","attachments_0","optimisticMessage","optimisticMessage_0","t5","error","variables","context","optimisticMessage_1","notificationError","queryKey","reply_root_id","setQueryData","data_0","record","processRecord","_next","prev","pending","result","variables_0","context_0","optimisticMessage_2","updatedMessage","queryKey_0","data_1","data_2","_temp2","t6","mutationFn","onMutate","onError","onSuccess","mutation","current","k","v","join","replace","Math","floor","random","toString"],"sources":["../../src/hooks/use_message_create_or_update.ts"],"sourcesContent":["import { InfiniteData, useMutation } from '@tanstack/react-query'\nimport { chatQueryClient } from '../contexts/api_provider'\nimport { ApiCollection, ApiResource, MessageResource } from '../types'\nimport { DenormalizedAttachmentResourceForCreate } from '../types/resources/denormalized_attachment_resource_for_create'\nimport {\n deleteRecordInPagesData,\n Haptic,\n updateOrCreateRecordInPagesData,\n updateRecordInPagesData,\n} from '../utils'\nimport { isNewMessage, mergeMessageUpdate } from '../utils/cache/messages_cache'\nimport { optimisticallyCreateMessage } from '../utils/cache/optimistically_create_message'\nimport { optimisticallyUpdateMessage } from '../utils/cache/optimistically_update_message'\nimport { startMessageCreationTracking } from '../utils/performance_tracking'\nimport { getMessagesQueryKey, getMessagesRequestArgs } from '../utils/request/get_messages'\nimport { useApiClient } from './use_api_client'\nimport { useCurrentPerson } from './use_current_person'\n\ninterface Props {\n conversationId: number\n message?: MessageResource\n replyRootId?: string | null\n}\n\nexport function useMessageCreateOrUpdate({ conversationId, message, replyRootId }: Props) {\n const messageId = message?.id || null\n const isEditing = !isNewMessage(message)\n const apiClient = useApiClient()\n const currentPerson = useCurrentPerson()\n\n const mutation = useMutation({\n mutationFn: ({\n text,\n attachments,\n }: {\n text: string\n attachments?: DenormalizedAttachmentResourceForCreate[]\n }) => {\n const requestParams = getMessagesRequestArgs({ conversation_id: conversationId })\n const fieldsWithValueJoined = Object.fromEntries(\n Object.entries(requestParams.data.fields).map(([k, v]) => [k, v.join(',')])\n )\n let attributes: any = {\n text,\n ...(attachments ? { attachments } : {}),\n ...(!isEditing && replyRootId ? { reply_root: { id: replyRootId } } : {}),\n }\n if (!isEditing) {\n const idempotentKey = insecureUUID()\n attributes.idempotent_key = idempotentKey\n startMessageCreationTracking(idempotentKey)\n }\n const data = {\n ...requestParams.data,\n data: {\n type: 'Message',\n attributes,\n },\n fields: fieldsWithValueJoined,\n }\n\n if (isEditing) {\n return apiClient.chat.patch<ApiResource<MessageResource>>({\n url: `/me/conversations/${conversationId}/messages/${messageId}`,\n data,\n })\n } else {\n return apiClient.chat.post<ApiResource<MessageResource>>({\n url: `/me/conversations/${conversationId}/messages`,\n data,\n })\n }\n },\n onMutate: async ({\n text,\n attachments,\n }: {\n text: string\n attachments?: DenormalizedAttachmentResourceForCreate[]\n }) => {\n if (message && isEditing) {\n const optimisticMessage = optimisticallyUpdateMessage({\n conversationId,\n message,\n text,\n replyRootId,\n })\n\n return { message: optimisticMessage }\n }\n\n const optimisticMessage = optimisticallyCreateMessage({\n conversationId,\n message,\n text,\n attachments,\n currentPerson,\n replyRootId,\n })\n\n return { message: optimisticMessage }\n },\n onError: (error, variables, context) => {\n const { message: optimisticMessage } = context || {}\n Haptic.notificationError()\n\n // Add error to the optimistic message from the cache on error\n if (optimisticMessage) {\n const queryKey = getMessagesQueryKey({\n conversation_id: conversationId,\n reply_root_id: replyRootId,\n })\n chatQueryClient.setQueryData(\n queryKey,\n (data: InfiniteData<ApiCollection<MessageResource>> | undefined) =>\n updateRecordInPagesData({\n data,\n record: optimisticMessage,\n processRecord: (_next, prev) => ({\n ...prev,\n error: error.message || 'Failed to send message',\n pending: false, // Mark as no longer pending\n }),\n })\n )\n }\n },\n onSuccess: (result: ApiResource<MessageResource>, variables, context) => {\n const { message: optimisticMessage } = context || {}\n const updatedMessage = result.data\n type QueryData = InfiniteData<ApiCollection<MessageResource>>\n const queryKey = getMessagesQueryKey({\n conversation_id: conversationId,\n reply_root_id: replyRootId,\n })\n\n // First remove the optimistic message if it exists\n if (optimisticMessage) {\n chatQueryClient.setQueryData<QueryData>(queryKey, data =>\n deleteRecordInPagesData({\n data,\n record: optimisticMessage,\n })\n )\n }\n\n // Then add the real message\n chatQueryClient.setQueryData<QueryData>(queryKey, data =>\n updateOrCreateRecordInPagesData({\n data,\n record: updatedMessage,\n processRecord: (record, current) => mergeMessageUpdate(record, current),\n })\n )\n },\n })\n\n return mutation\n}\n\n/**\n * Generate a random UUID (v4) for idempotent keys.\n * Uses Math.random, which is not cryptographically secure.\n * An actual crypto library requires native dependencies.\n * This is OK for now since idempotent keys are not security-sensitive\n * or need to be guaranteed unique.\n * They are short lived and we use it in combination with the message's creator_id so\n * their impact is scoped only the current user.\n */\nfunction insecureUUID(): string {\n return 'xxxxxxxx-xxxx-4xxx-Nxxx-xxxxxxxxxxxx'\n .replace(/x/g, () => Math.floor(Math.random() * 16).toString(16))\n .replace(/N/g, () => (Math.floor(Math.random() * 4) + 8).toString(16))\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"use_message_create_or_update.js","names":["useMutation","chatQueryClient","deleteRecordInPagesData","Haptic","updateOrCreateRecordInPagesData","updateRecordInPagesData","isNewMessage","mergeMessageUpdate","optimisticallyCreateMessage","optimisticallyUpdateMessage","startMessageCreationTracking","getMessagesQueryKey","getMessagesRequestArgs","useApiClient","useCurrentPerson","useMessageCreateOrUpdate","t0","$","_c","conversationId","message","replyRootId","messageId","id","t1","isEditing","apiClient","currentPerson","t2","t3","text","attachments","idempotentKey","requestParams","conversation_id","fieldsWithValueJoined","Object","fromEntries","entries","data","fields","map","_temp","attributes","reply_root","idempotent_key","type","chat","patch","url","post","t4","text_0","attachments_0","idempotentKey_0","optimisticMessage","optimisticMessage_0","t5","error","variables","context","optimisticMessage_1","notificationError","queryKey","reply_root_id","setQueryData","data_0","record","processRecord","_next","prev","pending","result","variables_0","context_0","optimisticMessage_2","updatedMessage","queryKey_0","data_1","data_2","_temp2","t6","mutationFn","onMutate","onError","onSuccess","mutation","t7","variables_1","insecureUUID","resolveVariables","mutate","mutateAsync","variables_2","options","variables_3","options_0","t8","current","k","v","join","replace","Math","floor","random","toString"],"sources":["../../src/hooks/use_message_create_or_update.ts"],"sourcesContent":["import { InfiniteData, useMutation } from '@tanstack/react-query'\nimport { chatQueryClient } from '../contexts/api_provider'\nimport { ApiCollection, ApiResource, MessageResource } from '../types'\nimport { DenormalizedAttachmentResourceForCreate } from '../types/resources/denormalized_attachment_resource_for_create'\nimport {\n deleteRecordInPagesData,\n Haptic,\n updateOrCreateRecordInPagesData,\n updateRecordInPagesData,\n} from '../utils'\nimport { isNewMessage, mergeMessageUpdate } from '../utils/cache/messages_cache'\nimport { optimisticallyCreateMessage } from '../utils/cache/optimistically_create_message'\nimport { optimisticallyUpdateMessage } from '../utils/cache/optimistically_update_message'\nimport { startMessageCreationTracking } from '../utils/performance_tracking'\nimport { getMessagesQueryKey, getMessagesRequestArgs } from '../utils/request/get_messages'\nimport { useApiClient } from './use_api_client'\nimport { useCurrentPerson } from './use_current_person'\n\ninterface Props {\n conversationId: number\n message?: MessageResource\n replyRootId?: string | null\n}\n\ninterface MessageMutationVariables {\n text: string\n attachments?: DenormalizedAttachmentResourceForCreate[]\n idempotentKey?: string\n}\n\ntype ResolvedMessageMutationVariables = MessageMutationVariables & { idempotentKey: string }\n\nexport function useMessageCreateOrUpdate({ conversationId, message, replyRootId }: Props) {\n const messageId = message?.id || null\n const isEditing = !isNewMessage(message)\n const apiClient = useApiClient()\n const currentPerson = useCurrentPerson()\n\n const mutation = useMutation({\n mutationFn: ({ text, attachments, idempotentKey }: ResolvedMessageMutationVariables) => {\n const requestParams = getMessagesRequestArgs({ conversation_id: conversationId })\n const fieldsWithValueJoined = Object.fromEntries(\n Object.entries(requestParams.data.fields).map(([k, v]) => [k, v.join(',')])\n )\n let attributes: any = {\n text,\n ...(attachments ? { attachments } : {}),\n ...(!isEditing && replyRootId ? { reply_root: { id: replyRootId } } : {}),\n }\n if (!isEditing) {\n attributes.idempotent_key = idempotentKey\n startMessageCreationTracking(idempotentKey)\n }\n const data = {\n ...requestParams.data,\n data: {\n type: 'Message',\n attributes,\n },\n fields: fieldsWithValueJoined,\n }\n\n if (isEditing) {\n return apiClient.chat.patch<ApiResource<MessageResource>>({\n url: `/me/conversations/${conversationId}/messages/${messageId}`,\n data,\n })\n } else {\n return apiClient.chat.post<ApiResource<MessageResource>>({\n url: `/me/conversations/${conversationId}/messages`,\n data,\n })\n }\n },\n onMutate: async ({ text, attachments, idempotentKey }: ResolvedMessageMutationVariables) => {\n if (message && isEditing) {\n const optimisticMessage = optimisticallyUpdateMessage({\n conversationId,\n message,\n text,\n replyRootId,\n })\n\n return { message: optimisticMessage }\n }\n\n const optimisticMessage = optimisticallyCreateMessage({\n conversationId,\n message,\n text,\n attachments,\n currentPerson,\n replyRootId,\n idempotentKey,\n })\n\n return { message: optimisticMessage }\n },\n onError: (error, variables, context) => {\n const { message: optimisticMessage } = context || {}\n Haptic.notificationError()\n\n // Add error to the optimistic message from the cache on error\n if (optimisticMessage) {\n const queryKey = getMessagesQueryKey({\n conversation_id: conversationId,\n reply_root_id: replyRootId,\n })\n chatQueryClient.setQueryData(\n queryKey,\n (data: InfiniteData<ApiCollection<MessageResource>> | undefined) =>\n updateRecordInPagesData({\n data,\n record: optimisticMessage,\n processRecord: (_next, prev) => ({\n ...prev,\n error: error.message || 'Failed to send message',\n pending: false, // Mark as no longer pending\n }),\n })\n )\n }\n },\n onSuccess: (result: ApiResource<MessageResource>, variables, context) => {\n const { message: optimisticMessage } = context || {}\n const updatedMessage = result.data\n type QueryData = InfiniteData<ApiCollection<MessageResource>>\n const queryKey = getMessagesQueryKey({\n conversation_id: conversationId,\n reply_root_id: replyRootId,\n })\n\n // First remove the optimistic message if it exists\n if (optimisticMessage) {\n chatQueryClient.setQueryData<QueryData>(queryKey, data =>\n deleteRecordInPagesData({\n data,\n record: optimisticMessage,\n })\n )\n }\n\n // Then add the real message\n chatQueryClient.setQueryData<QueryData>(queryKey, data =>\n updateOrCreateRecordInPagesData({\n data,\n record: updatedMessage,\n processRecord: (record, current) => mergeMessageUpdate(record, current),\n })\n )\n },\n })\n\n const resolveVariables = (\n variables: MessageMutationVariables\n ): ResolvedMessageMutationVariables => ({\n ...variables,\n idempotentKey: variables.idempotentKey ?? message?.idempotentKey ?? insecureUUID(),\n })\n\n const mutate = (\n variables: MessageMutationVariables,\n options?: Parameters<typeof mutation.mutate>[1]\n ) => mutation.mutate(resolveVariables(variables), options)\n\n const mutateAsync = (\n variables: MessageMutationVariables,\n options?: Parameters<typeof mutation.mutateAsync>[1]\n ) => mutation.mutateAsync(resolveVariables(variables), options)\n\n return { ...mutation, mutate, mutateAsync }\n}\n\n/**\n * Generate a random UUID (v4) for idempotent keys.\n * Uses Math.random, which is not cryptographically secure.\n * An actual crypto library requires native dependencies.\n * This is OK for now since idempotent keys are not security-sensitive\n * or need to be guaranteed unique.\n * They are short lived and we use it in combination with the message's creator_id so\n * their impact is scoped only the current user.\n */\nfunction insecureUUID(): string {\n return 'xxxxxxxx-xxxx-4xxx-Nxxx-xxxxxxxxxxxx'\n .replace(/x/g, () => Math.floor(Math.random() * 16).toString(16))\n .replace(/N/g, () => (Math.floor(Math.random() * 4) + 8).toString(16))\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../../../src/types/resources/message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,8BAA8B,EAAE,MAAM,oCAAoC,CAAA;AACnF,OAAO,EAAE,uCAAuC,EAAE,MAAM,+CAA+C,CAAA;AACvG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAC9C,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAEvD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,SAAS,CAAA;IACf,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,IAAI,EAAE,OAAO,CAAA;IACb,WAAW,EAAE,8BAA8B,EAAE,CAAA;IAC7C,MAAM,EAAE,cAAc,CAAA;IACtB,cAAc,EAAE,qBAAqB,EAAE,CAAA;IACvC,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,WAAW,EAAE,MAAM,CAAA;IACnB,uBAAuB,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;IACzC,eAAe,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IAGnF,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,sBAAsB,CAAC,EAAE,OAAO,CAAA;IAChC,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,cAAc,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,IAAI,CAAA;IACnD,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,wBAAwB,CAAC,EAAE,OAAO,CAAA;IAGlC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,oBAAoB,CAAC,EAAE,uCAAuC,EAAE,CAAA;CACjE"}
|
|
1
|
+
{"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../../../src/types/resources/message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,8BAA8B,EAAE,MAAM,oCAAoC,CAAA;AACnF,OAAO,EAAE,uCAAuC,EAAE,MAAM,+CAA+C,CAAA;AACvG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAC9C,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAEvD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,SAAS,CAAA;IACf,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,IAAI,EAAE,OAAO,CAAA;IACb,WAAW,EAAE,8BAA8B,EAAE,CAAA;IAC7C,MAAM,EAAE,cAAc,CAAA;IACtB,cAAc,EAAE,qBAAqB,EAAE,CAAA;IACvC,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,WAAW,EAAE,MAAM,CAAA;IACnB,uBAAuB,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;IACzC,eAAe,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IAGnF,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,sBAAsB,CAAC,EAAE,OAAO,CAAA;IAChC,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,cAAc,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,IAAI,CAAA;IACnD,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,wBAAwB,CAAC,EAAE,OAAO,CAAA;IAGlC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,oBAAoB,CAAC,EAAE,uCAAuC,EAAE,CAAA;CACjE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message.js","names":[],"sources":["../../../src/types/resources/message.ts"],"sourcesContent":["import { DenormalizedAttachmentResource } from './denormalized_attachment_resource'\nimport { DenormalizedAttachmentResourceForCreate } from './denormalized_attachment_resource_for_create'\nimport type { PersonResource } from './person'\nimport type { ReactionCountResource } from './reaction'\n\nexport interface MessageResource {\n type: 'Message'\n id: string\n text: string | null\n html: string\n createdAt: string\n deletedAt: string | null\n textEditedAt: string | null\n mine: boolean\n attachments: DenormalizedAttachmentResource[]\n author: PersonResource\n reactionCounts: ReactionCountResource[]\n replyCount: number\n replyRootId?: string | null\n messageType: string\n personIdsForSystemEvent?: number[] | null\n systemTextParts?: { names: string[]; overflowCount: number; action: string } | null\n\n // Custom Local Properties we set for rendering\n renderAuthor?: boolean\n renderTime?: boolean\n myLatestInConversation?: boolean\n lastInGroup?: boolean\n threadPosition?: 'first' | 'center' | 'last' | null\n prevIsMyReply?: boolean\n nextIsMyReply?: boolean\n nextRendersAuthor?: boolean\n isReplyShadowMessage?: boolean\n nextIsReplyShadowMessage?: boolean\n\n // Properties for mutation state\n pending?: boolean // Indicates if the message is optimistically created and pending server response\n error?: string // Error message if the message failed to send\n attachmentsForCreate?: DenormalizedAttachmentResourceForCreate[]\n}\n"],"mappings":"","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"message.js","names":[],"sources":["../../../src/types/resources/message.ts"],"sourcesContent":["import { DenormalizedAttachmentResource } from './denormalized_attachment_resource'\nimport { DenormalizedAttachmentResourceForCreate } from './denormalized_attachment_resource_for_create'\nimport type { PersonResource } from './person'\nimport type { ReactionCountResource } from './reaction'\n\nexport interface MessageResource {\n type: 'Message'\n id: string\n text: string | null\n html: string\n createdAt: string\n deletedAt: string | null\n textEditedAt: string | null\n mine: boolean\n attachments: DenormalizedAttachmentResource[]\n author: PersonResource\n reactionCounts: ReactionCountResource[]\n replyCount: number\n replyRootId?: string | null\n idempotentKey?: string | null\n messageType: string\n personIdsForSystemEvent?: number[] | null\n systemTextParts?: { names: string[]; overflowCount: number; action: string } | null\n\n // Custom Local Properties we set for rendering\n renderAuthor?: boolean\n renderTime?: boolean\n myLatestInConversation?: boolean\n lastInGroup?: boolean\n threadPosition?: 'first' | 'center' | 'last' | null\n prevIsMyReply?: boolean\n nextIsMyReply?: boolean\n nextRendersAuthor?: boolean\n isReplyShadowMessage?: boolean\n nextIsReplyShadowMessage?: boolean\n\n // Properties for mutation state\n pending?: boolean // Indicates if the message is optimistically created and pending server response\n error?: string // Error message if the message failed to send\n attachmentsForCreate?: DenormalizedAttachmentResourceForCreate[]\n}\n"],"mappings":"","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages_cache.d.ts","sourceRoot":"","sources":["../../../src/utils/cache/messages_cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAEjE,OAAO,EAA8B,eAAe,EAAE,MAAM,aAAa,CAAA;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAM3D,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,OAAO,EAAE,EACnB,OAAO,EAAE,eAAe,EACxB,KAAK,EAAE,iBAAiB,GAAG,iBAAiB,
|
|
1
|
+
{"version":3,"file":"messages_cache.d.ts","sourceRoot":"","sources":["../../../src/utils/cache/messages_cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAEjE,OAAO,EAA8B,eAAe,EAAE,MAAM,aAAa,CAAA;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAM3D,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,OAAO,EAAE,EACnB,OAAO,EAAE,eAAe,EACxB,KAAK,EAAE,iBAAiB,GAAG,iBAAiB,QA8B7C;AAID,wBAAgB,gCAAgC,CAC9C,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,OAAO,EAAE,EACnB,OAAO,EAAE,eAAe,QAgBzB;AAED,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,OAAO,EAAE,EACnB,KAAK,EAAE,iBAAiB,EACxB,eAAe,EAAE,MAAM,QAuCxB;AAGD,wBAAgB,oBAAoB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAEvE;AACD,wBAAgB,YAAY,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAE/D;AAED,wBAAgB,2BAA2B,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,mCAMtF;AAED,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,eAAe,EACvB,OAAO,CAAC,EAAE,eAAe,GACxB,eAAe,CAWjB"}
|
|
@@ -6,16 +6,14 @@ import { deleteRecordInPagesData } from './page_mutations';
|
|
|
6
6
|
export function updateCacheWithMessage(queryClient, queryKey, message, event) {
|
|
7
7
|
queryClient.setQueryData(queryKey, prev => {
|
|
8
8
|
if (event === 'message.created') {
|
|
9
|
-
// Before adding the new message, remove
|
|
10
|
-
//
|
|
9
|
+
// Before adding the new message, remove its pending temporary counterpart.
|
|
10
|
+
// The Jolt echo can arrive before the HTTP response has swapped it out.
|
|
11
11
|
let dataAfterTempRemoval = prev;
|
|
12
|
-
if (prev && message.
|
|
12
|
+
if (prev && message.mine && message.idempotentKey) {
|
|
13
13
|
dataAfterTempRemoval = deleteRecordInPagesData({
|
|
14
14
|
data: prev,
|
|
15
15
|
record: message,
|
|
16
|
-
matchFn: (existingMessage
|
|
17
|
-
return isTemporaryMessageId(existingMessage.id) && existingMessage.text === message.text && existingMessage.mine;
|
|
18
|
-
}
|
|
16
|
+
matchFn: existingMessage => isTemporaryMessageId(existingMessage.id) && existingMessage.idempotentKey === message.idempotentKey
|
|
19
17
|
});
|
|
20
18
|
}
|
|
21
19
|
return updateOrCreateRecordInPagesData({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages_cache.js","names":["getRequestQueryKey","transformReactionEventDataToReactionCountResource","getMessagesRequestArgs","updateOrCreateRecordInPagesData","updateRecordInPagesData","deleteRecordInPagesData","updateCacheWithMessage","queryClient","queryKey","message","event","setQueryData","prev","dataAfterTempRemoval","
|
|
1
|
+
{"version":3,"file":"messages_cache.js","names":["getRequestQueryKey","transformReactionEventDataToReactionCountResource","getMessagesRequestArgs","updateOrCreateRecordInPagesData","updateRecordInPagesData","deleteRecordInPagesData","updateCacheWithMessage","queryClient","queryKey","message","event","setQueryData","prev","dataAfterTempRemoval","mine","idempotentKey","data","record","matchFn","existingMessage","isTemporaryMessageId","id","processRecord","current","mergeMessageUpdate","updateCacheWithIndividualMessage","links","meta","updateCacheWithReaction","currentPersonId","message_sort_key","oldMessage","reactionCounts","foundMatch","newReactionCounts","map","reactionCount","value","oldData","newReactionCount","count","messageId","endsWith","isNewMessage","getThreadedMessagesQueryKey","conversationId","replyRootId","requestArgs","conversation_id","reply_root_id"],"sources":["../../../src/utils/cache/messages_cache.ts"],"sourcesContent":["import { InfiniteData, QueryClient } from '@tanstack/react-query'\nimport { getRequestQueryKey } from '../../hooks/use_suspense_api'\nimport { ApiCollection, ApiResource, MessageResource } from '../../types'\nimport { JoltReactionEvent } from '../../types/jolt_events'\nimport { transformReactionEventDataToReactionCountResource } from '../jolt/transform_reaction_event_data_to_reaction_count_resource'\nimport { getMessagesRequestArgs } from '../request/get_messages'\nimport { updateOrCreateRecordInPagesData, updateRecordInPagesData } from './page_mutations'\nimport { deleteRecordInPagesData } from './page_mutations'\n\nexport function updateCacheWithMessage(\n queryClient: QueryClient,\n queryKey: unknown[],\n message: MessageResource,\n event: 'message.created' | 'message.updated'\n) {\n queryClient.setQueryData<MessagesQueryData>(queryKey, prev => {\n if (event === 'message.created') {\n // Before adding the new message, remove its pending temporary counterpart.\n // The Jolt echo can arrive before the HTTP response has swapped it out.\n let dataAfterTempRemoval = prev\n if (prev && message.mine && message.idempotentKey) {\n dataAfterTempRemoval = deleteRecordInPagesData({\n data: prev,\n record: message,\n matchFn: existingMessage =>\n isTemporaryMessageId(existingMessage.id) &&\n existingMessage.idempotentKey === message.idempotentKey,\n })\n }\n\n return updateOrCreateRecordInPagesData({\n data: dataAfterTempRemoval,\n record: message,\n processRecord: (record, current) => mergeMessageUpdate(record, current),\n })\n } else {\n return updateRecordInPagesData({\n data: prev,\n record: message,\n processRecord: (record, current) => mergeMessageUpdate(record, current),\n })\n }\n })\n}\n\ntype IndividualMessageQueryData = ApiResource<MessageResource>\n\nexport function updateCacheWithIndividualMessage(\n queryClient: QueryClient,\n queryKey: unknown[],\n message: MessageResource\n) {\n queryClient.setQueryData<IndividualMessageQueryData>(queryKey, prev => {\n if (!prev) {\n return {\n data: message,\n links: {},\n meta: {},\n }\n }\n\n return {\n ...prev,\n data: mergeMessageUpdate(message, prev.data),\n }\n })\n}\n\nexport function updateCacheWithReaction(\n queryClient: QueryClient,\n queryKey: unknown[],\n event: JoltReactionEvent,\n currentPersonId: number\n) {\n const message = { id: event.data.data.message_sort_key } as MessageResource\n queryClient.setQueryData<MessagesQueryData>(queryKey, prev =>\n updateRecordInPagesData({\n data: prev,\n record: message,\n processRecord: (record, oldMessage) => {\n const reactionCounts = oldMessage.reactionCounts || []\n let foundMatch = false\n let newReactionCounts = reactionCounts.map(reactionCount => {\n if (reactionCount.value === event.data.data.value) {\n foundMatch = true\n return transformReactionEventDataToReactionCountResource({\n data: event.data.data,\n oldData: reactionCount,\n event: event.event,\n currentPersonId,\n })\n }\n return reactionCount\n })\n\n if (!foundMatch) {\n const newReactionCount = transformReactionEventDataToReactionCountResource({\n data: event.data.data,\n event: event.event,\n currentPersonId,\n })\n\n if (newReactionCount?.count) {\n newReactionCounts = [...newReactionCounts, newReactionCount]\n }\n }\n\n return { ...oldMessage, reactionCounts: newReactionCounts }\n },\n })\n )\n}\n\ntype MessagesQueryData = InfiniteData<ApiCollection<MessageResource>>\nexport function isTemporaryMessageId(messageId?: string | null): boolean {\n return !!messageId && messageId.endsWith('-temp')\n}\nexport function isNewMessage(message?: MessageResource): boolean {\n return !message?.id || isTemporaryMessageId(message.id)\n}\n\nexport function getThreadedMessagesQueryKey(conversationId: number, replyRootId: string) {\n const requestArgs = getMessagesRequestArgs({\n conversation_id: conversationId,\n reply_root_id: replyRootId,\n })\n return getRequestQueryKey(requestArgs)\n}\n\nexport function mergeMessageUpdate(\n record: MessageResource,\n current?: MessageResource\n): MessageResource {\n if (!current) {\n return record\n }\n\n return {\n ...current,\n ...record,\n // Preserve reactions from cache - they're managed via separate reaction events\n reactionCounts: current.reactionCounts || [],\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { CurrentPersonResource, MessageResource } from '../../types';
|
|
2
2
|
import { DenormalizedAttachmentResourceForCreate } from '../../types/resources/denormalized_attachment_resource_for_create';
|
|
3
|
-
export declare function optimisticallyCreateMessage({ conversationId, text, attachments, currentPerson, message, replyRootId, }: {
|
|
3
|
+
export declare function optimisticallyCreateMessage({ conversationId, text, attachments, currentPerson, message, replyRootId, idempotentKey, }: {
|
|
4
4
|
conversationId: number;
|
|
5
5
|
text: string;
|
|
6
6
|
attachments?: DenormalizedAttachmentResourceForCreate[];
|
|
7
7
|
currentPerson: CurrentPersonResource;
|
|
8
8
|
message?: MessageResource;
|
|
9
9
|
replyRootId?: string | null;
|
|
10
|
+
idempotentKey?: string;
|
|
10
11
|
}): MessageResource;
|
|
11
12
|
//# sourceMappingURL=optimistically_create_message.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"optimistically_create_message.d.ts","sourceRoot":"","sources":["../../../src/utils/cache/optimistically_create_message.ts"],"names":[],"mappings":"AAEA,OAAO,EAAiB,qBAAqB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAEnF,OAAO,EAAE,uCAAuC,EAAE,MAAM,mEAAmE,CAAA;AAM3H,wBAAgB,2BAA2B,CAAC,EAC1C,cAAc,EACd,IAAI,EACJ,WAAW,EACX,aAAa,EACb,OAAO,EACP,WAAW,
|
|
1
|
+
{"version":3,"file":"optimistically_create_message.d.ts","sourceRoot":"","sources":["../../../src/utils/cache/optimistically_create_message.ts"],"names":[],"mappings":"AAEA,OAAO,EAAiB,qBAAqB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAEnF,OAAO,EAAE,uCAAuC,EAAE,MAAM,mEAAmE,CAAA;AAM3H,wBAAgB,2BAA2B,CAAC,EAC1C,cAAc,EACd,IAAI,EACJ,WAAW,EACX,aAAa,EACb,OAAO,EACP,WAAW,EACX,aAAa,GACd,EAAE;IACD,cAAc,EAAE,MAAM,CAAA;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,CAAC,EAAE,uCAAuC,EAAE,CAAA;IACvD,aAAa,EAAE,qBAAqB,CAAA;IACpC,OAAO,CAAC,EAAE,eAAe,CAAA;IACzB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,mBAiDA"}
|
|
@@ -9,7 +9,8 @@ export function optimisticallyCreateMessage({
|
|
|
9
9
|
attachments,
|
|
10
10
|
currentPerson,
|
|
11
11
|
message,
|
|
12
|
-
replyRootId
|
|
12
|
+
replyRootId,
|
|
13
|
+
idempotentKey
|
|
13
14
|
}) {
|
|
14
15
|
const id = message?.id || generateTempMessageId();
|
|
15
16
|
|
|
@@ -39,7 +40,8 @@ export function optimisticallyCreateMessage({
|
|
|
39
40
|
lastInGroup: true,
|
|
40
41
|
pending: true,
|
|
41
42
|
replyCount: 0,
|
|
42
|
-
replyRootId: replyRootId || null
|
|
43
|
+
replyRootId: replyRootId || null,
|
|
44
|
+
idempotentKey
|
|
43
45
|
};
|
|
44
46
|
|
|
45
47
|
// Add the optimistic message to the cache
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"optimistically_create_message.js","names":["chatQueryClient","getMessagesQueryKey","convertAttachmentsForCreate","generatePlaceholderUlid","updateOrCreateRecordInPagesData","optimisticallyCreateMessage","conversationId","text","attachments","currentPerson","message","replyRootId","id","generateTempMessageId","decoratedAttachments","optimisticMessage","type","messageType","html","createdAt","Date","toISOString","deletedAt","textEditedAt","mine","attachmentsForCreate","author","reactionCounts","renderAuthor","renderTime","myLatestInConversation","lastInGroup","pending","replyCount","queryKey","conversation_id","reply_root_id","setQueryData","data","record","offsetMs"],"sources":["../../../src/utils/cache/optimistically_create_message.ts"],"sourcesContent":["import { InfiniteData } from '@tanstack/react-query'\nimport { chatQueryClient } from '../../contexts/api_provider'\nimport { ApiCollection, CurrentPersonResource, MessageResource } from '../../types'\nimport { DenormalizedAttachmentResource } from '../../types/resources/denormalized_attachment_resource'\nimport { DenormalizedAttachmentResourceForCreate } from '../../types/resources/denormalized_attachment_resource_for_create'\nimport { getMessagesQueryKey } from '../../utils/request/get_messages'\nimport { convertAttachmentsForCreate } from '../convert_attachments_for_create'\nimport { generatePlaceholderUlid } from '../generate_placeholder_ulid'\nimport { updateOrCreateRecordInPagesData } from './page_mutations'\n\nexport function optimisticallyCreateMessage({\n conversationId,\n text,\n attachments,\n currentPerson,\n message,\n replyRootId,\n}: {\n conversationId: number\n text: string\n attachments?: DenormalizedAttachmentResourceForCreate[]\n currentPerson: CurrentPersonResource\n message?: MessageResource\n replyRootId?: string | null\n}) {\n const id = message?.id || generateTempMessageId()\n\n // Convert attachments to denormalized format for optimistic UI\n const decoratedAttachments: DenormalizedAttachmentResource[] = convertAttachmentsForCreate(\n attachments || []\n )\n\n // Create an optimistic message\n const optimisticMessage: MessageResource = {\n ...message,\n type: 'Message',\n messageType: 'user',\n id,\n text,\n html: '', // Will be filled by server\n createdAt: new Date().toISOString(),\n deletedAt: null,\n textEditedAt: null,\n mine: true,\n attachments: decoratedAttachments,\n attachmentsForCreate: attachments || [],\n author: currentPerson,\n reactionCounts: [],\n renderAuthor: false,\n renderTime: true,\n myLatestInConversation: true,\n lastInGroup: true,\n pending: true,\n replyCount: 0,\n replyRootId: replyRootId || null,\n }\n\n // Add the optimistic message to the cache\n type QueryData = InfiniteData<ApiCollection<MessageResource>>\n const queryKey = getMessagesQueryKey({\n conversation_id: conversationId,\n reply_root_id: replyRootId,\n })\n\n chatQueryClient.setQueryData<QueryData>(queryKey, data =>\n updateOrCreateRecordInPagesData({\n data,\n record: optimisticMessage as MessageResource,\n })\n )\n\n return optimisticMessage\n}\n\nconst generateTempMessageId = () => {\n // Put it 5 seconds in the future to account for server lag of previously created messages\n // that are still pending. This gets overwritten by the server anyway.\n return `${generatePlaceholderUlid({ offsetMs: 5000 })}-temp`\n}\n"],"mappings":"AACA,SAASA,eAAe,QAAQ,6BAA6B;AAI7D,SAASC,mBAAmB,QAAQ,kCAAkC;AACtE,SAASC,2BAA2B,QAAQ,mCAAmC;AAC/E,SAASC,uBAAuB,QAAQ,8BAA8B;AACtE,SAASC,+BAA+B,QAAQ,kBAAkB;AAElE,OAAO,SAASC,2BAA2BA,CAAC;EAC1CC,cAAc;EACdC,IAAI;EACJC,WAAW;EACXC,aAAa;EACbC,OAAO;EACPC;
|
|
1
|
+
{"version":3,"file":"optimistically_create_message.js","names":["chatQueryClient","getMessagesQueryKey","convertAttachmentsForCreate","generatePlaceholderUlid","updateOrCreateRecordInPagesData","optimisticallyCreateMessage","conversationId","text","attachments","currentPerson","message","replyRootId","idempotentKey","id","generateTempMessageId","decoratedAttachments","optimisticMessage","type","messageType","html","createdAt","Date","toISOString","deletedAt","textEditedAt","mine","attachmentsForCreate","author","reactionCounts","renderAuthor","renderTime","myLatestInConversation","lastInGroup","pending","replyCount","queryKey","conversation_id","reply_root_id","setQueryData","data","record","offsetMs"],"sources":["../../../src/utils/cache/optimistically_create_message.ts"],"sourcesContent":["import { InfiniteData } from '@tanstack/react-query'\nimport { chatQueryClient } from '../../contexts/api_provider'\nimport { ApiCollection, CurrentPersonResource, MessageResource } from '../../types'\nimport { DenormalizedAttachmentResource } from '../../types/resources/denormalized_attachment_resource'\nimport { DenormalizedAttachmentResourceForCreate } from '../../types/resources/denormalized_attachment_resource_for_create'\nimport { getMessagesQueryKey } from '../../utils/request/get_messages'\nimport { convertAttachmentsForCreate } from '../convert_attachments_for_create'\nimport { generatePlaceholderUlid } from '../generate_placeholder_ulid'\nimport { updateOrCreateRecordInPagesData } from './page_mutations'\n\nexport function optimisticallyCreateMessage({\n conversationId,\n text,\n attachments,\n currentPerson,\n message,\n replyRootId,\n idempotentKey,\n}: {\n conversationId: number\n text: string\n attachments?: DenormalizedAttachmentResourceForCreate[]\n currentPerson: CurrentPersonResource\n message?: MessageResource\n replyRootId?: string | null\n idempotentKey?: string\n}) {\n const id = message?.id || generateTempMessageId()\n\n // Convert attachments to denormalized format for optimistic UI\n const decoratedAttachments: DenormalizedAttachmentResource[] = convertAttachmentsForCreate(\n attachments || []\n )\n\n // Create an optimistic message\n const optimisticMessage: MessageResource = {\n ...message,\n type: 'Message',\n messageType: 'user',\n id,\n text,\n html: '', // Will be filled by server\n createdAt: new Date().toISOString(),\n deletedAt: null,\n textEditedAt: null,\n mine: true,\n attachments: decoratedAttachments,\n attachmentsForCreate: attachments || [],\n author: currentPerson,\n reactionCounts: [],\n renderAuthor: false,\n renderTime: true,\n myLatestInConversation: true,\n lastInGroup: true,\n pending: true,\n replyCount: 0,\n replyRootId: replyRootId || null,\n idempotentKey,\n }\n\n // Add the optimistic message to the cache\n type QueryData = InfiniteData<ApiCollection<MessageResource>>\n const queryKey = getMessagesQueryKey({\n conversation_id: conversationId,\n reply_root_id: replyRootId,\n })\n\n chatQueryClient.setQueryData<QueryData>(queryKey, data =>\n updateOrCreateRecordInPagesData({\n data,\n record: optimisticMessage as MessageResource,\n })\n )\n\n return optimisticMessage\n}\n\nconst generateTempMessageId = () => {\n // Put it 5 seconds in the future to account for server lag of previously created messages\n // that are still pending. This gets overwritten by the server anyway.\n return `${generatePlaceholderUlid({ offsetMs: 5000 })}-temp`\n}\n"],"mappings":"AACA,SAASA,eAAe,QAAQ,6BAA6B;AAI7D,SAASC,mBAAmB,QAAQ,kCAAkC;AACtE,SAASC,2BAA2B,QAAQ,mCAAmC;AAC/E,SAASC,uBAAuB,QAAQ,8BAA8B;AACtE,SAASC,+BAA+B,QAAQ,kBAAkB;AAElE,OAAO,SAASC,2BAA2BA,CAAC;EAC1CC,cAAc;EACdC,IAAI;EACJC,WAAW;EACXC,aAAa;EACbC,OAAO;EACPC,WAAW;EACXC;AASF,CAAC,EAAE;EACD,MAAMC,EAAE,GAAGH,OAAO,EAAEG,EAAE,IAAIC,qBAAqB,CAAC,CAAC;;EAEjD;EACA,MAAMC,oBAAsD,GAAGb,2BAA2B,CACxFM,WAAW,IAAI,EACjB,CAAC;;EAED;EACA,MAAMQ,iBAAkC,GAAG;IACzC,GAAGN,OAAO;IACVO,IAAI,EAAE,SAAS;IACfC,WAAW,EAAE,MAAM;IACnBL,EAAE;IACFN,IAAI;IACJY,IAAI,EAAE,EAAE;IAAE;IACVC,SAAS,EAAE,IAAIC,IAAI,CAAC,CAAC,CAACC,WAAW,CAAC,CAAC;IACnCC,SAAS,EAAE,IAAI;IACfC,YAAY,EAAE,IAAI;IAClBC,IAAI,EAAE,IAAI;IACVjB,WAAW,EAAEO,oBAAoB;IACjCW,oBAAoB,EAAElB,WAAW,IAAI,EAAE;IACvCmB,MAAM,EAAElB,aAAa;IACrBmB,cAAc,EAAE,EAAE;IAClBC,YAAY,EAAE,KAAK;IACnBC,UAAU,EAAE,IAAI;IAChBC,sBAAsB,EAAE,IAAI;IAC5BC,WAAW,EAAE,IAAI;IACjBC,OAAO,EAAE,IAAI;IACbC,UAAU,EAAE,CAAC;IACbvB,WAAW,EAAEA,WAAW,IAAI,IAAI;IAChCC;EACF,CAAC;;EAED;;EAEA,MAAMuB,QAAQ,GAAGlC,mBAAmB,CAAC;IACnCmC,eAAe,EAAE9B,cAAc;IAC/B+B,aAAa,EAAE1B;EACjB,CAAC,CAAC;EAEFX,eAAe,CAACsC,YAAY,CAAYH,QAAQ,EAAEI,IAAI,IACpDnC,+BAA+B,CAAC;IAC9BmC,IAAI;IACJC,MAAM,EAAExB;EACV,CAAC,CACH,CAAC;EAED,OAAOA,iBAAiB;AAC1B;AAEA,MAAMF,qBAAqB,GAAGA,CAAA,KAAM;EAClC;EACA;EACA,OAAO,GAAGX,uBAAuB,CAAC;IAAEsC,QAAQ,EAAE;EAAK,CAAC,CAAC,OAAO;AAC9D,CAAC","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transform_message_event_data_to_message_resource.d.ts","sourceRoot":"","sources":["../../../src/utils/jolt/transform_message_event_data_to_message_resource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAA;AAI5E,wBAAgB,0CAA0C,CAAC,EACzD,IAAI,EACJ,eAAe,GAChB,EAAE;IACD,IAAI,EAAE,mBAAmB,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAA;IACzC,eAAe,EAAE,MAAM,CAAA;CACxB,GAAG,eAAe,
|
|
1
|
+
{"version":3,"file":"transform_message_event_data_to_message_resource.d.ts","sourceRoot":"","sources":["../../../src/utils/jolt/transform_message_event_data_to_message_resource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAA;AAI5E,wBAAgB,0CAA0C,CAAC,EACzD,IAAI,EACJ,eAAe,GAChB,EAAE;IACD,IAAI,EAAE,mBAAmB,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAA;IACzC,eAAe,EAAE,MAAM,CAAA;CACxB,GAAG,eAAe,CA+BlB"}
|
|
@@ -22,6 +22,7 @@ export function transformMessageEventDataToMessageResource({
|
|
|
22
22
|
reactionCounts: [],
|
|
23
23
|
replyCount: data.reply_count || 0,
|
|
24
24
|
replyRootId: data.reply_root_id || null,
|
|
25
|
+
idempotentKey: data.idempotent_key ?? null,
|
|
25
26
|
messageType: data.message_type,
|
|
26
27
|
personIdsForSystemEvent: data.system_event_person_ids,
|
|
27
28
|
systemTextParts: data.system_text_parts ? {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transform_message_event_data_to_message_resource.js","names":["deepCamelCaseKeys","transformMessageEventDataToMessageResource","data","currentPersonId","type","id","sort_key","text","html","createdAt","created_at","deletedAt","deleted_at","textEditedAt","text_edited_at","mine","author_id","attachments","author","name","author_name","avatar","author_avatar","reactionCounts","replyCount","reply_count","replyRootId","reply_root_id","messageType","message_type","personIdsForSystemEvent","system_event_person_ids","systemTextParts","system_text_parts","names","overflowCount","overflow_count","action"],"sources":["../../../src/utils/jolt/transform_message_event_data_to_message_resource.ts"],"sourcesContent":["import { MessageResource } from '../../types'\nimport { MessageCreatedEvent } from '../../types/jolt_events/message_events'\nimport { DenormalizedAttachmentResource } from '../../types/resources/denormalized_attachment_resource'\nimport { deepCamelCaseKeys } from '../deepCamelCaseKeys'\n\nexport function transformMessageEventDataToMessageResource({\n data,\n currentPersonId,\n}: {\n data: MessageCreatedEvent['data']['data']\n currentPersonId: number\n}): MessageResource {\n return {\n type: 'Message',\n id: data.sort_key,\n text: data.text,\n html: data.html,\n createdAt: data.created_at,\n deletedAt: data.deleted_at,\n textEditedAt: data.text_edited_at,\n mine: data.author_id === currentPersonId,\n attachments: deepCamelCaseKeys<DenormalizedAttachmentResource[]>(data.attachments) || [],\n author: {\n type: 'Person',\n id: data.author_id,\n name: data.author_name,\n avatar: data.author_avatar,\n },\n reactionCounts: [],\n replyCount: data.reply_count || 0,\n replyRootId: data.reply_root_id || null,\n messageType: data.message_type,\n personIdsForSystemEvent: data.system_event_person_ids,\n systemTextParts: data.system_text_parts\n ? {\n names: data.system_text_parts.names,\n overflowCount: data.system_text_parts.overflow_count,\n action: data.system_text_parts.action,\n }\n : null,\n }\n}\n"],"mappings":"AAGA,SAASA,iBAAiB,QAAQ,sBAAsB;AAExD,OAAO,SAASC,0CAA0CA,CAAC;EACzDC,IAAI;EACJC;AAIF,CAAC,EAAmB;EAClB,OAAO;IACLC,IAAI,EAAE,SAAS;IACfC,EAAE,EAAEH,IAAI,CAACI,QAAQ;IACjBC,IAAI,EAAEL,IAAI,CAACK,IAAI;IACfC,IAAI,EAAEN,IAAI,CAACM,IAAI;IACfC,SAAS,EAAEP,IAAI,CAACQ,UAAU;IAC1BC,SAAS,EAAET,IAAI,CAACU,UAAU;IAC1BC,YAAY,EAAEX,IAAI,CAACY,cAAc;IACjCC,IAAI,EAAEb,IAAI,CAACc,SAAS,KAAKb,eAAe;IACxCc,WAAW,EAAEjB,iBAAiB,CAAmCE,IAAI,CAACe,WAAW,CAAC,IAAI,EAAE;IACxFC,MAAM,EAAE;MACNd,IAAI,EAAE,QAAQ;MACdC,EAAE,EAAEH,IAAI,CAACc,SAAS;MAClBG,IAAI,EAAEjB,IAAI,CAACkB,WAAW;MACtBC,MAAM,EAAEnB,IAAI,CAACoB;IACf,CAAC;IACDC,cAAc,EAAE,EAAE;IAClBC,UAAU,EAAEtB,IAAI,CAACuB,WAAW,IAAI,CAAC;IACjCC,WAAW,EAAExB,IAAI,CAACyB,aAAa,IAAI,IAAI;IACvCC,
|
|
1
|
+
{"version":3,"file":"transform_message_event_data_to_message_resource.js","names":["deepCamelCaseKeys","transformMessageEventDataToMessageResource","data","currentPersonId","type","id","sort_key","text","html","createdAt","created_at","deletedAt","deleted_at","textEditedAt","text_edited_at","mine","author_id","attachments","author","name","author_name","avatar","author_avatar","reactionCounts","replyCount","reply_count","replyRootId","reply_root_id","idempotentKey","idempotent_key","messageType","message_type","personIdsForSystemEvent","system_event_person_ids","systemTextParts","system_text_parts","names","overflowCount","overflow_count","action"],"sources":["../../../src/utils/jolt/transform_message_event_data_to_message_resource.ts"],"sourcesContent":["import { MessageResource } from '../../types'\nimport { MessageCreatedEvent } from '../../types/jolt_events/message_events'\nimport { DenormalizedAttachmentResource } from '../../types/resources/denormalized_attachment_resource'\nimport { deepCamelCaseKeys } from '../deepCamelCaseKeys'\n\nexport function transformMessageEventDataToMessageResource({\n data,\n currentPersonId,\n}: {\n data: MessageCreatedEvent['data']['data']\n currentPersonId: number\n}): MessageResource {\n return {\n type: 'Message',\n id: data.sort_key,\n text: data.text,\n html: data.html,\n createdAt: data.created_at,\n deletedAt: data.deleted_at,\n textEditedAt: data.text_edited_at,\n mine: data.author_id === currentPersonId,\n attachments: deepCamelCaseKeys<DenormalizedAttachmentResource[]>(data.attachments) || [],\n author: {\n type: 'Person',\n id: data.author_id,\n name: data.author_name,\n avatar: data.author_avatar,\n },\n reactionCounts: [],\n replyCount: data.reply_count || 0,\n replyRootId: data.reply_root_id || null,\n idempotentKey: data.idempotent_key ?? null,\n messageType: data.message_type,\n personIdsForSystemEvent: data.system_event_person_ids,\n systemTextParts: data.system_text_parts\n ? {\n names: data.system_text_parts.names,\n overflowCount: data.system_text_parts.overflow_count,\n action: data.system_text_parts.action,\n }\n : null,\n }\n}\n"],"mappings":"AAGA,SAASA,iBAAiB,QAAQ,sBAAsB;AAExD,OAAO,SAASC,0CAA0CA,CAAC;EACzDC,IAAI;EACJC;AAIF,CAAC,EAAmB;EAClB,OAAO;IACLC,IAAI,EAAE,SAAS;IACfC,EAAE,EAAEH,IAAI,CAACI,QAAQ;IACjBC,IAAI,EAAEL,IAAI,CAACK,IAAI;IACfC,IAAI,EAAEN,IAAI,CAACM,IAAI;IACfC,SAAS,EAAEP,IAAI,CAACQ,UAAU;IAC1BC,SAAS,EAAET,IAAI,CAACU,UAAU;IAC1BC,YAAY,EAAEX,IAAI,CAACY,cAAc;IACjCC,IAAI,EAAEb,IAAI,CAACc,SAAS,KAAKb,eAAe;IACxCc,WAAW,EAAEjB,iBAAiB,CAAmCE,IAAI,CAACe,WAAW,CAAC,IAAI,EAAE;IACxFC,MAAM,EAAE;MACNd,IAAI,EAAE,QAAQ;MACdC,EAAE,EAAEH,IAAI,CAACc,SAAS;MAClBG,IAAI,EAAEjB,IAAI,CAACkB,WAAW;MACtBC,MAAM,EAAEnB,IAAI,CAACoB;IACf,CAAC;IACDC,cAAc,EAAE,EAAE;IAClBC,UAAU,EAAEtB,IAAI,CAACuB,WAAW,IAAI,CAAC;IACjCC,WAAW,EAAExB,IAAI,CAACyB,aAAa,IAAI,IAAI;IACvCC,aAAa,EAAE1B,IAAI,CAAC2B,cAAc,IAAI,IAAI;IAC1CC,WAAW,EAAE5B,IAAI,CAAC6B,YAAY;IAC9BC,uBAAuB,EAAE9B,IAAI,CAAC+B,uBAAuB;IACrDC,eAAe,EAAEhC,IAAI,CAACiC,iBAAiB,GACnC;MACEC,KAAK,EAAElC,IAAI,CAACiC,iBAAiB,CAACC,KAAK;MACnCC,aAAa,EAAEnC,IAAI,CAACiC,iBAAiB,CAACG,cAAc;MACpDC,MAAM,EAAErC,IAAI,CAACiC,iBAAiB,CAACI;IACjC,CAAC,GACD;EACN,CAAC;AACH","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages_data_options.d.ts","sourceRoot":"","sources":["../../../src/utils/request/messages_data_options.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB;;;;
|
|
1
|
+
{"version":3,"file":"messages_data_options.d.ts","sourceRoot":"","sources":["../../../src/utils/request/messages_data_options.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB;;;;CAmB5B,CAAA;AAED,eAAO,MAAM,kBAAkB,UAAgC,CAAA"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const getMessageFields = {
|
|
2
|
-
Message: ['text', 'text_edited_at', 'mine', 'attachments', 'created_at', 'deleted_at', 'author', 'reaction_counts', 'reply_count', 'reply_root', 'message_type', 'person_ids_for_system_event', 'system_text_parts'],
|
|
2
|
+
Message: ['text', 'text_edited_at', 'mine', 'attachments', 'created_at', 'deleted_at', 'author', 'reaction_counts', 'reply_count', 'reply_root', 'idempotent_key', 'message_type', 'person_ids_for_system_event', 'system_text_parts'],
|
|
3
3
|
Person: ['name', 'avatar'],
|
|
4
4
|
ReactionCount: ['value', 'count', 'mine', 'message_id', 'author_ids']
|
|
5
5
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages_data_options.js","names":["getMessageFields","Message","Person","ReactionCount","getMessagesInclude"],"sources":["../../../src/utils/request/messages_data_options.ts"],"sourcesContent":["export const getMessageFields = {\n Message: [\n 'text',\n 'text_edited_at',\n 'mine',\n 'attachments',\n 'created_at',\n 'deleted_at',\n 'author',\n 'reaction_counts',\n 'reply_count',\n 'reply_root',\n 'message_type',\n 'person_ids_for_system_event',\n 'system_text_parts',\n ],\n Person: ['name', 'avatar'],\n ReactionCount: ['value', 'count', 'mine', 'message_id', 'author_ids'],\n}\n\nexport const getMessagesInclude = ['author', 'reaction_counts']\n"],"mappings":"AAAA,OAAO,MAAMA,gBAAgB,GAAG;EAC9BC,OAAO,EAAE,CACP,MAAM,EACN,gBAAgB,EAChB,MAAM,EACN,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,QAAQ,EACR,iBAAiB,EACjB,aAAa,EACb,YAAY,EACZ,cAAc,EACd,6BAA6B,EAC7B,mBAAmB,CACpB;EACDC,MAAM,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC;EAC1BC,aAAa,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY;AACtE,CAAC;AAED,OAAO,MAAMC,kBAAkB,GAAG,CAAC,QAAQ,EAAE,iBAAiB,CAAC","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"messages_data_options.js","names":["getMessageFields","Message","Person","ReactionCount","getMessagesInclude"],"sources":["../../../src/utils/request/messages_data_options.ts"],"sourcesContent":["export const getMessageFields = {\n Message: [\n 'text',\n 'text_edited_at',\n 'mine',\n 'attachments',\n 'created_at',\n 'deleted_at',\n 'author',\n 'reaction_counts',\n 'reply_count',\n 'reply_root',\n 'idempotent_key',\n 'message_type',\n 'person_ids_for_system_event',\n 'system_text_parts',\n ],\n Person: ['name', 'avatar'],\n ReactionCount: ['value', 'count', 'mine', 'message_id', 'author_ids'],\n}\n\nexport const getMessagesInclude = ['author', 'reaction_counts']\n"],"mappings":"AAAA,OAAO,MAAMA,gBAAgB,GAAG;EAC9BC,OAAO,EAAE,CACP,MAAM,EACN,gBAAgB,EAChB,MAAM,EACN,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,QAAQ,EACR,iBAAiB,EACjB,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACd,6BAA6B,EAC7B,mBAAmB,CACpB;EACDC,MAAM,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC;EAC1BC,aAAa,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY;AACtE,CAAC;AAED,OAAO,MAAMC,kBAAkB,GAAG,CAAC,QAAQ,EAAE,iBAAiB,CAAC","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planningcenter/chat-react-native",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.2.0-rc.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -84,5 +84,5 @@
|
|
|
84
84
|
"sf-symbols-typescript": "^2.2.0",
|
|
85
85
|
"typescript": "~6.0.3"
|
|
86
86
|
},
|
|
87
|
-
"gitHead": "
|
|
87
|
+
"gitHead": "c3c00b88ac61f4be9a12571182d60e01017f8dde"
|
|
88
88
|
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { InfiniteData, QueryClientProvider, notifyManager } from '@tanstack/react-query'
|
|
2
|
+
import { act, renderHook } from '@testing-library/react-native'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
import { chatQueryClient } from '../../contexts/api_provider'
|
|
5
|
+
import type { ApiCollection, MessageResource } from '../../types'
|
|
6
|
+
import { getMessagesQueryKey } from '../../utils/request/get_messages'
|
|
7
|
+
import { useMessageCreateOrUpdate } from '../use_message_create_or_update'
|
|
8
|
+
|
|
9
|
+
jest.mock('../../contexts/api_provider', () => {
|
|
10
|
+
const { QueryClient } = require('@tanstack/react-query')
|
|
11
|
+
// gcTime 0 so nothing outlives the test and keeps jest from exiting
|
|
12
|
+
return {
|
|
13
|
+
chatQueryClient: new QueryClient({
|
|
14
|
+
defaultOptions: { queries: { gcTime: 0 }, mutations: { gcTime: 0 } },
|
|
15
|
+
}),
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const mockPost = jest.fn()
|
|
20
|
+
jest.mock('../use_api_client', () => ({
|
|
21
|
+
useApiClient: () => ({ chat: { post: mockPost, patch: jest.fn() } }),
|
|
22
|
+
}))
|
|
23
|
+
|
|
24
|
+
jest.mock('../use_current_person', () => ({
|
|
25
|
+
useCurrentPerson: () => ({ type: 'Person', id: 1, name: 'Me', avatar: null }),
|
|
26
|
+
}))
|
|
27
|
+
|
|
28
|
+
// Flush TanStack's batched state updates inside act, as its own tests do
|
|
29
|
+
notifyManager.setNotifyFunction(fn => act(fn))
|
|
30
|
+
|
|
31
|
+
type MessagesQueryData = InfiniteData<ApiCollection<MessageResource>>
|
|
32
|
+
|
|
33
|
+
const conversationId = 7
|
|
34
|
+
const queryKey = getMessagesQueryKey({ conversation_id: conversationId })
|
|
35
|
+
|
|
36
|
+
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
37
|
+
<QueryClientProvider client={chatQueryClient}>{children}</QueryClientProvider>
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
const pendingMessagesInCache = () =>
|
|
41
|
+
(chatQueryClient.getQueryData<MessagesQueryData>(queryKey)?.pages[0].data ?? []).filter(
|
|
42
|
+
m => m.pending
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
const sentKey = (call: number): string =>
|
|
46
|
+
mockPost.mock.calls[call][0].data.data.attributes.idempotent_key
|
|
47
|
+
|
|
48
|
+
// What the cache held when each request went out, i.e. after onMutate ran
|
|
49
|
+
let pendingAtSend: MessageResource[][]
|
|
50
|
+
|
|
51
|
+
describe('useMessageCreateOrUpdate idempotent key', () => {
|
|
52
|
+
beforeEach(() => {
|
|
53
|
+
chatQueryClient.clear()
|
|
54
|
+
chatQueryClient.setQueryData<MessagesQueryData>(queryKey, {
|
|
55
|
+
pages: [{ data: [], links: {}, meta: { count: 0, totalCount: 0 } }],
|
|
56
|
+
pageParams: [],
|
|
57
|
+
})
|
|
58
|
+
pendingAtSend = []
|
|
59
|
+
mockPost.mockReset()
|
|
60
|
+
mockPost.mockImplementation(async request => {
|
|
61
|
+
pendingAtSend.push(pendingMessagesInCache())
|
|
62
|
+
const key = request.data.data.attributes.idempotent_key
|
|
63
|
+
return { data: { type: 'Message', id: `real-${key}`, idempotentKey: key, mine: true } }
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('gives the optimistic message the same key that is sent to the server', async () => {
|
|
68
|
+
const { result } = renderHook(() => useMessageCreateOrUpdate({ conversationId }), { wrapper })
|
|
69
|
+
|
|
70
|
+
await act(() => result.current.mutateAsync({ text: 'ok' }))
|
|
71
|
+
|
|
72
|
+
expect(sentKey(0)).toEqual(expect.any(String))
|
|
73
|
+
expect(pendingAtSend[0].map(m => m.idempotentKey)).toEqual([sentKey(0)])
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('mints a distinct key for each send, even with identical text', async () => {
|
|
77
|
+
const { result } = renderHook(() => useMessageCreateOrUpdate({ conversationId }), { wrapper })
|
|
78
|
+
|
|
79
|
+
await act(() =>
|
|
80
|
+
Promise.all([
|
|
81
|
+
result.current.mutateAsync({ text: 'ok' }),
|
|
82
|
+
result.current.mutateAsync({ text: 'ok' }),
|
|
83
|
+
])
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
expect(sentKey(0)).not.toBe(sentKey(1))
|
|
87
|
+
expect(pendingAtSend[1].map(m => m.idempotentKey).sort()).toEqual(
|
|
88
|
+
[sentKey(0), sentKey(1)].sort()
|
|
89
|
+
)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('reuses the original key when retrying a failed send', async () => {
|
|
93
|
+
const failed = {
|
|
94
|
+
type: 'Message',
|
|
95
|
+
id: '01FAILED-temp',
|
|
96
|
+
text: 'ok',
|
|
97
|
+
idempotentKey: 'original-key',
|
|
98
|
+
error: 'Failed to send message',
|
|
99
|
+
} as MessageResource
|
|
100
|
+
const { result } = renderHook(
|
|
101
|
+
() => useMessageCreateOrUpdate({ conversationId, message: failed }),
|
|
102
|
+
{ wrapper }
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
await act(() => result.current.mutateAsync({ text: 'ok' }))
|
|
106
|
+
|
|
107
|
+
expect(sentKey(0)).toBe('original-key')
|
|
108
|
+
expect(pendingAtSend[0].map(m => m.idempotentKey)).toEqual(['original-key'])
|
|
109
|
+
})
|
|
110
|
+
})
|
|
@@ -22,6 +22,14 @@ interface Props {
|
|
|
22
22
|
replyRootId?: string | null
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
interface MessageMutationVariables {
|
|
26
|
+
text: string
|
|
27
|
+
attachments?: DenormalizedAttachmentResourceForCreate[]
|
|
28
|
+
idempotentKey?: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type ResolvedMessageMutationVariables = MessageMutationVariables & { idempotentKey: string }
|
|
32
|
+
|
|
25
33
|
export function useMessageCreateOrUpdate({ conversationId, message, replyRootId }: Props) {
|
|
26
34
|
const messageId = message?.id || null
|
|
27
35
|
const isEditing = !isNewMessage(message)
|
|
@@ -29,13 +37,7 @@ export function useMessageCreateOrUpdate({ conversationId, message, replyRootId
|
|
|
29
37
|
const currentPerson = useCurrentPerson()
|
|
30
38
|
|
|
31
39
|
const mutation = useMutation({
|
|
32
|
-
mutationFn: ({
|
|
33
|
-
text,
|
|
34
|
-
attachments,
|
|
35
|
-
}: {
|
|
36
|
-
text: string
|
|
37
|
-
attachments?: DenormalizedAttachmentResourceForCreate[]
|
|
38
|
-
}) => {
|
|
40
|
+
mutationFn: ({ text, attachments, idempotentKey }: ResolvedMessageMutationVariables) => {
|
|
39
41
|
const requestParams = getMessagesRequestArgs({ conversation_id: conversationId })
|
|
40
42
|
const fieldsWithValueJoined = Object.fromEntries(
|
|
41
43
|
Object.entries(requestParams.data.fields).map(([k, v]) => [k, v.join(',')])
|
|
@@ -46,7 +48,6 @@ export function useMessageCreateOrUpdate({ conversationId, message, replyRootId
|
|
|
46
48
|
...(!isEditing && replyRootId ? { reply_root: { id: replyRootId } } : {}),
|
|
47
49
|
}
|
|
48
50
|
if (!isEditing) {
|
|
49
|
-
const idempotentKey = insecureUUID()
|
|
50
51
|
attributes.idempotent_key = idempotentKey
|
|
51
52
|
startMessageCreationTracking(idempotentKey)
|
|
52
53
|
}
|
|
@@ -71,13 +72,7 @@ export function useMessageCreateOrUpdate({ conversationId, message, replyRootId
|
|
|
71
72
|
})
|
|
72
73
|
}
|
|
73
74
|
},
|
|
74
|
-
onMutate: async ({
|
|
75
|
-
text,
|
|
76
|
-
attachments,
|
|
77
|
-
}: {
|
|
78
|
-
text: string
|
|
79
|
-
attachments?: DenormalizedAttachmentResourceForCreate[]
|
|
80
|
-
}) => {
|
|
75
|
+
onMutate: async ({ text, attachments, idempotentKey }: ResolvedMessageMutationVariables) => {
|
|
81
76
|
if (message && isEditing) {
|
|
82
77
|
const optimisticMessage = optimisticallyUpdateMessage({
|
|
83
78
|
conversationId,
|
|
@@ -96,6 +91,7 @@ export function useMessageCreateOrUpdate({ conversationId, message, replyRootId
|
|
|
96
91
|
attachments,
|
|
97
92
|
currentPerson,
|
|
98
93
|
replyRootId,
|
|
94
|
+
idempotentKey,
|
|
99
95
|
})
|
|
100
96
|
|
|
101
97
|
return { message: optimisticMessage }
|
|
@@ -155,7 +151,24 @@ export function useMessageCreateOrUpdate({ conversationId, message, replyRootId
|
|
|
155
151
|
},
|
|
156
152
|
})
|
|
157
153
|
|
|
158
|
-
|
|
154
|
+
const resolveVariables = (
|
|
155
|
+
variables: MessageMutationVariables
|
|
156
|
+
): ResolvedMessageMutationVariables => ({
|
|
157
|
+
...variables,
|
|
158
|
+
idempotentKey: variables.idempotentKey ?? message?.idempotentKey ?? insecureUUID(),
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
const mutate = (
|
|
162
|
+
variables: MessageMutationVariables,
|
|
163
|
+
options?: Parameters<typeof mutation.mutate>[1]
|
|
164
|
+
) => mutation.mutate(resolveVariables(variables), options)
|
|
165
|
+
|
|
166
|
+
const mutateAsync = (
|
|
167
|
+
variables: MessageMutationVariables,
|
|
168
|
+
options?: Parameters<typeof mutation.mutateAsync>[1]
|
|
169
|
+
) => mutation.mutateAsync(resolveVariables(variables), options)
|
|
170
|
+
|
|
171
|
+
return { ...mutation, mutate, mutateAsync }
|
|
159
172
|
}
|
|
160
173
|
|
|
161
174
|
/**
|
|
@@ -17,6 +17,7 @@ export interface MessageResource {
|
|
|
17
17
|
reactionCounts: ReactionCountResource[]
|
|
18
18
|
replyCount: number
|
|
19
19
|
replyRootId?: string | null
|
|
20
|
+
idempotentKey?: string | null
|
|
20
21
|
messageType: string
|
|
21
22
|
personIdsForSystemEvent?: number[] | null
|
|
22
23
|
systemTextParts?: { names: string[]; overflowCount: number; action: string } | null
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { InfiniteData, QueryClient } from '@tanstack/react-query'
|
|
2
|
+
import type { ApiCollection, MessageResource } from '../../../types'
|
|
3
|
+
import type { PersonResource } from '../../../types/resources/person'
|
|
4
|
+
import { updateCacheWithMessage } from '../messages_cache'
|
|
5
|
+
|
|
6
|
+
type MessagesQueryData = InfiniteData<ApiCollection<MessageResource>>
|
|
7
|
+
|
|
8
|
+
const queryKey = ['messages']
|
|
9
|
+
const author = { type: 'Person', id: 1, name: 'Me', avatar: null } as unknown as PersonResource
|
|
10
|
+
|
|
11
|
+
const message = (overrides: Partial<MessageResource>): MessageResource =>
|
|
12
|
+
({
|
|
13
|
+
type: 'Message',
|
|
14
|
+
text: 'ok',
|
|
15
|
+
html: '',
|
|
16
|
+
createdAt: '2026-01-01T00:00:00Z',
|
|
17
|
+
deletedAt: null,
|
|
18
|
+
textEditedAt: null,
|
|
19
|
+
mine: true,
|
|
20
|
+
attachments: [],
|
|
21
|
+
author,
|
|
22
|
+
reactionCounts: [],
|
|
23
|
+
replyCount: 0,
|
|
24
|
+
replyRootId: null,
|
|
25
|
+
messageType: 'user',
|
|
26
|
+
...overrides,
|
|
27
|
+
}) as MessageResource
|
|
28
|
+
|
|
29
|
+
const seed = (queryClient: QueryClient, ...messages: MessageResource[]) =>
|
|
30
|
+
queryClient.setQueryData<MessagesQueryData>(queryKey, {
|
|
31
|
+
pages: [
|
|
32
|
+
{ data: messages, links: {}, meta: { count: messages.length, totalCount: messages.length } },
|
|
33
|
+
],
|
|
34
|
+
pageParams: [],
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const idsInCache = (queryClient: QueryClient) =>
|
|
38
|
+
queryClient.getQueryData<MessagesQueryData>(queryKey)!.pages[0].data.map(m => m.id)
|
|
39
|
+
|
|
40
|
+
const echo = (queryClient: QueryClient, overrides: Partial<MessageResource>) =>
|
|
41
|
+
updateCacheWithMessage(queryClient, queryKey, message(overrides), 'message.created')
|
|
42
|
+
|
|
43
|
+
describe('updateCacheWithMessage on message.created', () => {
|
|
44
|
+
let queryClient: QueryClient
|
|
45
|
+
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
queryClient = new QueryClient()
|
|
48
|
+
seed(
|
|
49
|
+
queryClient,
|
|
50
|
+
message({ id: '01B-temp', idempotentKey: 'key-b', pending: true }),
|
|
51
|
+
message({ id: '01A-temp', idempotentKey: 'key-a', pending: true })
|
|
52
|
+
)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('swaps out only the pending message with the same idempotent key', () => {
|
|
56
|
+
echo(queryClient, { id: '01A', idempotentKey: 'key-a' })
|
|
57
|
+
|
|
58
|
+
expect(idsInCache(queryClient)).toEqual(['01A', '01B-temp'])
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('does not swap out a pending message that only shares the same text', () => {
|
|
62
|
+
echo(queryClient, { id: '01C', idempotentKey: 'key-c', text: 'ok' })
|
|
63
|
+
|
|
64
|
+
expect(idsInCache(queryClient)).toEqual(['01C', '01B-temp', '01A-temp'])
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('swaps out an attachment-only pending message, which has no text to match', () => {
|
|
68
|
+
seed(
|
|
69
|
+
queryClient,
|
|
70
|
+
message({ id: '01D-temp', idempotentKey: 'key-d', text: null, pending: true })
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
echo(queryClient, { id: '01D', idempotentKey: 'key-d', text: null })
|
|
74
|
+
|
|
75
|
+
expect(idsInCache(queryClient)).toEqual(['01D'])
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('leaves pending messages alone when the echo carries no key', () => {
|
|
79
|
+
echo(queryClient, { id: '01E', idempotentKey: null })
|
|
80
|
+
|
|
81
|
+
expect(idsInCache(queryClient)).toEqual(['01E', '01B-temp', '01A-temp'])
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it("leaves pending messages alone when the echo is someone else's", () => {
|
|
85
|
+
echo(queryClient, { id: '01F', idempotentKey: 'key-a', mine: false })
|
|
86
|
+
|
|
87
|
+
expect(idsInCache(queryClient)).toEqual(['01F', '01B-temp', '01A-temp'])
|
|
88
|
+
})
|
|
89
|
+
})
|
|
@@ -15,20 +15,16 @@ export function updateCacheWithMessage(
|
|
|
15
15
|
) {
|
|
16
16
|
queryClient.setQueryData<MessagesQueryData>(queryKey, prev => {
|
|
17
17
|
if (event === 'message.created') {
|
|
18
|
-
// Before adding the new message, remove
|
|
19
|
-
//
|
|
18
|
+
// Before adding the new message, remove its pending temporary counterpart.
|
|
19
|
+
// The Jolt echo can arrive before the HTTP response has swapped it out.
|
|
20
20
|
let dataAfterTempRemoval = prev
|
|
21
|
-
if (prev && message.
|
|
21
|
+
if (prev && message.mine && message.idempotentKey) {
|
|
22
22
|
dataAfterTempRemoval = deleteRecordInPagesData({
|
|
23
23
|
data: prev,
|
|
24
24
|
record: message,
|
|
25
|
-
matchFn:
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
existingMessage.text === message.text &&
|
|
29
|
-
existingMessage.mine
|
|
30
|
-
)
|
|
31
|
-
},
|
|
25
|
+
matchFn: existingMessage =>
|
|
26
|
+
isTemporaryMessageId(existingMessage.id) &&
|
|
27
|
+
existingMessage.idempotentKey === message.idempotentKey,
|
|
32
28
|
})
|
|
33
29
|
}
|
|
34
30
|
|
|
@@ -15,6 +15,7 @@ export function optimisticallyCreateMessage({
|
|
|
15
15
|
currentPerson,
|
|
16
16
|
message,
|
|
17
17
|
replyRootId,
|
|
18
|
+
idempotentKey,
|
|
18
19
|
}: {
|
|
19
20
|
conversationId: number
|
|
20
21
|
text: string
|
|
@@ -22,6 +23,7 @@ export function optimisticallyCreateMessage({
|
|
|
22
23
|
currentPerson: CurrentPersonResource
|
|
23
24
|
message?: MessageResource
|
|
24
25
|
replyRootId?: string | null
|
|
26
|
+
idempotentKey?: string
|
|
25
27
|
}) {
|
|
26
28
|
const id = message?.id || generateTempMessageId()
|
|
27
29
|
|
|
@@ -53,6 +55,7 @@ export function optimisticallyCreateMessage({
|
|
|
53
55
|
pending: true,
|
|
54
56
|
replyCount: 0,
|
|
55
57
|
replyRootId: replyRootId || null,
|
|
58
|
+
idempotentKey,
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
// Add the optimistic message to the cache
|
|
@@ -29,6 +29,7 @@ export function transformMessageEventDataToMessageResource({
|
|
|
29
29
|
reactionCounts: [],
|
|
30
30
|
replyCount: data.reply_count || 0,
|
|
31
31
|
replyRootId: data.reply_root_id || null,
|
|
32
|
+
idempotentKey: data.idempotent_key ?? null,
|
|
32
33
|
messageType: data.message_type,
|
|
33
34
|
personIdsForSystemEvent: data.system_event_person_ids,
|
|
34
35
|
systemTextParts: data.system_text_parts
|