@@ -195,6 +195,63 @@ function registerSecureSessionHandlers() {
195195 ipcMain . handle ( 'secure-session:clear' , ( ) => clearSecureSession ( ) ) ;
196196}
197197
198+ function registerUploadHandlers ( ) {
199+ ipcMain . handle ( 'upload:nostr-build' , async ( _event , payload ) => {
200+ try {
201+ return await uploadToNostrBuild ( payload ) ;
202+ } catch ( error ) {
203+ return {
204+ ok : false ,
205+ status : 0 ,
206+ data : { message : error ?. message || 'Could not reach nostr.build for upload.' } ,
207+ error : error ?. message || 'Could not reach nostr.build for upload.'
208+ } ;
209+ }
210+ } ) ;
211+ }
212+
213+ async function uploadToNostrBuild ( payload ) {
214+ const authorization = typeof payload ?. authorization === 'string' ? payload . authorization : '' ;
215+ const mediaType = [ 'avatar' , 'banner' , 'media' ] . includes ( payload ?. mediaType ) ? payload . mediaType : 'media' ;
216+ const name = safeUploadName ( typeof payload ?. name === 'string' && payload . name ? payload . name : 'upload' ) ;
217+ const type = typeof payload ?. type === 'string' && payload . type ? payload . type : 'application/octet-stream' ;
218+ const size = Number . isFinite ( payload ?. size ) ? String ( payload . size ) : '' ;
219+ const bytes = Buffer . isBuffer ( payload ?. bytes ) ? payload . bytes : Buffer . from ( payload ?. bytes ?? [ ] ) ;
220+ if ( ! authorization . startsWith ( 'Nostr ' ) || ! bytes . length ) return { ok : false , status : 400 , data : { message : 'Missing upload data.' } } ;
221+
222+ const form = new FormData ( ) ;
223+ form . set ( 'file' , new Blob ( [ bytes ] , { type } ) , name ) ;
224+ form . set ( 'media_type' , mediaType ) ;
225+ form . set ( 'content_type' , type ) ;
226+ form . set ( 'size' , size || String ( bytes . length ) ) ;
227+
228+ const response = await fetch ( 'https://nostr.build/api/v2/upload/files' , {
229+ method : 'POST' ,
230+ headers : { Authorization : authorization } ,
231+ body : form
232+ } ) ;
233+ const text = await response . text ( ) . catch ( ( ) => '' ) ;
234+ return {
235+ ok : response . ok ,
236+ status : response . status ,
237+ location : response . headers . get ( 'location' ) || '' ,
238+ data : parseJsonOrText ( text )
239+ } ;
240+ }
241+
242+ function safeUploadName ( name ) {
243+ return name . replace ( / [ \\ / : * ? " < > | \u0000 - \u001f ] / g, '_' ) . slice ( 0 , 180 ) || 'upload' ;
244+ }
245+
246+ function parseJsonOrText ( text ) {
247+ if ( ! text ) return '' ;
248+ try {
249+ return JSON . parse ( text ) ;
250+ } catch {
251+ return text ;
252+ }
253+ }
254+
198255registerNostrProtocol ( ) ;
199256openNostrUrl ( findNostrUrl ( ) ) ;
200257
@@ -213,6 +270,7 @@ if (!gotSingleInstanceLock) {
213270
214271 app . whenReady ( ) . then ( ( ) => {
215272 registerSecureSessionHandlers ( ) ;
273+ registerUploadHandlers ( ) ;
216274 return createWindow ( ) ;
217275 } ) ;
218276}
0 commit comments