@linktr.ee/messaging-react 1.29.0-rc-1776320021 → 1.29.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/dist/index.js +415 -418
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/ChannelList/index.test.tsx +14 -4
- package/src/components/ChannelList/index.tsx +17 -1
- package/src/components/ChannelView.tsx +5 -8
- package/src/components/CustomMessage/CustomMessage.stories.tsx +11 -6
- package/src/components/CustomMessage/index.tsx +1 -1
- package/src/components/LockedAttachment/LockedAttachment.stories.tsx +18 -48
- package/src/hooks/useRestorePendingMessages.test.ts +15 -47
- package/src/hooks/useRestorePendingMessages.ts +8 -25
package/package.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
+
import type { Channel } from 'stream-chat'
|
|
2
3
|
import { describe, expect, it, vi, beforeEach } from 'vitest'
|
|
3
4
|
|
|
4
5
|
import { renderWithProviders } from '../../test/utils'
|
|
@@ -77,8 +78,8 @@ describe('ChannelList', () => {
|
|
|
77
78
|
expect(streamProps.onAddedToChannel).toBe(onAddedToChannel)
|
|
78
79
|
})
|
|
79
80
|
|
|
80
|
-
it('
|
|
81
|
-
const filterFn = vi.fn()
|
|
81
|
+
it('wraps channelRenderFilterFn to restore pending messages and delegates to consumer filter', () => {
|
|
82
|
+
const filterFn = vi.fn((channels: Channel[]) => channels)
|
|
82
83
|
|
|
83
84
|
renderWithProviders(
|
|
84
85
|
React.createElement(ChannelList, {
|
|
@@ -89,9 +90,18 @@ describe('ChannelList', () => {
|
|
|
89
90
|
|
|
90
91
|
expect(streamChannelListMock).toHaveBeenCalledOnce()
|
|
91
92
|
const streamProps = streamChannelListMock.mock.calls[0][0] as {
|
|
92
|
-
channelRenderFilterFn?: unknown
|
|
93
|
+
channelRenderFilterFn?: (channels: unknown[]) => unknown[]
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
|
|
96
|
+
// The wrapper should not be the same reference as the original filter
|
|
97
|
+
expect(streamProps.channelRenderFilterFn).not.toBe(filterFn)
|
|
98
|
+
expect(typeof streamProps.channelRenderFilterFn).toBe('function')
|
|
99
|
+
|
|
100
|
+
// When the wrapper is called, it should delegate to the consumer's filter
|
|
101
|
+
const mockChannels = [
|
|
102
|
+
{ cid: 'ch-1', state: { pending_messages: [], addMessageSorted: vi.fn() } },
|
|
103
|
+
]
|
|
104
|
+
streamProps.channelRenderFilterFn!(mockChannels)
|
|
105
|
+
expect(filterFn).toHaveBeenCalledWith(mockChannels)
|
|
96
106
|
})
|
|
97
107
|
})
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import classNames from 'classnames'
|
|
2
2
|
import React from 'react'
|
|
3
|
+
import type { Channel } from 'stream-chat'
|
|
3
4
|
import { ChannelList as StreamChannelList } from 'stream-chat-react'
|
|
4
5
|
|
|
6
|
+
import { restorePendingMessages } from '../../hooks/useRestorePendingMessages'
|
|
5
7
|
import { useMessagingContext } from '../../providers/MessagingProvider'
|
|
6
8
|
import type { ChannelListProps } from '../../types'
|
|
7
9
|
|
|
@@ -34,6 +36,20 @@ export const ChannelList = React.memo<ChannelListProps>(
|
|
|
34
36
|
// Get debug flag from context
|
|
35
37
|
const { debug = false } = useMessagingContext()
|
|
36
38
|
|
|
39
|
+
// Wrap channelRenderFilterFn to restore pending messages for all channels
|
|
40
|
+
// as soon as they are loaded, without waiting for the user to click into each one.
|
|
41
|
+
const wrappedChannelRenderFilterFn = React.useCallback(
|
|
42
|
+
(channels: Channel[]) => {
|
|
43
|
+
for (const channel of channels) {
|
|
44
|
+
restorePendingMessages(channel)
|
|
45
|
+
}
|
|
46
|
+
return channelRenderFilterFn
|
|
47
|
+
? channelRenderFilterFn(channels)
|
|
48
|
+
: channels
|
|
49
|
+
},
|
|
50
|
+
[channelRenderFilterFn]
|
|
51
|
+
)
|
|
52
|
+
|
|
37
53
|
if (debug) {
|
|
38
54
|
console.log('📺 [ChannelList] 🔄 RENDER START', {
|
|
39
55
|
renderCount: renderCountRef.current,
|
|
@@ -72,7 +88,7 @@ export const ChannelList = React.memo<ChannelListProps>(
|
|
|
72
88
|
}
|
|
73
89
|
onMessageNew={onMessageNew}
|
|
74
90
|
onAddedToChannel={onAddedToChannel}
|
|
75
|
-
channelRenderFilterFn={
|
|
91
|
+
channelRenderFilterFn={wrappedChannelRenderFilterFn}
|
|
76
92
|
Preview={CustomChannelPreview}
|
|
77
93
|
EmptyStateIndicator={customEmptyStateIndicator}
|
|
78
94
|
/>
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ArrowLeftIcon,
|
|
3
|
-
DotsThreeIcon,
|
|
4
|
-
StarIcon,
|
|
5
|
-
} from '@phosphor-icons/react'
|
|
1
|
+
import { ArrowLeftIcon, DotsThreeIcon, StarIcon } from '@phosphor-icons/react'
|
|
6
2
|
import classNames from 'classnames'
|
|
7
3
|
import React, { useCallback, useRef } from 'react'
|
|
8
4
|
import { Channel as ChannelType, LocalMessage } from 'stream-chat'
|
|
@@ -17,7 +13,6 @@ import {
|
|
|
17
13
|
} from 'stream-chat-react'
|
|
18
14
|
|
|
19
15
|
import { useChannelStar } from '../hooks/useChannelStar'
|
|
20
|
-
import { useRestorePendingMessages } from '../hooks/useRestorePendingMessages'
|
|
21
16
|
import type { ChannelViewProps, LockedAttachmentSource } from '../types'
|
|
22
17
|
|
|
23
18
|
import { Avatar } from './Avatar'
|
|
@@ -212,7 +207,10 @@ const ChannelViewInner: React.FC<{
|
|
|
212
207
|
onReportParticipantClick?: () => void
|
|
213
208
|
showStarButton?: boolean
|
|
214
209
|
chatbotVotingEnabled?: boolean
|
|
215
|
-
onAttachmentUnlock?: (
|
|
210
|
+
onAttachmentUnlock?: (
|
|
211
|
+
message: LocalMessage,
|
|
212
|
+
channel: ChannelType
|
|
213
|
+
) => Promise<LockedAttachmentSource>
|
|
216
214
|
onAttachmentDownload?: (message: LocalMessage, channel: ChannelType) => void
|
|
217
215
|
renderChannelBanner?: () => React.ReactNode
|
|
218
216
|
customProfileContent?: React.ReactNode
|
|
@@ -242,7 +240,6 @@ const ChannelViewInner: React.FC<{
|
|
|
242
240
|
renderMessage,
|
|
243
241
|
}) => {
|
|
244
242
|
const { channel } = useChannelStateContext()
|
|
245
|
-
useRestorePendingMessages(channel)
|
|
246
243
|
const infoDialogRef = useRef<HTMLDialogElement>(null)
|
|
247
244
|
|
|
248
245
|
// Get participant info for info dialog
|
|
@@ -106,6 +106,11 @@ interface TemplateProps {
|
|
|
106
106
|
| 'MESSAGE_PAID'
|
|
107
107
|
| 'MESSAGE_CHATBOT'
|
|
108
108
|
| 'MESSAGE_ATTACHMENT'
|
|
109
|
+
payment_status?:
|
|
110
|
+
| 'pending'
|
|
111
|
+
| 'paid'
|
|
112
|
+
| 'failed'
|
|
113
|
+
| 'refunded'
|
|
109
114
|
amount_text?: string
|
|
110
115
|
attachment_title?: string
|
|
111
116
|
attachment_mime_type?: string
|
|
@@ -316,7 +321,12 @@ PaidAttachment.args = {
|
|
|
316
321
|
},
|
|
317
322
|
{
|
|
318
323
|
id: 'msg-2',
|
|
319
|
-
text: '
|
|
324
|
+
text: 'Yes, of course!',
|
|
325
|
+
user: storyUsers.creator,
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
id: 'msg-3',
|
|
329
|
+
text: 'let me know if you have any questions!',
|
|
320
330
|
user: storyUsers.creator,
|
|
321
331
|
metadata: {
|
|
322
332
|
custom_type: 'MESSAGE_ATTACHMENT',
|
|
@@ -327,11 +337,6 @@ PaidAttachment.args = {
|
|
|
327
337
|
attachment_detail: '1:20',
|
|
328
338
|
},
|
|
329
339
|
},
|
|
330
|
-
{
|
|
331
|
-
id: 'msg-3',
|
|
332
|
-
text: 'let me know if you have any questions!',
|
|
333
|
-
user: storyUsers.creator,
|
|
334
|
-
},
|
|
335
340
|
{
|
|
336
341
|
id: 'msg-4',
|
|
337
342
|
text: 'Looks amazing, unlocking now!',
|
|
@@ -202,7 +202,7 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
|
|
|
202
202
|
}}
|
|
203
203
|
>
|
|
204
204
|
{isAttachment ? (
|
|
205
|
-
<div className="flex flex-col gap-
|
|
205
|
+
<div className={classNames("flex flex-col gap-1", isMine ? "items-end" : "items-start")}>
|
|
206
206
|
<LockedAttachment
|
|
207
207
|
title={message.metadata?.attachment_title}
|
|
208
208
|
mimeType={message.metadata?.attachment_mime_type}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Meta, StoryFn } from '@storybook/react'
|
|
2
|
-
import React
|
|
2
|
+
import React from 'react'
|
|
3
3
|
|
|
4
|
-
import LockedAttachment
|
|
4
|
+
import LockedAttachment from '.'
|
|
5
5
|
|
|
6
6
|
const VIDEO_THUMBNAIL_BLURRED = '/video-thumbnail-blurred.jpg'
|
|
7
7
|
const VIDEO_SOURCE = '/video-source.mp4'
|
|
@@ -22,42 +22,11 @@ const meta: Meta = {
|
|
|
22
22
|
}
|
|
23
23
|
export default meta
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
const withDelay = <T,>(fn: () => Promise<T> | T) => async (): Promise<T> => {
|
|
26
|
+
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
27
|
+
return fn()
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
/** Simulates a slow network by withholding the source for 2s, then loading via blob URL. */
|
|
30
|
-
type SlowCreatorPreviewProps = Omit<CreatorCardProps, 'isPreview'> & { sourcePath: string }
|
|
31
|
-
const SlowCreatorPreview = ({ sourcePath, ...props }: SlowCreatorPreviewProps) => {
|
|
32
|
-
const [source, setSource] = useState<string | undefined>(undefined)
|
|
33
|
-
useEffect(() => {
|
|
34
|
-
setSource(undefined)
|
|
35
|
-
let cancelled = false
|
|
36
|
-
const load = async () => {
|
|
37
|
-
await new Promise((resolve) => setTimeout(resolve, 2000))
|
|
38
|
-
const res = await fetch(sourcePath)
|
|
39
|
-
const blob = await res.blob()
|
|
40
|
-
if (!cancelled) setSource(URL.createObjectURL(blob))
|
|
41
|
-
}
|
|
42
|
-
void load()
|
|
43
|
-
return () => { cancelled = true }
|
|
44
|
-
}, [sourcePath])
|
|
45
|
-
return <LockedAttachment isCreator isPreview source={source} {...props} />
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const Interactive = ({ unlockedSource, ...props }: InteractiveProps) => (
|
|
49
|
-
<LockedAttachment
|
|
50
|
-
{...props}
|
|
51
|
-
onUnlock={async () => {
|
|
52
|
-
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
53
|
-
const res = await fetch(unlockedSource)
|
|
54
|
-
const blob = await res.blob()
|
|
55
|
-
return { source: URL.createObjectURL(blob) }
|
|
56
|
-
}}
|
|
57
|
-
onDownload={() => {}}
|
|
58
|
-
/>
|
|
59
|
-
)
|
|
60
|
-
|
|
61
30
|
const VARIANTS = [
|
|
62
31
|
{
|
|
63
32
|
label: 'Video',
|
|
@@ -168,13 +137,14 @@ export const Visitor: StoryFn = () => (
|
|
|
168
137
|
</td>
|
|
169
138
|
{VARIANTS.map(({ title, mimeType, detail, thumbnail, source }) => (
|
|
170
139
|
<td key={mimeType} className="align-top">
|
|
171
|
-
<
|
|
140
|
+
<LockedAttachment
|
|
172
141
|
title={title}
|
|
173
142
|
amountText="AU$9.99"
|
|
174
143
|
thumbnail={thumbnail}
|
|
175
144
|
mimeType={mimeType}
|
|
176
145
|
detail={detail}
|
|
177
|
-
|
|
146
|
+
onUnlock={withDelay(() => ({ source }))}
|
|
147
|
+
onDownload={() => {}}
|
|
178
148
|
/>
|
|
179
149
|
</td>
|
|
180
150
|
))}
|
|
@@ -185,14 +155,15 @@ export const Visitor: StoryFn = () => (
|
|
|
185
155
|
</td>
|
|
186
156
|
{VARIANTS.map(({ title, mimeType, detail, thumbnail, source }) => (
|
|
187
157
|
<td key={mimeType} className="align-top">
|
|
188
|
-
<
|
|
158
|
+
<LockedAttachment
|
|
189
159
|
title={title}
|
|
190
160
|
amountText="AU$9.99"
|
|
191
161
|
thumbnail={thumbnail}
|
|
192
162
|
mimeType={mimeType}
|
|
193
163
|
detail={detail}
|
|
194
|
-
unlockedSource={source}
|
|
195
164
|
paymentStatus="paid"
|
|
165
|
+
onUnlock={withDelay(() => ({ source }))}
|
|
166
|
+
onDownload={() => {}}
|
|
196
167
|
/>
|
|
197
168
|
</td>
|
|
198
169
|
))}
|
|
@@ -230,9 +201,10 @@ export const Creator: StoryFn = () => (
|
|
|
230
201
|
</td>
|
|
231
202
|
{VARIANTS.map(({ mimeType, detail, thumbnail, source }) => (
|
|
232
203
|
<td key={mimeType} className="align-top">
|
|
233
|
-
<
|
|
204
|
+
<LockedAttachment
|
|
205
|
+
isPreview
|
|
234
206
|
thumbnail={thumbnail}
|
|
235
|
-
|
|
207
|
+
source={source}
|
|
236
208
|
mimeType={mimeType}
|
|
237
209
|
detail={detail}
|
|
238
210
|
placeholderAmountText="A$0.00"
|
|
@@ -247,7 +219,6 @@ export const Creator: StoryFn = () => (
|
|
|
247
219
|
{VARIANTS.map(({ title, mimeType, detail, thumbnail, source }) => (
|
|
248
220
|
<td key={mimeType} className="align-top">
|
|
249
221
|
<LockedAttachment
|
|
250
|
-
isCreator
|
|
251
222
|
title={title}
|
|
252
223
|
thumbnail={thumbnail}
|
|
253
224
|
source={source}
|
|
@@ -266,7 +237,7 @@ export const Creator: StoryFn = () => (
|
|
|
266
237
|
{VARIANTS.map(({ title, mimeType, detail, thumbnail, source }) => (
|
|
267
238
|
<td key={mimeType} className="align-top">
|
|
268
239
|
<LockedAttachment
|
|
269
|
-
|
|
240
|
+
|
|
270
241
|
title={title}
|
|
271
242
|
thumbnail={thumbnail}
|
|
272
243
|
source={source}
|
|
@@ -284,7 +255,6 @@ export const Creator: StoryFn = () => (
|
|
|
284
255
|
{VARIANTS.map(({ title, mimeType, detail, thumbnail, source }) => (
|
|
285
256
|
<td key={mimeType} className="align-top">
|
|
286
257
|
<LockedAttachment
|
|
287
|
-
isCreator
|
|
288
258
|
title={title}
|
|
289
259
|
thumbnail={thumbnail}
|
|
290
260
|
source={source}
|
|
@@ -311,7 +281,6 @@ export const NoThumbnail: StoryFn = () => (
|
|
|
311
281
|
{NO_THUMBNAIL_VARIANTS.map(({ title, mimeType, detail, source }) => (
|
|
312
282
|
<td key={mimeType} className="align-top">
|
|
313
283
|
<LockedAttachment
|
|
314
|
-
isCreator
|
|
315
284
|
title={title}
|
|
316
285
|
source={source}
|
|
317
286
|
mimeType={mimeType}
|
|
@@ -327,12 +296,13 @@ export const NoThumbnail: StoryFn = () => (
|
|
|
327
296
|
</td>
|
|
328
297
|
{NO_THUMBNAIL_VARIANTS.map(({ title, mimeType, detail, source }) => (
|
|
329
298
|
<td key={mimeType} className="align-top">
|
|
330
|
-
<
|
|
299
|
+
<LockedAttachment
|
|
331
300
|
title={title}
|
|
332
301
|
amountText="AU$9.99"
|
|
333
302
|
mimeType={mimeType}
|
|
334
303
|
detail={detail}
|
|
335
|
-
|
|
304
|
+
onUnlock={withDelay(() => ({ source }))}
|
|
305
|
+
onDownload={() => {}}
|
|
336
306
|
/>
|
|
337
307
|
</td>
|
|
338
308
|
))}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { renderHook } from '@testing-library/react'
|
|
2
1
|
import type { Channel } from 'stream-chat'
|
|
3
2
|
import { describe, expect, it, vi, beforeEach } from 'vitest'
|
|
4
3
|
|
|
5
|
-
import {
|
|
4
|
+
import { restorePendingMessages } from './useRestorePendingMessages'
|
|
6
5
|
|
|
7
6
|
const createChannel = (
|
|
8
7
|
overrides: {
|
|
@@ -21,7 +20,7 @@ const createChannel = (
|
|
|
21
20
|
},
|
|
22
21
|
}) as unknown as Channel
|
|
23
22
|
|
|
24
|
-
describe('
|
|
23
|
+
describe('restorePendingMessages', () => {
|
|
25
24
|
beforeEach(() => {
|
|
26
25
|
vi.clearAllMocks()
|
|
27
26
|
})
|
|
@@ -35,7 +34,7 @@ describe('useRestorePendingMessages', () => {
|
|
|
35
34
|
}
|
|
36
35
|
const channel = createChannel({ pending_messages: [pendingMsg] })
|
|
37
36
|
|
|
38
|
-
|
|
37
|
+
restorePendingMessages(channel)
|
|
39
38
|
|
|
40
39
|
expect(channel.state.addMessageSorted).toHaveBeenCalledOnce()
|
|
41
40
|
expect(channel.state.addMessageSorted).toHaveBeenCalledWith(
|
|
@@ -58,7 +57,7 @@ describe('useRestorePendingMessages', () => {
|
|
|
58
57
|
}
|
|
59
58
|
const channel = createChannel({ pending_messages: [msg1, msg2] })
|
|
60
59
|
|
|
61
|
-
|
|
60
|
+
restorePendingMessages(channel)
|
|
62
61
|
|
|
63
62
|
expect(channel.state.addMessageSorted).toHaveBeenCalledTimes(2)
|
|
64
63
|
expect(channel.state.addMessageSorted).toHaveBeenCalledWith(msg1.message)
|
|
@@ -68,54 +67,23 @@ describe('useRestorePendingMessages', () => {
|
|
|
68
67
|
it('does nothing when there are no pending messages', () => {
|
|
69
68
|
const channel = createChannel({ pending_messages: [] })
|
|
70
69
|
|
|
71
|
-
|
|
70
|
+
restorePendingMessages(channel)
|
|
72
71
|
|
|
73
72
|
expect(channel.state.addMessageSorted).not.toHaveBeenCalled()
|
|
74
73
|
})
|
|
75
74
|
|
|
76
|
-
it('
|
|
77
|
-
const
|
|
78
|
-
message: {
|
|
79
|
-
id: 'msg-1',
|
|
80
|
-
text: 'Hello',
|
|
81
|
-
},
|
|
82
|
-
}
|
|
83
|
-
const channel = createChannel({ pending_messages: [pendingMsg] })
|
|
84
|
-
|
|
85
|
-
const { rerender } = renderHook(() => useRestorePendingMessages(channel))
|
|
86
|
-
|
|
87
|
-
rerender()
|
|
88
|
-
rerender()
|
|
89
|
-
|
|
90
|
-
expect(channel.state.addMessageSorted).toHaveBeenCalledOnce()
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
it('restores again when switching to a different channel', () => {
|
|
94
|
-
const pendingMsg = {
|
|
95
|
-
message: {
|
|
96
|
-
id: 'msg-1',
|
|
97
|
-
text: 'Hello',
|
|
98
|
-
},
|
|
99
|
-
}
|
|
100
|
-
const channel1 = createChannel({
|
|
75
|
+
it('does nothing when pending_messages is undefined', () => {
|
|
76
|
+
const channel = {
|
|
101
77
|
cid: 'messaging:channel-1',
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
const { rerender } = renderHook(
|
|
110
|
-
({ channel }) => useRestorePendingMessages(channel),
|
|
111
|
-
{ initialProps: { channel: channel1 } }
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
expect(channel1.state.addMessageSorted).toHaveBeenCalledOnce()
|
|
78
|
+
state: {
|
|
79
|
+
pending_messages: undefined,
|
|
80
|
+
addMessageSorted: vi.fn(),
|
|
81
|
+
},
|
|
82
|
+
} as unknown as Channel
|
|
115
83
|
|
|
116
|
-
|
|
84
|
+
restorePendingMessages(channel)
|
|
117
85
|
|
|
118
|
-
expect(
|
|
86
|
+
expect(channel.state.addMessageSorted).not.toHaveBeenCalled()
|
|
119
87
|
})
|
|
120
88
|
|
|
121
89
|
it('handles pending messages with no metadata gracefully', () => {
|
|
@@ -126,7 +94,7 @@ describe('useRestorePendingMessages', () => {
|
|
|
126
94
|
pending_messages: [noMetadataMsg],
|
|
127
95
|
})
|
|
128
96
|
|
|
129
|
-
|
|
97
|
+
restorePendingMessages(channel)
|
|
130
98
|
|
|
131
99
|
expect(channel.state.addMessageSorted).toHaveBeenCalledOnce()
|
|
132
100
|
expect(channel.state.addMessageSorted).toHaveBeenCalledWith(
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { useEffect, useRef } from 'react'
|
|
2
1
|
import type { Channel } from 'stream-chat'
|
|
3
2
|
|
|
4
3
|
/**
|
|
@@ -6,31 +5,15 @@ import type { Channel } from 'stream-chat'
|
|
|
6
5
|
* appear as if they were already sent.
|
|
7
6
|
*
|
|
8
7
|
* Stream's pending-messages feature removes messages from the channel view
|
|
9
|
-
* once client state is lost (e.g. page refresh). This
|
|
10
|
-
* limitation by reading `channel.state.pending_messages`
|
|
8
|
+
* once client state is lost (e.g. page refresh). This function works around
|
|
9
|
+
* that limitation by reading `channel.state.pending_messages` and
|
|
11
10
|
* re-inserting them via `channel.state.addMessageSorted`.
|
|
12
|
-
*
|
|
13
|
-
* The restoration runs once per channel (tracked by `channel.cid`).
|
|
14
11
|
*/
|
|
15
|
-
export function
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
useEffect(() => {
|
|
19
|
-
const cid = channel.cid
|
|
20
|
-
if (!cid || restoredChannelRef.current === cid) return
|
|
21
|
-
|
|
22
|
-
const pendingMessages = channel.state.pending_messages
|
|
23
|
-
if (!pendingMessages?.length) {
|
|
24
|
-
// No pending messages — mark as restored so we don't re-check on
|
|
25
|
-
// re-renders, but still allow a retry if the channel object changes.
|
|
26
|
-
restoredChannelRef.current = cid
|
|
27
|
-
return
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
for (const pending of pendingMessages) {
|
|
31
|
-
channel.state.addMessageSorted(pending.message)
|
|
32
|
-
}
|
|
12
|
+
export function restorePendingMessages(channel: Channel) {
|
|
13
|
+
const pendingMessages = channel.state.pending_messages
|
|
14
|
+
if (!pendingMessages?.length) return
|
|
33
15
|
|
|
34
|
-
|
|
35
|
-
|
|
16
|
+
for (const pending of pendingMessages) {
|
|
17
|
+
channel.state.addMessageSorted(pending.message)
|
|
18
|
+
}
|
|
36
19
|
}
|