@linktr.ee/messaging-react 3.38.2 → 4.0.0-rc-1786383199

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/{Card-2Ms7lBDO.js → Card-B3XsV8Td.js} +2 -2
  2. package/dist/{Card-2Ms7lBDO.js.map → Card-B3XsV8Td.js.map} +1 -1
  3. package/dist/{Card-KwVE3M5j.cjs → Card-DkQdjw4V.cjs} +2 -2
  4. package/dist/{Card-KwVE3M5j.cjs.map → Card-DkQdjw4V.cjs.map} +1 -1
  5. package/dist/index-B1u9TBSD.cjs +3 -0
  6. package/dist/index-B1u9TBSD.cjs.map +1 -0
  7. package/dist/{index-i03XGFsr.js → index-NfAAanC-.js} +372 -371
  8. package/dist/index-NfAAanC-.js.map +1 -0
  9. package/dist/index.cjs +1 -1
  10. package/dist/index.d.ts +18 -13
  11. package/dist/index.js +1 -1
  12. package/dist/testing.d.ts +5 -2
  13. package/package.json +2 -2
  14. package/src/components/{ChannelHeaderRedesign.tsx → ChannelHeader.tsx} +4 -2
  15. package/src/components/{ChannelView.broadcast-name.integration.test.tsx → ChannelView.broadcast-resolution.integration.test.tsx} +11 -9
  16. package/src/components/ChannelView.tsx +15 -15
  17. package/src/components/CustomMessage/BroadcastContext.test.ts +52 -0
  18. package/src/components/CustomMessage/BroadcastContext.ts +101 -0
  19. package/src/components/CustomMessage/CustomMessage.stories.tsx +29 -21
  20. package/src/components/CustomMessage/CustomMessage.test.tsx +49 -20
  21. package/src/components/CustomMessage/SentMessageDeliveryStatus.test.tsx +37 -17
  22. package/src/components/CustomMessage/SentMessageDeliveryStatus.tsx +17 -24
  23. package/src/components/CustomMessage/index.tsx +18 -13
  24. package/src/components/MessagingShell/index.tsx +2 -2
  25. package/src/index.ts +2 -1
  26. package/src/stream-custom-data.ts +5 -2
  27. package/src/types.ts +13 -9
  28. package/dist/index-DzOGH7lG.cjs +0 -3
  29. package/dist/index-DzOGH7lG.cjs.map +0 -1
  30. package/dist/index-i03XGFsr.js.map +0 -1
  31. package/src/components/CustomMessage/BroadcastNameContext.test.ts +0 -43
  32. package/src/components/CustomMessage/BroadcastNameContext.ts +0 -95
@@ -4,7 +4,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
4
4
 
5
5
  import { renderWithProviders, screen } from '../../test/utils'
6
6
 
7
- import { BroadcastNameContext } from './BroadcastNameContext'
7
+ import { BroadcastContext } from './BroadcastContext'
8
8
 
9
9
  import { CustomMessage } from './index'
10
10
 
