@@ -14,6 +14,10 @@ import {
14
14
isFileReference ,
15
15
isImageFile ,
16
16
} from './fileUtils' ;
17
+ import {
18
+ AttachmentPostUploadMiddlewareExecutor ,
19
+ AttachmentPreUploadMiddlewareExecutor ,
20
+ } from './middleware/attachmentManager' ;
17
21
import { StateStore } from '../store' ;
18
22
import { generateUUIDv4 } from '../utils' ;
19
23
import { DEFAULT_UPLOAD_SIZE_LIMIT_BYTES } from '../constants' ;
@@ -22,23 +26,14 @@ import type {
22
26
FileLike ,
23
27
FileReference ,
24
28
LocalAttachment ,
25
- LocalAudioAttachment ,
26
- LocalFileAttachment ,
29
+ LocalNotImageAttachment ,
27
30
LocalUploadAttachment ,
28
- LocalVideoAttachment ,
29
- LocalVoiceRecordingAttachment ,
30
31
UploadPermissionCheckResult ,
31
32
} from './types' ;
32
33
import type { ChannelResponse , DraftMessage , LocalMessage } from '../types' ;
33
34
import type { MessageComposer } from './messageComposer' ;
34
35
import { mergeWithDiff } from '../utils/mergeWith' ;
35
36
36
- type LocalNotImageAttachment =
37
- | LocalFileAttachment
38
- | LocalAudioAttachment
39
- | LocalVideoAttachment
40
- | LocalVoiceRecordingAttachment ;
41
-
42
37
export type FileUploadFilter = ( file : Partial < LocalUploadAttachment > ) => boolean ;
43
38
44
39
export type AttachmentManagerState = {
@@ -71,6 +66,8 @@ const initState = ({
71
66
export class AttachmentManager {
72
67
readonly state : StateStore < AttachmentManagerState > ;
73
68
readonly composer : MessageComposer ;
69
+ readonly preUploadMiddlewareExecutor : AttachmentPreUploadMiddlewareExecutor ;
70
+ readonly postUploadMiddlewareExecutor : AttachmentPostUploadMiddlewareExecutor ;
74
71
private attachmentsByIdGetterCache : {
75
72
attachmentsById : Record < string , LocalAttachment > ;
76
73
attachments : LocalAttachment [ ] ;
@@ -80,6 +77,13 @@ export class AttachmentManager {
80
77
this . composer = composer ;
81
78
this . state = new StateStore < AttachmentManagerState > ( initState ( { message } ) ) ;
82
79
this . attachmentsByIdGetterCache = { attachmentsById : { } , attachments : [ ] } ;
80
+
81
+ this . preUploadMiddlewareExecutor = new AttachmentPreUploadMiddlewareExecutor ( {
82
+ composer,
83
+ } ) ;
84
+ this . postUploadMiddlewareExecutor = new AttachmentPostUploadMiddlewareExecutor ( {
85
+ composer,
86
+ } ) ;
83
87
}
84
88
85
89
get attachmentsById ( ) {
@@ -122,10 +126,16 @@ export class AttachmentManager {
122
126
this . composer . updateConfig ( { attachments : { acceptedFiles } } ) ;
123
127
}
124
128
129
+ /*
130
+ @deprecated attachments can be filtered using injecting pre-upload middleware
131
+ */
125
132
get fileUploadFilter ( ) {
126
133
return this . config . fileUploadFilter ;
127
134
}
128
135
136
+ /*
137
+ @deprecated attachments can be filtered using injecting pre-upload middleware
138
+ */
129
139
set fileUploadFilter ( fileUploadFilter : AttachmentManagerConfig [ 'fileUploadFilter' ] ) {
130
140
this . composer . updateConfig ( { attachments : { fileUploadFilter } } ) ;
131
141
}
@@ -333,9 +343,9 @@ export class AttachmentManager {
333
343
return { uploadBlocked : false } ;
334
344
} ;
335
345
336
- fileToLocalUploadAttachment = async (
346
+ static toLocalUploadAttachment = (
337
347
fileLike : FileReference | FileLike ,
338
- ) : Promise < LocalUploadAttachment > => {
348
+ ) : LocalUploadAttachment => {
339
349
const file =
340
350
isFileReference ( fileLike ) || isFile ( fileLike )
341
351
? fileLike
@@ -345,16 +355,13 @@ export class AttachmentManager {
345
355
mimeType : fileLike . type ,
346
356
} ) ;
347
357
348
- const uploadPermissionCheck = await this . getUploadConfigCheck ( file ) ;
349
-
350
358
const localAttachment : LocalUploadAttachment = {
351
359
file_size : file . size ,
352
360
mime_type : file . type ,
353
361
localMetadata : {
354
362
file,
355
363
id : generateUUIDv4 ( ) ,
356
- uploadPermissionCheck,
357
- uploadState : uploadPermissionCheck . uploadBlocked ? 'blocked' : 'pending' ,
364
+ uploadState : 'pending' ,
358
365
} ,
359
366
type : getAttachmentTypeFromMimeType ( file . type ) ,
360
367
} ;
@@ -383,10 +390,26 @@ export class AttachmentManager {
383
390
return localAttachment ;
384
391
} ;
385
392
393
+ // @deprecated use AttachmentManager.toLocalUploadAttachment(file)
394
+ fileToLocalUploadAttachment = async (
395
+ fileLike : FileReference | FileLike ,
396
+ ) : Promise < LocalUploadAttachment > => {
397
+ const localAttachment = AttachmentManager . toLocalUploadAttachment ( fileLike ) ;
398
+ const uploadPermissionCheck = await this . getUploadConfigCheck (
399
+ localAttachment . localMetadata . file ,
400
+ ) ;
401
+ localAttachment . localMetadata . uploadPermissionCheck = uploadPermissionCheck ;
402
+ localAttachment . localMetadata . uploadState = uploadPermissionCheck . uploadBlocked
403
+ ? 'blocked'
404
+ : 'pending' ;
405
+
406
+ return localAttachment ;
407
+ } ;
408
+
386
409
private ensureLocalUploadAttachment = async (
387
410
attachment : Partial < LocalUploadAttachment > ,
388
411
) => {
389
- if ( ! attachment . localMetadata ?. file || ! attachment . localMetadata . id ) {
412
+ if ( ! attachment . localMetadata ?. file ) {
390
413
this . client . notifications . addError ( {
391
414
message : 'File is required for upload attachment' ,
392
415
origin : { emitter : 'AttachmentManager' , context : { attachment } } ,
@@ -395,6 +418,15 @@ export class AttachmentManager {
395
418
return ;
396
419
}
397
420
421
+ if ( ! attachment . localMetadata . id ) {
422
+ this . client . notifications . addError ( {
423
+ message : 'Local upload attachment missing local id' ,
424
+ origin : { emitter : 'AttachmentManager' , context : { attachment } } ,
425
+ options : { type : 'validation:attachment:id:missing' } ,
426
+ } ) ;
427
+ return ;
428
+ }
429
+
398
430
if ( ! this . fileUploadFilter ( attachment ) ) return ;
399
431
400
432
const newAttachment = await this . fileToLocalUploadAttachment (
@@ -446,6 +478,7 @@ export class AttachmentManager {
446
478
return this . doDefaultUploadRequest ( fileLike ) ;
447
479
} ;
448
480
481
+ // @deprecated use attachmentManager.uploadFile(file)
449
482
uploadAttachment = async ( attachment : LocalUploadAttachment ) => {
450
483
if ( ! this . isUploadEnabled ) return ;
451
484
@@ -546,20 +579,78 @@ export class AttachmentManager {
546
579
return uploadedAttachment ;
547
580
} ;
548
581
582
+ uploadFile = async ( file : FileReference | FileLike ) => {
583
+ const preUpload = await this . preUploadMiddlewareExecutor . execute ( {
584
+ eventName : 'prepare' ,
585
+ initialValue : {
586
+ attachment : AttachmentManager . toLocalUploadAttachment ( file ) ,
587
+ } ,
588
+ mode : 'concurrent' ,
589
+ } ) ;
590
+
591
+ let attachment : LocalUploadAttachment = preUpload . state . attachment ;
592
+
593
+ if ( preUpload . status === 'discard' ) return attachment ;
594
+ // todo: remove with the next major release as filtering can be done in middleware
595
+ // should we return the attachment object?
596
+ if ( ! this . fileUploadFilter ( attachment ) ) return attachment ;
597
+
598
+ if ( attachment . localMetadata . uploadState === 'blocked' ) {
599
+ this . upsertAttachments ( [ attachment ] ) ;
600
+ return preUpload . state . attachment ;
601
+ }
602
+
603
+ attachment = {
604
+ ...attachment ,
605
+ localMetadata : {
606
+ ...attachment . localMetadata ,
607
+ uploadState : 'uploading' ,
608
+ } ,
609
+ } ;
610
+ this . upsertAttachments ( [ attachment ] ) ;
611
+
612
+ let response : MinimumUploadRequestResult | undefined ;
613
+ let error : Error | undefined ;
614
+ try {
615
+ response = await this . doUploadRequest ( file ) ;
616
+ } catch ( err ) {
617
+ error = err instanceof Error ? err : undefined ;
618
+ }
619
+
620
+ const postUpload = await this . postUploadMiddlewareExecutor . execute ( {
621
+ eventName : 'postProcess' ,
622
+ initialValue : {
623
+ attachment : {
624
+ ...attachment ,
625
+ localMetadata : {
626
+ ...attachment . localMetadata ,
627
+ uploadState : error ? 'failed' : 'finished' ,
628
+ } ,
629
+ } ,
630
+ error,
631
+ response,
632
+ } ,
633
+ mode : 'concurrent' ,
634
+ } ) ;
635
+ attachment = postUpload . state . attachment ;
636
+
637
+ if ( postUpload . status === 'discard' ) {
638
+ this . removeAttachments ( [ attachment . localMetadata . id ] ) ;
639
+ return attachment ;
640
+ }
641
+
642
+ this . updateAttachment ( attachment ) ;
643
+ return attachment ;
644
+ } ;
645
+
549
646
uploadFiles = async ( files : FileReference [ ] | FileList | FileLike [ ] ) => {
550
647
if ( ! this . isUploadEnabled ) return ;
551
648
const iterableFiles : FileReference [ ] | FileLike [ ] = isFileList ( files )
552
649
? Array . from ( files )
553
650
: files ;
554
- const attachments = await Promise . all (
555
- iterableFiles . map ( this . fileToLocalUploadAttachment ) ,
556
- ) ;
557
651
558
- return Promise . all (
559
- attachments
560
- . filter ( this . fileUploadFilter )
561
- . slice ( 0 , this . availableUploadSlots )
562
- . map ( this . uploadAttachment ) ,
652
+ return await Promise . all (
653
+ iterableFiles . slice ( 0 , this . availableUploadSlots ) . map ( this . uploadFile ) ,
563
654
) ;
564
655
} ;
565
656
}
0 commit comments