1+ /**
2+ * UploadInterceptor - Orchestrates upload interception and plugin system
3+ *
4+ * This class manages the monkey patching of Pydio's upload system and
5+ * provides a plugin architecture for extending upload functionality.
6+ */
7+ class UploadInterceptor {
8+ constructor ( ) {
9+ this . plugins = new Map ( ) ;
10+ this . isPatched = false ;
11+ this . originalUploadPresigned = null ;
12+ }
13+
14+ /**
15+ * Register a plugin with the upload interceptor
16+ * @param {Object } plugin - Plugin object with name and hooks
17+ * @param {string } plugin.name - Unique plugin name
18+ * @param {Object } plugin.hooks - Event hooks object
19+ * @param {Function } [plugin.hooks.beforeUpload] - Called before upload starts
20+ * @param {Function } [plugin.hooks.onUploadComplete] - Called when upload completes
21+ * @param {Function } [plugin.hooks.onUploadError] - Called when upload errors
22+ * @param {Function } [plugin.hooks.onStatusChange] - Called when upload status changes
23+ */
24+ register ( plugin ) {
25+ if ( ! plugin . name ) {
26+ throw new Error ( 'Plugin must have a name' ) ;
27+ }
28+
29+ if ( this . plugins . has ( plugin . name ) ) {
30+ console . warn ( `Plugin '${ plugin . name } ' is already registered. Overwriting.` ) ;
31+ }
32+
33+ this . plugins . set ( plugin . name , plugin ) ;
34+ console . log ( `UploadInterceptor: Registered plugin '${ plugin . name } '` ) ;
35+
36+ // If we haven't patched yet, patch now that we have plugins
37+ if ( ! this . isPatched ) {
38+ this . patchUploadSystem ( ) ;
39+ }
40+ }
41+
42+ /**
43+ * Unregister a plugin
44+ * @param {string } pluginName - Name of plugin to remove
45+ */
46+ unregister ( pluginName ) {
47+ if ( this . plugins . delete ( pluginName ) ) {
48+ console . log ( `UploadInterceptor: Unregistered plugin '${ pluginName } '` ) ;
49+ }
50+ }
51+
52+ /**
53+ * Get list of registered plugin names
54+ * @returns {string[] } Array of plugin names
55+ */
56+ getRegisteredPlugins ( ) {
57+ return Array . from ( this . plugins . keys ( ) ) ;
58+ }
59+
60+ /**
61+ * Monkey patch the Pydio upload system
62+ */
63+ patchUploadSystem ( ) {
64+ if ( this . isPatched ) {
65+ console . warn ( 'UploadInterceptor: Upload system already patched' ) ;
66+ return ;
67+ }
68+
69+ // Wait for UploaderModel to be available
70+ this . waitForUploaderModel ( ) . then ( ( ) => {
71+ this . applyUploadPatch ( ) ;
72+ } ) ;
73+ }
74+
75+ /**
76+ * Wait for UploaderModel to be defined in the global scope
77+ * @returns {Promise<void> }
78+ */
79+ async waitForUploaderModel ( ) {
80+ while ( typeof UploaderModel === "undefined" || ! UploaderModel . UploadItem ) {
81+ await new Promise ( ( resolve ) => setTimeout ( resolve , 100 ) ) ;
82+ }
83+ }
84+
85+ /**
86+ * Apply the actual upload system patch
87+ */
88+ applyUploadPatch ( ) {
89+ // Store the original uploadPresigned function
90+ this . originalUploadPresigned = UploaderModel . UploadItem . prototype . uploadPresigned ;
91+
92+ // Override the uploadPresigned method
93+ UploaderModel . UploadItem . prototype . uploadPresigned = this . createPatchedUploadMethod ( ) ;
94+
95+ this . isPatched = true ;
96+ console . log ( 'UploadInterceptor: Successfully patched upload system' ) ;
97+ }
98+
99+ /**
100+ * Create the patched upload method
101+ * @returns {Function } Patched upload method
102+ */
103+ createPatchedUploadMethod ( ) {
104+ const self = this ;
105+
106+ return function ( ) {
107+ // 'this' refers to the UploaderModel.UploadItem instance
108+ const uploadItem = this ;
109+
110+ // Execute the original upload logic first
111+ const originalPromise = self . originalUploadPresigned . apply ( uploadItem , arguments ) ;
112+
113+ // Call beforeUpload hooks
114+ self . callPluginHooks ( 'beforeUpload' , uploadItem ) ;
115+
116+ // Create observer to watch for upload status changes
117+ const observer = ( status ) => {
118+ // Call onStatusChange hooks
119+ self . callPluginHooks ( 'onStatusChange' , uploadItem , status ) ;
120+
121+ // Handle upload completion
122+ if ( status === "loaded" ) {
123+ // Remove observer to prevent multiple calls
124+ self . removeObserver ( uploadItem , observer ) ;
125+
126+ // Call onUploadComplete hooks
127+ self . callPluginHooks ( 'onUploadComplete' , uploadItem ) ;
128+ } else if ( status === "error" ) {
129+ // Remove observer
130+ self . removeObserver ( uploadItem , observer ) ;
131+
132+ // Call onUploadError hooks
133+ self . callPluginHooks ( 'onUploadError' , uploadItem ) ;
134+ }
135+ } ;
136+
137+ // Add observer to the upload item
138+ self . addObserver ( uploadItem , observer ) ;
139+
140+ return originalPromise ;
141+ } ;
142+ }
143+
144+ /**
145+ * Call a specific hook on all registered plugins
146+ * @param {string } hookName - Name of hook to call
147+ * @param {...any } args - Arguments to pass to hook
148+ */
149+ callPluginHooks ( hookName , ...args ) {
150+ for ( const [ pluginName , plugin ] of this . plugins ) {
151+ try {
152+ if ( plugin . hooks && typeof plugin . hooks [ hookName ] === 'function' ) {
153+ plugin . hooks [ hookName ] ( ...args ) ;
154+ }
155+ } catch ( error ) {
156+ console . error ( `UploadInterceptor: Error in plugin '${ pluginName } ' hook '${ hookName } ':` , error ) ;
157+ }
158+ }
159+ }
160+
161+ /**
162+ * Add observer to upload item
163+ * @param {Object } uploadItem - Upload item instance
164+ * @param {Function } observer - Observer function
165+ */
166+ addObserver ( uploadItem , observer ) {
167+ if ( ! uploadItem . _observers ) uploadItem . _observers = { } ;
168+ if ( ! uploadItem . _observers . status ) uploadItem . _observers . status = [ ] ;
169+
170+ if ( ! uploadItem . _observers . status . includes ( observer ) ) {
171+ uploadItem . _observers . status . push ( observer ) ;
172+ }
173+ }
174+
175+ /**
176+ * Remove observer from upload item
177+ * @param {Object } uploadItem - Upload item instance
178+ * @param {Function } observer - Observer function to remove
179+ */
180+ removeObserver ( uploadItem , observer ) {
181+ if ( uploadItem . _observers && uploadItem . _observers . status ) {
182+ const index = uploadItem . _observers . status . indexOf ( observer ) ;
183+ if ( index > - 1 ) {
184+ uploadItem . _observers . status . splice ( index , 1 ) ;
185+ }
186+ }
187+ }
188+
189+ /**
190+ * Restore the original upload system (for testing/cleanup)
191+ */
192+ unpatch ( ) {
193+ if ( this . isPatched && this . originalUploadPresigned ) {
194+ UploaderModel . UploadItem . prototype . uploadPresigned = this . originalUploadPresigned ;
195+ this . isPatched = false ;
196+ console . log ( 'UploadInterceptor: Unpatched upload system' ) ;
197+ }
198+ }
199+ }
200+
201+ // Create singleton instance
202+ const uploadInterceptor = new UploadInterceptor ( ) ;
203+
204+ export default uploadInterceptor ;
0 commit comments