@@ -91,8 +91,7 @@ describe('CustomMessage', () => {
91
91
  status: 'received',
92
92
  user: { id: 'me', name: 'Me' },
93
93
  metadata: {
94
- broadcast_id: 'broadcast-1',
95
- broadcast_name: 'July 9th Class attendees',
94
+ outbound_id: 'outbound-1',
96
95
  },
97
96
  },
98
97
  isMyMessage: () => true,
@@ -114,10 +113,30 @@ describe('CustomMessage', () => {
114
113
  const firstHandler = vi.fn()
115
114
  const secondHandler = vi.fn()
116
115
  const { rerender } = renderWithProviders(
117
- <CustomMessage onBroadcastClick={firstHandler} />
116
+ <BroadcastContext.Provider
117
+ value={{
118
+ 'outbound-1': {
119
+ id: 'broadcast-1',
120
+ name: 'July 9th Class attendees',
121
+ },
122
+ }}
123
+ >
124
+ <CustomMessage onBroadcastClick={firstHandler} />
125
+ </BroadcastContext.Provider>
118
126
  )
119
127
 
120
- rerender(<CustomMessage onBroadcastClick={secondHandler} />)
128
+ rerender(
129
+ <BroadcastContext.Provider
130
+ value={{
131
+ 'outbound-1': {
132
+ id: 'broadcast-1',
133
+ name: 'July 9th Class attendees',
134
+ },
135
+ }}
136
+ >
137
+ <CustomMessage onBroadcastClick={secondHandler} />
138
+ </BroadcastContext.Provider>
139
+ )
121
140
  screen
122
141
  .getByRole('button', {
123
142
  name: 'View broadcast July 9th Class attendees',
@@ -131,13 +150,18 @@ describe('CustomMessage', () => {
131
150
  })
132
151
  })
133
152
 
134
- it('prefers a resolved broadcast name over the stamped name', () => {
153
+ it('renders the current broadcast returned for the outbound id', () => {
135
154
  renderWithProviders(
136
- <BroadcastNameContext.Provider
137
- value={{ 'broadcast-1': 'July 9th Class absentee' }}
155
+ <BroadcastContext.Provider
156
+ value={{
157
+ 'outbound-1': {
158
+ id: 'broadcast-1',
159
+ name: 'July 9th Class absentee',
160
+ },
161
+ }}
138
162
  >
139
163
  <CustomMessage />
140
- </BroadcastNameContext.Provider>
164
+ </BroadcastContext.Provider>
141
165
  )
142
166
 
143
167
  expect(
@@ -145,31 +169,36 @@ describe('CustomMessage', () => {
145
169
  ).toHaveTextContent('July 9th Class absentee')
146
170
  })
147
171
 
148
- it('falls back to the stamped broadcast name when resolution is unavailable', () => {
172
+ it('hides the label when the outbound record cannot be resolved', () => {
149
173
  renderWithProviders(
150
- <BroadcastNameContext.Provider value={{}}>
174
+ <BroadcastContext.Provider value={{}}>
151
175
  <CustomMessage />
152
- </BroadcastNameContext.Provider>
176
+ </BroadcastContext.Provider>
153
177
  )
154
178
 
155
179
  expect(
156
- screen.getByTestId('sent-message-broadcast-status')
157
- ).toHaveTextContent('July 9th Class attendees')
180
+ screen.queryByTestId('sent-message-broadcast-status')
181
+ ).not.toBeInTheDocument()
158
182
  })
159
183
 
160
- it('renders a later resolved name when the live context value changes', () => {
184
+ it('renders a broadcast when the outbound lookup later resolves', () => {
161
185
  const { rerender } = renderWithProviders(
162
- <BroadcastNameContext.Provider value={{}}>
186
+ <BroadcastContext.Provider value={{}}>
163
187
  <CustomMessage />
164
- </BroadcastNameContext.Provider>
188
+ </BroadcastContext.Provider>
165
189
  )
166
190
 
167
191
  rerender(
168
- <BroadcastNameContext.Provider
169
- value={{ 'broadcast-1': 'July 9th Class absentee' }}
192
+ <BroadcastContext.Provider
193
+ value={{
194
+ 'outbound-1': {
195
+ id: 'broadcast-1',
196
+ name: 'July 9th Class absentee',
197
+ },
198
+ }}
170
199
  >
171
200
  <CustomMessage />
172
- </BroadcastNameContext.Provider>
201
+ </BroadcastContext.Provider>
173
202
  )
174
203
 
175
204
  expect(
@@ -182,15 +182,21 @@ describe('SentMessageDeliveryStatus', () => {
182
182
  message: {
183
183
  id: 'own-1',
184
184
  metadata: {
185
- broadcast_id: 'broadcast-1',
186
- broadcast_name: 'July 9th Class attendees',
185
+ outbound_id: 'outbound-1',
187
186
  },
188
187
  },
189
188
  isMyMessage: () => true,
190
189
  threadList: false,
191
190
  } as never)
192
191
 
193
- renderWithProviders(<SentMessageDeliveryStatus />)
192
+ renderWithProviders(
193
+ <SentMessageDeliveryStatus
194
+ resolvedBroadcast={{
195
+ id: 'broadcast-1',
196
+ name: 'July 9th Class attendees',
197
+ }}
198
+ />
199
+ )
194
200
 
195
201
  expect(
196
202
  screen.getByTestId('sent-message-broadcast-status')
@@ -204,13 +210,16 @@ describe('SentMessageDeliveryStatus', () => {
204
210
  it('uses a generic label and invokes the broadcast callback when the name is missing', () => {
205
211
  const onBroadcastClick = vi.fn()
206
212
  mockedUseMessageContext.mockReturnValue({
207
- message: { id: 'own-2', metadata: { broadcast_id: 'broadcast-1' } },
213
+ message: { id: 'own-2', metadata: { outbound_id: 'outbound-1' } },
208
214
  isMyMessage: () => true,
209
215
  threadList: false,
210
216
  } as never)
211
217
 
212
218
  renderWithProviders(
213
- <SentMessageDeliveryStatus onBroadcastClick={onBroadcastClick} />
219
+ <SentMessageDeliveryStatus
220
+ onBroadcastClick={onBroadcastClick}
221
+ resolvedBroadcast={{ id: 'broadcast-1' }}
222
+ />
214
223
  )
215
224
 
216
225
  expect(
@@ -240,8 +249,7 @@ describe('SentMessageDeliveryStatus', () => {
240
249
  message: {
241
250
  id: 'own-2',
242
251
  metadata: {
243
- broadcast_id: 'broadcast-1',
244
- broadcast_name: 'July 9th Class attendees',
252
+ outbound_id: 'outbound-1',
245
253
  },
246
254
  },
247
255
  isMyMessage: () => true,
@@ -249,7 +257,13 @@ describe('SentMessageDeliveryStatus', () => {
249
257
  } as never)
250
258
 
251
259
  renderWithProviders(
252
- <SentMessageDeliveryStatus onBroadcastClick={onBroadcastClick} />
260
+ <SentMessageDeliveryStatus
261
+ onBroadcastClick={onBroadcastClick}
262
+ resolvedBroadcast={{
263
+ id: 'broadcast-1',
264
+ name: 'July 9th Class attendees',
265
+ }}
266
+ />
253
267
  )
254
268
 
255
269
  const button = screen.getByRole('button', {
@@ -274,8 +288,7 @@ describe('SentMessageDeliveryStatus', () => {
274
288
  message: {
275
289
  id: 'own-2',
276
290
  metadata: {
277
- broadcast_id: 'broadcast-1',
278
- broadcast_name: 'July 9th Class attendees',
291
+ outbound_id: 'outbound-1',
279
292
  },
280
293
  },
281
294
  isMyMessage: () => true,
@@ -285,7 +298,10 @@ describe('SentMessageDeliveryStatus', () => {
285
298
  renderWithProviders(
286
299
  <SentMessageDeliveryStatus
287
300
  onBroadcastClick={onBroadcastClick}
288
- resolvedBroadcastName="July 9th Class absentee"
301
+ resolvedBroadcast={{
302
+ id: 'broadcast-1',
303
+ name: 'July 9th Class absentee',
304
+ }}
289
305
  />
290
306
  )
291
307
 
@@ -307,8 +323,7 @@ describe('SentMessageDeliveryStatus', () => {
307
323
  message: {
308
324
  id: 'own-3',
309
325
  metadata: {
310
- broadcast_id: 'broadcast-1',
311
- broadcast_name: 'Old campaign name',
326
+ outbound_id: 'outbound-1',
312
327
  },
313
328
  },
314
329
  isMyMessage: () => true,
@@ -318,7 +333,7 @@ describe('SentMessageDeliveryStatus', () => {
318
333
  renderWithProviders(
319
334
  <SentMessageDeliveryStatus
320
335
  onBroadcastClick={onBroadcastClick}
321
- resolvedBroadcastName="Broadcast"
336
+ resolvedBroadcast={{ id: 'broadcast-1', name: 'Broadcast' }}
322
337
  />
323
338
  )
324
339
 
@@ -339,8 +354,7 @@ describe('SentMessageDeliveryStatus', () => {
339
354
  message: {
340
355
  id: 'broadcast-1',
341
356
  metadata: {
342
- broadcast_id: 'broadcast-1',
343
- broadcast_name: 'July 9th Class attendees',
357
+ outbound_id: 'outbound-1',
344
358
  },
345
359
  },
346
360
  isMyMessage: () => false,
@@ -348,7 +362,13 @@ describe('SentMessageDeliveryStatus', () => {
348
362
  } as never)
349
363
 
350
364
  renderWithProviders(
351
- <SentMessageDeliveryStatus onBroadcastClick={vi.fn()} />
365
+ <SentMessageDeliveryStatus
366
+ onBroadcastClick={vi.fn()}
367
+ resolvedBroadcast={{
368
+ id: 'broadcast-1',
369
+ name: 'July 9th Class attendees',
370
+ }}
371
+ />
352
372
  )
353
373
 
354
374
  expect(
@@ -11,6 +11,11 @@ import {
11
11
  useMessageContext,
12
12
  } from 'stream-chat-react'
13
13
 
14
+ import type {
15
+ BroadcastClickHandler,
16
+ ResolvedBroadcast,
17
+ } from '../../types'
18
+
14
19
  /**
15
20
  * Message status stamps for sending and terminal `MessageStatus` states
16
21
  * (sent / delivered / read), plus failed messages.
@@ -96,18 +101,13 @@ const SendingStatus = () => {
96
101
  }
97
102
 
98
103
  const BroadcastStatus = ({
99
- broadcastId,
100
- broadcastName,
101
- resolvedBroadcastName,
104
+ broadcast,
102
105
  onBroadcastClick,
103
106
  }: {
104
- broadcastId: string
105
- broadcastName?: string
106
- resolvedBroadcastName?: string
107
- onBroadcastClick?: (broadcast: { id: string; name?: string }) => void
107
+ broadcast: ResolvedBroadcast
108
+ onBroadcastClick?: BroadcastClickHandler
108
109
  }) => {
109
- const campaignName = resolvedBroadcastName || broadcastName
110
- const name = campaignName || 'Broadcast'
110
+ const name = broadcast.name || 'Broadcast'
111
111
  // Underline the name only in the clickable variant (it reads as a link).
112
112
  // The name's colour still comes from the `.str-chat`-scoped grid rule in
113
113
  // styles.css; we only add the underline here.
@@ -133,12 +133,7 @@ const BroadcastStatus = ({
133
133
  type="button"
134
134
  className="inline-flex cursor-pointer items-center gap-1 border-0 bg-transparent p-0 text-inherit [font:inherit]"
135
135
  aria-label={`View broadcast ${name}`}
136
- onClick={() =>
137
- onBroadcastClick({
138
- id: broadcastId,
139
- ...(campaignName ? { name: campaignName } : {}),
140
- })
141
- }
136
+ onClick={() => onBroadcastClick(broadcast)}
142
137
  >
143
138
  {content}
144
139
  </button>
@@ -170,10 +165,10 @@ const FailedStatus = () => (
170
165
  */
171
166
  export const SentMessageDeliveryStatus = ({
172
167
  onBroadcastClick,
173
- resolvedBroadcastName,
168
+ resolvedBroadcast,
174
169
  }: {
175
- onBroadcastClick?: (broadcast: { id: string; name?: string }) => void
176
- resolvedBroadcastName?: string
170
+ onBroadcastClick?: BroadcastClickHandler
171
+ resolvedBroadcast?: ResolvedBroadcast
177
172
  }) => {
178
173
  const { isMyMessage, message, threadList } = useMessageContext(
179
174
  'SentMessageDeliveryStatus'
@@ -195,16 +190,14 @@ export const SentMessageDeliveryStatus = ({
195
190
  return null
196
191
  }
197
192
 
198
- const broadcastId = message.metadata?.broadcast_id
199
- const broadcastName = message.metadata?.broadcast_name
193
+ const outboundId = message.metadata?.outbound_id
200
194
  const broadcastStatus =
201
195
  message.status !== 'sending' &&
202
196
  message.status !== 'failed' &&
203
- broadcastId ? (
197
+ outboundId &&
198
+ resolvedBroadcast ? (
204
199
  <BroadcastStatus
205
- broadcastId={broadcastId}
206
- broadcastName={broadcastName}
207
- resolvedBroadcastName={resolvedBroadcastName}
200
+ broadcast={resolvedBroadcast}
208
201
  onBroadcastClick={onBroadcastClick}
209
202
  />
210
203
  ) : null
@@ -29,12 +29,16 @@ import {
29
29
  type MessageUIComponentProps,
30
30
  } from 'stream-chat-react'
31
31
 
32
+ import type {
33
+ BroadcastClickHandler,
34
+ ResolvedBroadcast,
35
+ } from '../../types'
32
36
  import { getMessageDisplayText } from '../../utils/getMessageDisplayText'
33
37
  import { Avatar } from '../Avatar'
34
38
  import { isLinkAttachment } from '../MediaMessage'
35
39
  import type { BubbleGroupPosition } from '../MessageAttachment/types'
36
40
 
37
- import { BroadcastNameContext } from './BroadcastNameContext'
41
+ import { BroadcastContext } from './BroadcastContext'
38
42
  import { useCustomMessage } from './context'
39
43
  import LockedAttachment from './LockedAttachment'
40
44
  import {
@@ -53,13 +57,13 @@ import StreamAttachmentMessage, {
53
57
 
54
58
  type CustomMessageUIComponentProps = MessageUIComponentProps & {
55
59
  viewerLanguage?: string
56
- onBroadcastClick?: (broadcast: { id: string; name?: string }) => void
60
+ onBroadcastClick?: BroadcastClickHandler
57
61
  }
58
62
 
59
63
  type CustomMessageWithContextProps = MessageContextValue & {
60
64
  viewerLanguage?: string
61
- onBroadcastClick?: (broadcast: { id: string; name?: string }) => void
62
- resolvedBroadcastName?: string
65
+ onBroadcastClick?: BroadcastClickHandler
66
+ resolvedBroadcast?: ResolvedBroadcast
63
67
  }
64
68
 
65
69
  const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
@@ -81,7 +85,7 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
81
85
  threadList,
82
86
  viewerLanguage,
83
87
  onBroadcastClick,
84
- resolvedBroadcastName,
88
+ resolvedBroadcast,
85
89
  } = props
86
90
 
87
91
  const { client } = useChatContext('CustomMessage')
@@ -437,7 +441,7 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
437
441
  )}
438
442
  <SentMessageDeliveryStatus
439
443
  onBroadcastClick={onBroadcastClick}
440
- resolvedBroadcastName={resolvedBroadcastName}
444
+ resolvedBroadcast={resolvedBroadcast}
441
445
  />
442
446
  {showReplyCountButton && (
443
447
  <MessageRepliesCountButton
@@ -458,25 +462,26 @@ const MemoizedCustomMessage = React.memo(
458
462
  (prev, next) => {
459
463
  if (prev.viewerLanguage !== next.viewerLanguage) return false
460
464
  if (prev.onBroadcastClick !== next.onBroadcastClick) return false
461
- if (prev.resolvedBroadcastName !== next.resolvedBroadcastName) return false
465
+ if (prev.resolvedBroadcast?.id !== next.resolvedBroadcast?.id) return false
466
+ if (prev.resolvedBroadcast?.name !== next.resolvedBroadcast?.name) return false
462
467
  return areMessageUIPropsEqual(prev, next)
463
468
  }
464
469
  ) as typeof CustomMessageWithContext
465
470
 
466
471
  export const CustomMessage = (props: CustomMessageUIComponentProps) => {
467
472
  const messageContext = useMessageContext('CustomMessage')
468
- const broadcastNamesById = useContext(BroadcastNameContext)
469
- const broadcastId = messageContext.message.metadata?.broadcast_id
470
- const resolvedBroadcastName =
471
- typeof broadcastId === 'string'
472
- ? broadcastNamesById?.[broadcastId]
473
+ const broadcastsByOutboundId = useContext(BroadcastContext)
474
+ const outboundId = messageContext.message.metadata?.outbound_id
475
+ const resolvedBroadcast =
476
+ typeof outboundId === 'string'
477
+ ? broadcastsByOutboundId?.[outboundId]
473
478
  : undefined
474
479
 
475
480
  return (
476
481
  <MemoizedCustomMessage
477
482
  {...messageContext}
478
483
  {...props}
479
- resolvedBroadcastName={resolvedBroadcastName}
484
+ resolvedBroadcast={resolvedBroadcast}
480
485
  />
481
486
  )
482
487
  }
@@ -38,7 +38,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
38
38
  renderChannelActions,
39
39
  onParticipantNameClick,
40
40
  onBroadcastClick,
41
- resolveBroadcastName,
41
+ resolveBroadcast,
42
42
  renderMessage,
43
43
  onMessageLinkClick,
44
44
  showChannelInfo,
@@ -264,7 +264,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
264
264
  renderChannelActions={renderChannelActions}
265
265
  onParticipantNameClick={onParticipantNameClick}
266
266
  onBroadcastClick={onBroadcastClick}
267
- resolveBroadcastName={resolveBroadcastName}
267
+ resolveBroadcast={resolveBroadcast}
268
268
  renderMessage={renderMessage}
269
269
  onMessageLinkClick={onMessageLinkClick}
270
270
  showChannelInfo={showChannelInfo}
package/src/index.ts CHANGED
@@ -60,7 +60,8 @@ export type {
60
60
  MessagingCapabilities,
61
61
  MessageLinkClickHandler,
62
62
  BroadcastClickHandler,
63
- BroadcastNameResolver,
63
+ BroadcastResolver,
64
+ ResolvedBroadcast,
64
65
  Participant,
65
66
  LockedAttachmentSource,
66
67
  } from './types'
@@ -66,8 +66,11 @@ export interface MessageMetadata {
66
66
  * `custom_type` — it identifies the send context, not the message kind. (MES-1266)
67
67
  */
68
68
  sent_from?: 'stack_detail'
69
- broadcast_id?: string
70
- broadcast_name?: string
69
+ /**
70
+ * Opaque Outbound Record id on automation broadcast messages. Hosts resolve
71
+ * this server-side to the current automation id and name.
72
+ */
73
+ outbound_id?: string
71
74
  }
72
75
 
73
76
  /**
package/src/types.ts CHANGED
@@ -36,12 +36,16 @@ export type MessageLinkClickHandler = (
36
36
  message: LocalMessage | undefined
37
37
  ) => void
38
38
 
39
- export type BroadcastClickHandler = (broadcast: {
39
+ export type ResolvedBroadcast = {
40
40
  id: string
41
41
  name?: string
42
- }) => void
42
+ }
43
+
44
+ export type BroadcastClickHandler = (broadcast: ResolvedBroadcast) => void
43
45
 
44
- export type BroadcastNameResolver = (broadcastId: string) => string | undefined
46
+ export type BroadcastResolver = (
47
+ outboundId: string
48
+ ) => ResolvedBroadcast | undefined
45
49
 
46
50
  export type ChannelPreviewProps = ChannelPreviewUIComponentProps & {
47
51
  selectedChannel?: Channel | null
@@ -369,12 +373,12 @@ export interface ChannelViewProps {
369
373
  onBroadcastClick?: BroadcastClickHandler
370
374
 
371
375
  /**
372
- * Resolves the current campaign name for a broadcast label. The host must
373
- * provide a new resolver reference when a returned name changes so
374
- * ChannelView re-renders and publishes the new value to memoized rows. The
375
- * stamped message name remains the fallback until this returns a name.
376
+ * Resolves an outbound record id to the current broadcast id and name. The
377
+ * host must provide a new resolver reference when a returned broadcast
378
+ * changes so ChannelView re-renders and publishes the new value to memoized
379
+ * rows. Missing records stay unresolved and render no label.
376
380
  */
377
- resolveBroadcastName?: BroadcastNameResolver
381
+ resolveBroadcast?: BroadcastResolver
378
382
 
379
383
  /**
380
384
  * Passed to Stream `Channel` as `SendButton`. Required for hosts that replace
@@ -444,7 +448,7 @@ export type ChannelViewPassthroughProps = Pick<
444
448
  | 'renderMessage'
445
449
  | 'onMessageLinkClick'
446
450
  | 'onBroadcastClick'
447
- | 'resolveBroadcastName'
451
+ | 'resolveBroadcast'
448
452
  | 'showChannelInfo'
449
453
  | 'composerInput'
450
454
  >