@@ -14,6 +14,10 @@ import {
1414 isFileReference ,
1515 isImageFile ,
1616} from './fileUtils' ;
17+ import {
18+ AttachmentPostUploadMiddlewareExecutor ,
19+ AttachmentPreUploadMiddlewareExecutor ,
20+ } from './middleware/attachmentManager' ;
1721import { StateStore } from '../store' ;
1822import { generateUUIDv4 } from '../utils' ;
1923import { DEFAULT_UPLOAD_SIZE_LIMIT_BYTES } from '../constants' ;
@@ -22,23 +26,14 @@ import type {
2226 FileLike ,
2327 FileReference ,
2428 LocalAttachment ,
25- LocalAudioAttachment ,
26- LocalFileAttachment ,
29+ LocalNotImageAttachment ,
2730 LocalUploadAttachment ,
28- LocalVideoAttachment ,
29- LocalVoiceRecordingAttachment ,
3031 UploadPermissionCheckResult ,
3132} from './types' ;
3233import type { ChannelResponse , DraftMessage , LocalMessage } from '../types' ;
3334import type { MessageComposer } from './messageComposer' ;
3435import { mergeWithDiff } from '../utils/mergeWith' ;
3536
36- type LocalNotImageAttachment =
37- | LocalFileAttachment
38- | LocalAudioAttachment
39- | LocalVideoAttachment
40- | LocalVoiceRecordingAttachment ;
41-
4237export type FileUploadFilter = ( file : Partial < LocalUploadAttachment > ) => boolean ;
4338
4439export type AttachmentManagerState = {
@@ -71,6 +66,8 @@ const initState = ({
7166export class AttachmentManager {
7267 readonly state : StateStore < AttachmentManagerState > ;
7368 readonly composer : MessageComposer ;
69+ readonly preUploadMiddlewareExecutor : AttachmentPreUploadMiddlewareExecutor ;
70+ readonly postUploadMiddlewareExecutor : AttachmentPostUploadMiddlewareExecutor ;
7471 private attachmentsByIdGetterCache : {
7572 attachmentsById : Record < string , LocalAttachment > ;
7673 attachments : LocalAttachment [ ] ;
@@ -80,6 +77,13 @@ export class AttachmentManager {
8077 this . composer = composer ;
8178 this . state = new StateStore < AttachmentManagerState > ( initState ( { message } ) ) ;
8279 this . attachmentsByIdGetterCache = { attachmentsById : { } , attachments : [ ] } ;
80+
81+ this . preUploadMiddlewareExecutor = new AttachmentPreUploadMiddlewareExecutor ( {
82+ composer,
83+ } ) ;
84+ this . postUploadMiddlewareExecutor = new AttachmentPostUploadMiddlewareExecutor ( {
85+ composer,
86+ } ) ;
8387 }
8488
8589 get attachmentsById ( ) {
@@ -122,10 +126,16 @@ export class AttachmentManager {
122126 this . composer . updateConfig ( { attachments : { acceptedFiles } } ) ;
123127 }
124128
129+ /*
130+ @deprecated attachments can be filtered using injecting pre-upload middleware
131+ */
125132 get fileUploadFilter ( ) {
126133 return this . config . fileUploadFilter ;
127134 }
128135
136+ /*
137+ @deprecated attachments can be filtered using injecting pre-upload middleware
138+ */
129139 set fileUploadFilter ( fileUploadFilter : AttachmentManagerConfig [ 'fileUploadFilter' ] ) {
130140 this . composer . updateConfig ( { attachments : { fileUploadFilter } } ) ;
131141 }
@@ -333,9 +343,9 @@ export class AttachmentManager {
333343 return { uploadBlocked : false } ;
334344 } ;
335345
336- fileToLocalUploadAttachment = async (
346+ static toLocalUploadAttachment = (
337347 fileLike : FileReference | FileLike ,
338- ) : Promise < LocalUploadAttachment > => {
348+ ) : LocalUploadAttachment => {
339349 const file =
340350 isFileReference ( fileLike ) || isFile ( fileLike )
341351 ? fileLike
@@ -345,16 +355,13 @@ export class AttachmentManager {
345355 mimeType : fileLike . type ,
346356 } ) ;
347357
348- const uploadPermissionCheck = await this . getUploadConfigCheck ( file ) ;
349-
350358 const localAttachment : LocalUploadAttachment = {
351359 file_size : file . size ,
352360 mime_type : file . type ,
353361 localMetadata : {
354362 file,
355363 id : generateUUIDv4 ( ) ,
356- uploadPermissionCheck,
357- uploadState : uploadPermissionCheck . uploadBlocked ? 'blocked' : 'pending' ,
364+ uploadState : 'pending' ,
358365 } ,
359366 type : getAttachmentTypeFromMimeType ( file . type ) ,
360367 } ;
@@ -383,10 +390,26 @@ export class AttachmentManager {
383390 return localAttachment ;
384391 } ;
385392
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+
386409 private ensureLocalUploadAttachment = async (
387410 attachment : Partial < LocalUploadAttachment > ,
388411 ) => {
389- if ( ! attachment . localMetadata ?. file || ! attachment . localMetadata . id ) {
412+ if ( ! attachment . localMetadata ?. file ) {
390413 this . client . notifications . addError ( {
391414 message : 'File is required for upload attachment' ,
392415 origin : { emitter : 'AttachmentManager' , context : { attachment } } ,
@@ -395,6 +418,15 @@ export class AttachmentManager {
395418 return ;
396419 }
397420
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+
398430 if ( ! this . fileUploadFilter ( attachment ) ) return ;
399431
400432 const newAttachment = await this . fileToLocalUploadAttachment (
@@ -446,6 +478,7 @@ export class AttachmentManager {
446478 return this . doDefaultUploadRequest ( fileLike ) ;
447479 } ;
448480
481+ // @deprecated use attachmentManager.uploadFile(file)
449482 uploadAttachment = async ( attachment : LocalUploadAttachment ) => {
450483 if ( ! this . isUploadEnabled ) return ;
451484
@@ -546,20 +579,78 @@ export class AttachmentManager {
546579 return uploadedAttachment ;
547580 } ;
548581
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+
549646 uploadFiles = async ( files : FileReference [ ] | FileList | FileLike [ ] ) => {
550647 if ( ! this . isUploadEnabled ) return ;
551648 const iterableFiles : FileReference [ ] | FileLike [ ] = isFileList ( files )
552649 ? Array . from ( files )
553650 : files ;
554- const attachments = await Promise . all (
555- iterableFiles . map ( this . fileToLocalUploadAttachment ) ,
556- ) ;
557651
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 ) ,
563654 ) ;
564655 } ;
565656}
0 commit comments