11import {
22 any ,
33 array ,
4+ empty ,
45 integer ,
6+ length ,
57 literal ,
68 minValue ,
79 nonEmpty ,
@@ -11,75 +13,73 @@ import {
1113 pipe ,
1214 safeParse ,
1315 string ,
16+ transform ,
1417 undefinedable ,
15- union
18+ union ,
19+ unknown
1620} from 'valibot' ;
1721
1822import { type WebChatActivity } from '../types/WebChatActivity' ;
1923
2024const EMPTY_ARRAY = Object . freeze ( [ ] ) ;
2125
26+ type StreamData = {
27+ streamId ?: string ;
28+ streamSequence ?: number ;
29+ streamType ?: string ;
30+ } ;
31+
2232const streamSequenceSchema = pipe ( number ( ) , integer ( ) , minValue ( 1 ) ) ;
2333
24- const livestreamingActivitySchema = union ( [
25- // Interim.
34+ const streamInfoSchema = union ( [
2635 object ( {
27- attachments : optional ( array ( any ( ) ) , EMPTY_ARRAY ) ,
28- channelData : object ( {
29- // "streamId" is optional for the very first activity in the session.
30- streamId : optional ( undefinedable ( string ( ) ) ) ,
31- streamSequence : streamSequenceSchema ,
32- streamType : literal ( 'streaming' )
33- } ) ,
34- id : string ( ) ,
35- // "text" is optional. If not set or empty, it presents a contentless activity.
36- text : optional ( undefinedable ( string ( ) ) ) ,
37- type : literal ( 'typing' )
38- } ) ,
39- // Informative message.
40- object ( {
41- attachments : optional ( array ( any ( ) ) , EMPTY_ARRAY ) ,
42- channelData : object ( {
43- // "streamId" is optional for the very first activity in the session.
44- streamId : optional ( undefinedable ( string ( ) ) ) ,
45- streamSequence : streamSequenceSchema ,
46- streamType : literal ( 'informative' )
47- } ) ,
48- id : string ( ) ,
49- // Informative message must have "text".
50- text : string ( ) ,
51- type : literal ( 'typing' )
36+ // "streamId" is optional for the very first activity in the session.
37+ streamId : optional ( undefinedable ( string ( ) ) ) ,
38+ streamSequence : streamSequenceSchema ,
39+ streamType : union ( [ literal ( 'streaming' ) , literal ( 'informative' ) ] )
5240 } ) ,
53- // Conclude with a message.
5441 object ( {
55- attachments : optional ( array ( any ( ) ) , EMPTY_ARRAY ) ,
56- channelData : object ( {
57- // "streamId" is required for the final activity in the session.
58- // The final activity must not be the sole activity in the session.
59- streamId : pipe ( string ( ) , nonEmpty ( ) ) ,
60- streamType : literal ( 'final' )
61- } ) ,
62- id : string ( ) ,
63- // If "text" is empty, it represents "regretting" the livestream.
64- text : optional ( undefinedable ( string ( ) ) ) ,
65- type : literal ( 'message' )
66- } ) ,
67- // Conclude without a message.
68- object ( {
69- attachments : optional ( array ( any ( ) ) , EMPTY_ARRAY ) ,
70- channelData : object ( {
71- // "streamId" is required for the final activity in the session.
72- // The final activity must not be the sole activity in the session.
73- streamId : pipe ( string ( ) , nonEmpty ( ) ) ,
74- streamType : literal ( 'final' )
75- } ) ,
76- id : string ( ) ,
77- // If "text" is not set or empty, it represents "regretting" the livestream.
78- text : optional ( undefinedable ( literal ( '' ) ) ) ,
79- type : literal ( 'typing' )
42+ // "streamId" is required for the final activity in the session.
43+ // The final activity must not be the sole activity in the session.
44+ streamId : pipe ( string ( ) , nonEmpty ( ) ) ,
45+ streamType : literal ( 'final' )
8046 } )
8147] ) ;
8248
49+ const validEntitiesSchema = union ( [
50+ pipe (
51+ array ( streamInfoSchema ) ,
52+ length ( 1 ) ,
53+ transform ( data => data [ 0 ] )
54+ ) ,
55+ pipe (
56+ array ( unknown ( ) ) ,
57+ empty ( ) ,
58+ transform ( ( ) => undefined )
59+ )
60+ ] ) ;
61+
62+ const validChannelDataSchema = union ( [
63+ streamInfoSchema ,
64+ pipe (
65+ object ( {
66+ webChat : object ( {
67+ receivedAt : number ( )
68+ } )
69+ } ) ,
70+ transform ( ( ) => undefined )
71+ )
72+ ] ) ;
73+
74+ const livestreamingActivitySchema = object ( {
75+ entities : validEntitiesSchema ,
76+ channelData : validChannelDataSchema ,
77+ attachments : optional ( array ( any ( ) ) , EMPTY_ARRAY ) ,
78+ id : string ( ) ,
79+ text : optional ( undefinedable ( string ( ) ) ) ,
80+ type : union ( [ literal ( 'typing' ) , literal ( 'message' ) ] )
81+ } ) ;
82+
8383/**
8484 * Gets the livestreaming metadata of the activity, or `undefined` if the activity is not participating in a livestreaming session.
8585 *
@@ -109,22 +109,32 @@ export default function getActivityLivestreamingMetadata(activity: WebChatActivi
109109 if ( result . success ) {
110110 const { output } = result ;
111111
112+ let streamData : StreamData ;
113+
114+ if ( output . entities !== undefined ) {
115+ streamData = output . entities ;
116+ } else if ( output . channelData !== undefined ) {
117+ streamData = output . channelData ;
118+ } else {
119+ return undefined ;
120+ }
121+
112122 // If the activity is the first in the session, session ID should be the activity ID.
113- const sessionId = output . channelData . streamId || output . id ;
123+ const sessionId = streamData . streamId || output . id ;
114124
115125 return Object . freeze (
116- output . channelData . streamType === 'final'
126+ streamData . streamType === 'final'
117127 ? {
118128 sequenceNumber : Infinity ,
119129 sessionId,
120130 type : 'final activity'
121131 }
122132 : {
123- sequenceNumber : output . channelData . streamSequence ,
133+ sequenceNumber : streamData . streamSequence ,
124134 sessionId,
125135 type : ! ( output . text || output . attachments ?. length )
126136 ? 'contentless'
127- : output . channelData . streamType === 'informative'
137+ : streamData . streamType === 'informative'
128138 ? 'informative message'
129139 : 'interim activity'
130140 }
0 commit comments