diff --git a/go_backend/exports.go b/go_backend/exports.go index 70fa89f0..4ea78fdf 100644 --- a/go_backend/exports.go +++ b/go_backend/exports.go @@ -12,12 +12,7 @@ func CheckAvailability(spotifyID, isrc string) (string, error) { return "", err } - jsonBytes, err := json.Marshal(availability) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(availability) } // SetSongLinkNetworkOptions is kept for backward compatibility. @@ -44,12 +39,7 @@ func CheckDuplicate(outputDir, isrc string) (string, error) { "filepath": existingFile, } - jsonBytes, err := json.Marshal(result) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(result) } func CheckDuplicatesBatch(outputDir, tracksJSON string) (string, error) { diff --git a/go_backend/exports_deezer.go b/go_backend/exports_deezer.go index c7d8763d..fbbc60b1 100644 --- a/go_backend/exports_deezer.go +++ b/go_backend/exports_deezer.go @@ -39,8 +39,8 @@ func PreWarmTrackCacheJSON(tracksJSON string) (string, error) { "message": fmt.Sprintf("Pre-warming cache for %d tracks in background", len(tracks)), } - jsonBytes, _ := json.Marshal(resp) - return string(jsonBytes), nil + s, _ := marshalJSONString(resp) + return s, nil } func GetTrackCacheSize() int { @@ -64,11 +64,7 @@ func GetDeezerRelatedArtists(artistID string, limit int) (string, error) { resp := map[string]any{ "artists": artists, } - jsonBytes, err := json.Marshal(resp) - if err != nil { - return "", err - } - return string(jsonBytes), nil + return marshalJSONString(resp) } func GetDeezerMetadata(resourceType, resourceID string) (string, error) { @@ -96,12 +92,7 @@ func GetDeezerMetadata(resourceType, resourceID string) (string, error) { return "", err } - jsonBytes, err := json.Marshal(data) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(data) } func GetDeezerExtendedMetadata(trackID string) (string, error) { @@ -121,12 +112,7 @@ func GetDeezerExtendedMetadata(trackID string) (string, error) { result := buildDeezerExtendedMetadataResult(metadata) - jsonBytes, err := json.Marshal(result) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(result) } func SearchDeezerByISRC(isrc string) (string, error) { @@ -159,12 +145,7 @@ func SearchDeezerByISRCForItemID(isrc string, itemID string) (string, error) { } result := buildDeezerISRCSearchResult(track) - jsonBytes, err := json.Marshal(result) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(result) } func buildDeezerExtendedMetadataResult(metadata *AlbumExtendedMetadata) map[string]string { @@ -236,12 +217,7 @@ func ConvertSpotifyToDeezer(resourceType, spotifyID string) (string, error) { return "", fmt.Errorf("failed to fetch Deezer metadata: %w", err) } - jsonBytes, err := json.Marshal(trackResp) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(trackResp) } if resourceType == "album" { @@ -255,12 +231,7 @@ func ConvertSpotifyToDeezer(resourceType, spotifyID string) (string, error) { return "", fmt.Errorf("failed to fetch Deezer album metadata: %w", err) } - jsonBytes, err := json.Marshal(albumResp) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(albumResp) } return "", fmt.Errorf("spotify to Deezer conversion only supported for tracks and albums: please search by name for %s", resourceType) @@ -273,12 +244,7 @@ func CheckAvailabilityFromDeezerID(deezerTrackID string) (string, error) { return "", err } - jsonBytes, err := json.Marshal(availability) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(availability) } func CheckAvailabilityByPlatformID(platform, entityType, entityID string) (string, error) { @@ -288,12 +254,7 @@ func CheckAvailabilityByPlatformID(platform, entityType, entityID string) (strin return "", err } - jsonBytes, err := json.Marshal(availability) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(availability) } func GetSpotifyIDFromDeezerTrack(deezerTrackID string) (string, error) { diff --git a/go_backend/exports_download.go b/go_backend/exports_download.go index c9c29176..5b990bc8 100644 --- a/go_backend/exports_download.go +++ b/go_backend/exports_download.go @@ -438,8 +438,8 @@ func errorResponse(msg string) (string, error) { Error: msg, ErrorType: errorType, } - jsonBytes, _ := json.Marshal(resp) - return string(jsonBytes), nil + s, _ := marshalJSONString(resp) + return s, nil } func classifyDownloadErrorType(msg string) string { diff --git a/go_backend/exports_extensions.go b/go_backend/exports_extensions.go index 9a96ee6b..420e7855 100644 --- a/go_backend/exports_extensions.go +++ b/go_backend/exports_extensions.go @@ -248,11 +248,7 @@ func GetProviderMetadataJSON(providerID, resourceType, resourceID string) (strin if err != nil { return "", err } - jsonBytes, err := json.Marshal(response) - if err != nil { - return "", err - } - return string(jsonBytes), nil + return marshalJSONString(response) } return GetDeezerMetadata(resourceType, resourceID) default: @@ -261,11 +257,7 @@ func GetProviderMetadataJSON(providerID, resourceType, resourceID string) (strin return "", err } - jsonBytes, err := json.Marshal(response) - if err != nil { - return "", err - } - return string(jsonBytes), nil + return marshalJSONString(response) } } @@ -309,12 +301,7 @@ func LoadExtensionsFromDir(dirPath string) (string, error) { result["errors"].([]string)[i] = err.Error() } - jsonBytes, err := json.Marshal(result) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(result) } func LoadExtensionFromPath(filePath string) (string, error) { @@ -332,12 +319,7 @@ func LoadExtensionFromPath(filePath string) (string, error) { "enabled": ext.Enabled, } - jsonBytes, err := json.Marshal(result) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(result) } func UnloadExtensionByID(extensionID string) error { @@ -364,12 +346,7 @@ func UpgradeExtensionFromPath(filePath string) (string, error) { "enabled": ext.Enabled, } - jsonBytes, err := json.Marshal(result) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(result) } func CheckExtensionUpgradeFromPath(filePath string) (string, error) { @@ -399,11 +376,7 @@ func SetProviderPriorityJSON(priorityJSON string) error { func GetProviderPriorityJSON() (string, error) { priority := GetProviderPriority() - jsonBytes, err := json.Marshal(priority) - if err != nil { - return "", err - } - return string(jsonBytes), nil + return marshalJSONString(priority) } func SetExtensionFallbackProviderIDsJSON(providerIDsJSON string) error { @@ -423,11 +396,7 @@ func SetExtensionFallbackProviderIDsJSON(providerIDsJSON string) error { func GetExtensionFallbackProviderIDsJSON() (string, error) { providerIDs := GetExtensionFallbackProviderIDs() - jsonBytes, err := json.Marshal(providerIDs) - if err != nil { - return "", err - } - return string(jsonBytes), nil + return marshalJSONString(providerIDs) } func SetMetadataProviderPriorityJSON(priorityJSON string) error { @@ -442,23 +411,14 @@ func SetMetadataProviderPriorityJSON(priorityJSON string) error { func GetMetadataProviderPriorityJSON() (string, error) { priority := GetMetadataProviderPriority() - jsonBytes, err := json.Marshal(priority) - if err != nil { - return "", err - } - return string(jsonBytes), nil + return marshalJSONString(priority) } func GetExtensionSettingsJSON(extensionID string) (string, error) { store := GetExtensionSettingsStore() settings := store.GetAll(extensionID) - jsonBytes, err := json.Marshal(settings) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(settings) } func SetExtensionSettingsJSON(extensionID, settingsJSON string) error { @@ -483,12 +443,7 @@ func SearchTracksWithExtensionsJSON(query string, limit int) (string, error) { return "", err } - jsonBytes, err := json.Marshal(tracks) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(tracks) } func SearchTracksWithMetadataProvidersJSON(query string, limit int, includeExtensions bool) (string, error) { @@ -498,12 +453,7 @@ func SearchTracksWithMetadataProvidersJSON(query string, limit int, includeExten return "", err } - jsonBytes, err := json.Marshal(tracks) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(tracks) } func DownloadWithExtensionsJSON(requestJSON string) (string, error) { @@ -542,12 +492,7 @@ func DownloadWithExtensionsJSON(requestJSON string) (string, error) { return "", err } - jsonBytes, err := json.Marshal(result) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(result) } func CleanupExtensions() { @@ -562,12 +507,7 @@ func InvokeExtensionActionJSON(extensionID, actionName string) (string, error) { return "", err } - jsonBytes, err := json.Marshal(result) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(result) } func GetExtensionPendingAuthJSON(extensionID string) (string, error) { @@ -582,12 +522,7 @@ func GetExtensionPendingAuthJSON(extensionID string) (string, error) { "callback_url": req.CallbackURL, } - jsonBytes, err := json.Marshal(result) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(result) } func ensureExtensionPendingAuthRequest(extensionID string) *PendingAuthRequest { @@ -679,12 +614,7 @@ func GetAllPendingAuthRequestsJSON() (string, error) { }) } - jsonBytes, err := json.Marshal(requests) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(requests) } func GetPendingFFmpegCommandJSON(commandID string) (string, error) { @@ -701,12 +631,7 @@ func GetPendingFFmpegCommandJSON(commandID string) (string, error) { "output_path": cmd.OutputPath, } - jsonBytes, err := json.Marshal(result) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(result) } func SetFFmpegCommandResultByID(commandID string, success bool, output, errorMsg string) { @@ -728,12 +653,7 @@ func GetAllPendingFFmpegCommandsJSON() (string, error) { } } - jsonBytes, err := json.Marshal(commands) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(commands) } func EnrichTrackWithExtensionJSON(extensionID, trackJSON string) (string, error) { @@ -820,12 +740,7 @@ func CustomSearchWithExtensionJSONWithRequestID(extensionID, query string, optio } } - jsonBytes, err := json.Marshal(result) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(result) } func GetSearchProvidersJSON() (string, error) { @@ -843,12 +758,7 @@ func GetSearchProvidersJSON() (string, error) { }) } - jsonBytes, err := json.Marshal(result) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(result) } func HandleURLWithExtensionJSON(url string) (string, error) { @@ -1022,12 +932,7 @@ func HandleURLWithExtensionJSON(url string) (string, error) { response["artist"] = artistResponse } - jsonBytes, err := json.Marshal(response) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(response) } func FindURLHandlerJSON(url string) string { @@ -1052,12 +957,7 @@ func GetURLHandlersJSON() (string, error) { }) } - jsonBytes, err := json.Marshal(result) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(result) } func RunPostProcessingJSON(filePath, metadataJSON string) (string, error) { @@ -1074,12 +974,7 @@ func RunPostProcessingJSON(filePath, metadataJSON string) (string, error) { return "", err } - jsonBytes, err := json.Marshal(result) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(result) } func RunPostProcessingV2JSON(inputJSON, metadataJSON string) (string, error) { @@ -1103,12 +998,7 @@ func RunPostProcessingV2JSON(inputJSON, metadataJSON string) (string, error) { return "", err } - jsonBytes, err := json.Marshal(result) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(result) } func GetPostProcessingProvidersJSON() (string, error) { @@ -1135,12 +1025,7 @@ func GetPostProcessingProvidersJSON() (string, error) { }) } - jsonBytes, err := json.Marshal(result) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(result) } func callExtensionFunctionJSON(extensionID, functionName string, timeout time.Duration) (string, error) { diff --git a/go_backend/exports_lyrics.go b/go_backend/exports_lyrics.go index bdaf985e..b376cbcc 100644 --- a/go_backend/exports_lyrics.go +++ b/go_backend/exports_lyrics.go @@ -23,12 +23,7 @@ func FetchLyrics(spotifyID, trackName, artistName string, durationMs int64) (str "instrumental": lyrics.Instrumental, } - jsonBytes, err := json.Marshal(result) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(result) } func GetLyricsLRC(spotifyID, trackName, artistName string, filePath string, durationMs int64) (string, error) { @@ -69,11 +64,7 @@ func GetLyricsLRCWithSource(spotifyID, trackName, artistName string, filePath st "sync_type": "EMBEDDED", "instrumental": false, } - jsonBytes, err := json.Marshal(result) - if err != nil { - return "", err - } - return string(jsonBytes), nil + return marshalJSONString(result) } result := map[string]any{ @@ -82,11 +73,7 @@ func GetLyricsLRCWithSource(spotifyID, trackName, artistName string, filePath st "sync_type": "", "instrumental": false, } - jsonBytes, err := json.Marshal(result) - if err != nil { - return "", err - } - return string(jsonBytes), nil + return marshalJSONString(result) } client := NewLyricsClient() @@ -109,12 +96,7 @@ func GetLyricsLRCWithSource(spotifyID, trackName, artistName string, filePath st "sync_type": lyricsData.SyncType, "instrumental": lyricsData.Instrumental, } - jsonBytes, err := json.Marshal(result) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(result) } func EmbedLyricsToFile(filePath, lyrics string) (string, error) { @@ -128,8 +110,8 @@ func EmbedLyricsToFile(filePath, lyrics string) (string, error) { "message": "Lyrics embedded successfully", } - jsonBytes, _ := json.Marshal(resp) - return string(jsonBytes), nil + s, _ := marshalJSONString(resp) + return s, nil } func FetchAndSaveLyrics(trackName, artistName, spotifyID string, durationMs int64, outputPath string, audioFilePath string) error { @@ -183,20 +165,12 @@ func SetLyricsProvidersJSON(providersJSON string) error { func GetLyricsProvidersJSON() (string, error) { providers := GetLyricsProviderOrder() - jsonBytes, err := json.Marshal(providers) - if err != nil { - return "", err - } - return string(jsonBytes), nil + return marshalJSONString(providers) } func GetAvailableLyricsProvidersJSON() (string, error) { providers := GetAvailableLyricsProviders() - jsonBytes, err := json.Marshal(providers) - if err != nil { - return "", err - } - return string(jsonBytes), nil + return marshalJSONString(providers) } func SetLyricsFetchOptionsJSON(optionsJSON string) error { @@ -213,9 +187,5 @@ func SetLyricsFetchOptionsJSON(optionsJSON string) error { func GetLyricsFetchOptionsJSON() (string, error) { opts := GetLyricsFetchOptions() - jsonBytes, err := json.Marshal(opts) - if err != nil { - return "", err - } - return string(jsonBytes), nil + return marshalJSONString(opts) } diff --git a/go_backend/exports_metadata.go b/go_backend/exports_metadata.go index fd894842..64d0cd14 100644 --- a/go_backend/exports_metadata.go +++ b/go_backend/exports_metadata.go @@ -314,12 +314,7 @@ func ReadFileMetadata(filePath string) (string, error) { return "", fmt.Errorf("unsupported file format: %s", filePath) } - jsonBytes, err := json.Marshal(result) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(result) } // ParseCueSheet is called from Dart to get track listing and timing data for CUE splitting. @@ -382,8 +377,8 @@ func WriteM4AFreeformTags(filePath, metadataJSON string) (string, error) { } resp := map[string]any{"success": true, "method": "native_m4a_freeform"} - jsonBytes, _ := json.Marshal(resp) - return string(jsonBytes), nil + s, _ := marshalJSONString(resp) + return s, nil } // EnsureAC4Config normalizes a decrypted AC-4 file to a standards-compliant ISO @@ -405,8 +400,8 @@ func WriteAC4Metadata(filePath, metadataJSON, coverPath string) (string, error) return "", fmt.Errorf("failed to write AC-4 metadata: %w", err) } resp := map[string]any{"success": true, "handled": handled} - jsonBytes, _ := json.Marshal(resp) - return string(jsonBytes), nil + s, _ := marshalJSONString(resp) + return s, nil } // EditFileMetadata writes audio file tags: FLAC via native Go library, MP3/Opus returns map for Dart/FFmpeg. @@ -433,8 +428,8 @@ func EditFileMetadata(filePath, metadataJSON string) (string, error) { "success": true, "method": "native_m4a_replaygain", } - jsonBytes, _ := json.Marshal(resp) - return string(jsonBytes), nil + s, _ := marshalJSONString(resp) + return s, nil } if isFlac { @@ -455,8 +450,8 @@ func EditFileMetadata(filePath, metadataJSON string) (string, error) { "success": true, "method": "native", } - jsonBytes, _ := json.Marshal(resp) - return string(jsonBytes), nil + s, _ := marshalJSONString(resp) + return s, nil } // WAV / AIFF: write tags into an embedded ID3v2.4 chunk natively. @@ -465,16 +460,16 @@ func EditFileMetadata(filePath, metadataJSON string) (string, error) { return "", fmt.Errorf("failed to write WAV metadata: %w", err) } resp := map[string]any{"success": true, "method": "native_wav"} - jsonBytes, _ := json.Marshal(resp) - return string(jsonBytes), nil + s, _ := marshalJSONString(resp) + return s, nil } if isAiffFile { if err := WriteAIFFTags(filePath, fields); err != nil { return "", fmt.Errorf("failed to write AIFF metadata: %w", err) } resp := map[string]any{"success": true, "method": "native_aiff"} - jsonBytes, _ := json.Marshal(resp) - return string(jsonBytes), nil + s, _ := marshalJSONString(resp) + return s, nil } if isApeFile { @@ -569,8 +564,8 @@ func EditFileMetadata(filePath, metadataJSON string) (string, error) { "success": true, "method": "native_ape", } - jsonBytes, _ := json.Marshal(resp) - return string(jsonBytes), nil + s, _ := marshalJSONString(resp) + return s, nil } // MP3, Ogg/Opus, and M4A have native editors that preserve foreign @@ -584,8 +579,8 @@ func EditFileMetadata(filePath, metadataJSON string) (string, error) { GoLog("[Metadata] Native MP3 edit failed, falling back to ffmpeg: %v\n", err) } else { resp := map[string]any{"success": true, "method": "native_mp3"} - jsonBytes, _ := json.Marshal(resp) - return string(jsonBytes), nil + s, _ := marshalJSONString(resp) + return s, nil } } if isOggFile { @@ -593,8 +588,8 @@ func EditFileMetadata(filePath, metadataJSON string) (string, error) { GoLog("[Metadata] Native Ogg edit failed, falling back to ffmpeg: %v\n", err) } else { resp := map[string]any{"success": true, "method": "native_ogg"} - jsonBytes, _ := json.Marshal(resp) - return string(jsonBytes), nil + s, _ := marshalJSONString(resp) + return s, nil } } if isM4AFile || isMP4ContainerFile(filePath) { @@ -602,8 +597,8 @@ func EditFileMetadata(filePath, metadataJSON string) (string, error) { GoLog("[Metadata] Native M4A edit failed, falling back to ffmpeg: %v\n", err) } else { resp := map[string]any{"success": true, "method": "native_m4a"} - jsonBytes, _ := json.Marshal(resp) - return string(jsonBytes), nil + s, _ := marshalJSONString(resp) + return s, nil } } @@ -612,8 +607,8 @@ func EditFileMetadata(filePath, metadataJSON string) (string, error) { "method": "ffmpeg", "fields": fields, } - jsonBytes, _ := json.Marshal(resp) - return string(jsonBytes), nil + s, _ := marshalJSONString(resp) + return s, nil } func isMP4ContainerFile(filePath string) bool { @@ -669,8 +664,8 @@ func RewriteSplitArtistTagsExport(filePath, artist, albumArtist string) (string, "message": "Split artist tags written successfully", } - jsonBytes, _ := json.Marshal(resp) - return string(jsonBytes), nil + s, _ := marshalJSONString(resp) + return s, nil } func DownloadCoverToFile(coverURL string, outputPath string, maxQuality bool) error { diff --git a/go_backend/exports_musicbrainz.go b/go_backend/exports_musicbrainz.go index 331463b2..c7b004e5 100644 --- a/go_backend/exports_musicbrainz.go +++ b/go_backend/exports_musicbrainz.go @@ -112,7 +112,10 @@ func selectMusicBrainzAlbumArtist(releases []musicBrainzRelease, albumName strin return "" } -func FetchMusicBrainzAlbumArtistByISRC(isrc string, albumName string) (string, error) { +// fetchMusicBrainzRecordingByISRC queries the MusicBrainz recording endpoint +// for the given ISRC with the supplied inc= parameter, retrying up to 3 times, +// and decodes the JSON response into payload. It returns the normalized ISRC. +func fetchMusicBrainzRecordingByISRC(isrc string, inc string, payload any) (string, error) { normalizedISRC := strings.ToUpper(strings.TrimSpace(isrc)) if normalizedISRC == "" { return "", fmt.Errorf("no ISRC provided") @@ -121,9 +124,10 @@ func FetchMusicBrainzAlbumArtistByISRC(isrc string, albumName string) (string, e client := NewMetadataHTTPClient(10 * time.Second) query := fmt.Sprintf("isrc:%s", normalizedISRC) reqURL := fmt.Sprintf( - "%s/recording?query=%s&fmt=json&inc=releases+artist-credits", + "%s/recording?query=%s&fmt=json&inc=%s", musicBrainzAPIBase, url.QueryEscape(query), + inc, ) req, err := http.NewRequest(http.MethodGet, reqURL, nil) @@ -159,8 +163,16 @@ func FetchMusicBrainzAlbumArtistByISRC(isrc string, albumName string) (string, e } defer resp.Body.Close() + if err := json.NewDecoder(resp.Body).Decode(payload); err != nil { + return "", err + } + return normalizedISRC, nil +} + +func FetchMusicBrainzAlbumArtistByISRC(isrc string, albumName string) (string, error) { var payload musicBrainzAlbumArtistResponse - if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil { + normalizedISRC, err := fetchMusicBrainzRecordingByISRC(isrc, "releases+artist-credits", &payload) + if err != nil { return "", err } for _, recording := range payload.Recordings { @@ -173,54 +185,9 @@ func FetchMusicBrainzAlbumArtistByISRC(isrc string, albumName string) (string, e } func FetchMusicBrainzGenreByISRC(isrc string) (string, error) { - normalizedISRC := strings.ToUpper(strings.TrimSpace(isrc)) - if normalizedISRC == "" { - return "", fmt.Errorf("no ISRC provided") - } - - client := NewMetadataHTTPClient(10 * time.Second) - query := fmt.Sprintf("isrc:%s", normalizedISRC) - reqURL := fmt.Sprintf( - "%s/recording?query=%s&fmt=json&inc=tags", - musicBrainzAPIBase, - url.QueryEscape(query), - ) - - req, err := http.NewRequest(http.MethodGet, reqURL, nil) - if err != nil { - return "", err - } - req.Header.Set("User-Agent", getRandomUserAgent()) - - var resp *http.Response - var lastErr error - for attempt := 0; attempt < 3; attempt++ { - resp, lastErr = client.Do(req) - if lastErr == nil && resp.StatusCode == http.StatusOK { - break - } - if resp != nil { - resp.Body.Close() - } - if attempt < 2 { - time.Sleep(2 * time.Second) - } - } - - if lastErr != nil { - return "", lastErr - } - if resp == nil { - return "", fmt.Errorf("MusicBrainz request failed without response") - } - if resp.StatusCode != http.StatusOK { - resp.Body.Close() - return "", fmt.Errorf("MusicBrainz API returned status: %d", resp.StatusCode) - } - defer resp.Body.Close() - var payload musicBrainzRecordingResponse - if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil { + normalizedISRC, err := fetchMusicBrainzRecordingByISRC(isrc, "tags", &payload) + if err != nil { return "", err } if len(payload.Recordings) == 0 { diff --git a/go_backend/exports_reenrich.go b/go_backend/exports_reenrich.go index 9069329c..9b8eb7e2 100644 --- a/go_backend/exports_reenrich.go +++ b/go_backend/exports_reenrich.go @@ -739,8 +739,8 @@ func ReEnrichFile(requestJSON string) (string, error) { req.lyricsSidecarEnabled() && strings.TrimSpace(lyricsLRC) != "", } - jsonBytes, _ := json.Marshal(result) - return string(jsonBytes), nil + s, _ := marshalJSONString(result) + return s, nil } // Don't cleanup cover temp — Dart needs it for FFmpeg embed @@ -759,6 +759,6 @@ func ReEnrichFile(requestJSON string) (string, error) { strings.TrimSpace(lyricsLRC) != "", } - jsonBytes, _ := json.Marshal(result) - return string(jsonBytes), nil + s, _ := marshalJSONString(result) + return s, nil } diff --git a/go_backend/exports_store.go b/go_backend/exports_store.go index 59f6c82b..e625383a 100644 --- a/go_backend/exports_store.go +++ b/go_backend/exports_store.go @@ -1,7 +1,6 @@ package gobackend import ( - "encoding/json" "fmt" "net/url" "path/filepath" @@ -63,12 +62,7 @@ func GetStoreExtensionsJSON(forceRefresh bool) (string, error) { return "", err } - jsonBytes, err := json.Marshal(extensions) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(extensions) } func SearchStoreExtensionsJSON(query, category string) (string, error) { @@ -82,12 +76,7 @@ func SearchStoreExtensionsJSON(query, category string) (string, error) { return "", err } - jsonBytes, err := json.Marshal(extensions) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(extensions) } func GetStoreCategoriesJSON() (string, error) { @@ -97,12 +86,7 @@ func GetStoreCategoriesJSON() (string, error) { } categories := store.getCategories() - jsonBytes, err := json.Marshal(categories) - if err != nil { - return "", err - } - - return string(jsonBytes), nil + return marshalJSONString(categories) } func storeExtensionPackageSuffix(downloadURL string) string { diff --git a/go_backend/extension_runtime_auth.go b/go_backend/extension_runtime_auth.go index 83846e02..a91f4efe 100644 --- a/go_backend/extension_runtime_auth.go +++ b/go_backend/extension_runtime_auth.go @@ -54,10 +54,7 @@ func summarizeURLForLog(urlStr string) string { func (r *extensionRuntime) authOpenUrl(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "auth URL is required", - }) + return r.jsError("auth URL is required") } authURL := call.Arguments[0].String() @@ -67,10 +64,7 @@ func (r *extensionRuntime) authOpenUrl(call goja.FunctionCall) goja.Value { } if err := validateExtensionAuthURL(authURL); err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } pendingAuthRequestsMu.Lock() @@ -94,8 +88,7 @@ func (r *extensionRuntime) authOpenUrl(call goja.FunctionCall) goja.Value { GoLog("[Extension:%s] Auth URL requested: %s\n", r.extensionID, summarizeURLForLog(authURL)) - return r.vm.ToValue(map[string]any{ - "success": true, + return r.jsSuccess(map[string]any{ "message": "Auth URL will be opened by the app", }) } @@ -284,19 +277,13 @@ func (r *extensionRuntime) authGetPKCE(call goja.FunctionCall) goja.Value { func (r *extensionRuntime) authStartOAuthWithPKCE(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "config object is required", - }) + return r.jsError("config object is required") } configObj := call.Arguments[0].Export() config, ok := configObj.(map[string]any) if !ok { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "config must be an object", - }) + return r.jsError("config must be an object") } authURL, _ := config["authUrl"].(string) @@ -304,16 +291,10 @@ func (r *extensionRuntime) authStartOAuthWithPKCE(call goja.FunctionCall) goja.V redirectURI, _ := config["redirectUri"].(string) if authURL == "" || clientID == "" || redirectURI == "" { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "authUrl, clientId, and redirectUri are required", - }) + return r.jsError("authUrl, clientId, and redirectUri are required") } if err := validateExtensionAuthURL(authURL); err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } scope, _ := config["scope"].(string) @@ -321,10 +302,7 @@ func (r *extensionRuntime) authStartOAuthWithPKCE(call goja.FunctionCall) goja.V verifier, err := generatePKCEVerifier(64) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("failed to generate PKCE: %v", err), - }) + return r.jsError("failed to generate PKCE: %v", err) } challenge := generatePKCEChallenge(verifier) @@ -341,10 +319,7 @@ func (r *extensionRuntime) authStartOAuthWithPKCE(call goja.FunctionCall) goja.V parsedURL, err := url.Parse(authURL) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("invalid authUrl: %v", err), - }) + return r.jsError("invalid authUrl: %v", err) } query := parsedURL.Query() @@ -376,8 +351,7 @@ func (r *extensionRuntime) authStartOAuthWithPKCE(call goja.FunctionCall) goja.V GoLog("[Extension:%s] PKCE OAuth started: %s\n", r.extensionID, summarizeURLForLog(fullAuthURL)) - return r.vm.ToValue(map[string]any{ - "success": true, + return r.jsSuccess(map[string]any{ "authUrl": fullAuthURL, "pkce": map[string]any{ "verifier": verifier, @@ -389,19 +363,13 @@ func (r *extensionRuntime) authStartOAuthWithPKCE(call goja.FunctionCall) goja.V func (r *extensionRuntime) authExchangeCodeWithPKCE(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "config object is required", - }) + return r.jsError("config object is required") } configObj := call.Arguments[0].Export() config, ok := configObj.(map[string]any) if !ok { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "config must be an object", - }) + return r.jsError("config must be an object") } tokenURL, _ := config["tokenUrl"].(string) @@ -410,10 +378,7 @@ func (r *extensionRuntime) authExchangeCodeWithPKCE(call goja.FunctionCall) goja code, _ := config["code"].(string) if tokenURL == "" || clientID == "" || code == "" { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "tokenUrl, clientId, and code are required", - }) + return r.jsError("tokenUrl, clientId, and code are required") } extensionAuthStateMu.RLock() @@ -425,17 +390,11 @@ func (r *extensionRuntime) authExchangeCodeWithPKCE(call goja.FunctionCall) goja extensionAuthStateMu.RUnlock() if verifier == "" { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "no PKCE verifier found - call generatePKCE or startOAuthWithPKCE first", - }) + return r.jsError("no PKCE verifier found - call generatePKCE or startOAuthWithPKCE first") } if err := r.validateDomain(tokenURL); err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } formData := url.Values{} @@ -455,10 +414,7 @@ func (r *extensionRuntime) authExchangeCodeWithPKCE(call goja.FunctionCall) goja req, err := http.NewRequest("POST", tokenURL, strings.NewReader(formData.Encode())) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } req = r.bindDownloadCancelContext(req) @@ -467,19 +423,13 @@ func (r *extensionRuntime) authExchangeCodeWithPKCE(call goja.FunctionCall) goja resp, err := r.httpClient.Do(req) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } bodyPreview := sanitizeSensitiveLogText(string(body)) if len(bodyPreview) > 1000 { diff --git a/go_backend/extension_runtime_binary.go b/go_backend/extension_runtime_binary.go index dc84538a..908be6dd 100644 --- a/go_backend/extension_runtime_binary.go +++ b/go_backend/extension_runtime_binary.go @@ -270,43 +270,28 @@ func removePKCS7Padding(data []byte, blockSize int) ([]byte, error) { func (r *extensionRuntime) transformBlockCipher(call goja.FunctionCall, decrypt bool) goja.Value { if len(call.Arguments) < 2 { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "data and options are required", - }) + return r.jsError("data and options are required") } options := parseRuntimeOptionsArgument(call, 1) parsedOptions, err := parseRuntimeBlockCipherOptions(options) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } switch parsedOptions.Mode { case "cbc", "ctr": default: - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("unsupported block cipher mode: %s", parsedOptions.Mode), - }) + return r.jsError("unsupported block cipher mode: %s", parsedOptions.Mode) } inputData, err := decodeRuntimeBytesValue(call.Arguments[0].Export(), parsedOptions.InputEncoding) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } block, err := newRuntimeBlockCipher(parsedOptions) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } if len(parsedOptions.IV) != block.BlockSize() { @@ -314,10 +299,7 @@ func (r *extensionRuntime) transformBlockCipher(call goja.FunctionCall, decrypt if parsedOptions.Mode == "ctr" { ivLabel = "iv (counter)" } - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("%s must be %d bytes for %s", ivLabel, block.BlockSize(), parsedOptions.Algorithm), - }) + return r.jsError("%s must be %d bytes for %s", ivLabel, block.BlockSize(), parsedOptions.Algorithm) } var output []byte @@ -332,10 +314,7 @@ func (r *extensionRuntime) transformBlockCipher(call goja.FunctionCall, decrypt data = applyPKCS7Padding(data, block.BlockSize()) } if len(data)%block.BlockSize() != 0 { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("input length must be a multiple of %d bytes", block.BlockSize()), - }) + return r.jsError("input length must be a multiple of %d bytes", block.BlockSize()) } output = make([]byte, len(data)) @@ -344,10 +323,7 @@ func (r *extensionRuntime) transformBlockCipher(call goja.FunctionCall, decrypt if parsedOptions.Padding == "pkcs7" { output, err = removePKCS7Padding(output, block.BlockSize()) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } } } else { @@ -357,14 +333,10 @@ func (r *extensionRuntime) transformBlockCipher(call goja.FunctionCall, decrypt encoded, err := encodeRuntimeBytes(output, parsedOptions.OutputEncoding) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } - return r.vm.ToValue(map[string]any{ - "success": true, + return r.jsSuccess(map[string]any{ "data": encoded, "block_size": block.BlockSize(), }) @@ -405,10 +377,7 @@ func (r *extensionRuntime) decryptBlockCipher(call goja.FunctionCall) goja.Value // Returns { success, data, segments_processed } or { success:false, error }. func (r *extensionRuntime) decryptCTRSegments(call goja.FunctionCall) goja.Value { fail := func(msg string) goja.Value { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": msg, - }) + return r.jsError("%s", msg) } if len(call.Arguments) < 2 { @@ -516,8 +485,7 @@ func (r *extensionRuntime) decryptCTRSegments(call goja.FunctionCall) goja.Value // Return raw bytes as an ArrayBuffer when requested (zero-copy-ish, no // base64). Otherwise fall back to an encoded string. if outputEncoding == "bytes" || outputEncoding == "raw" { - return r.vm.ToValue(map[string]any{ - "success": true, + return r.jsSuccess(map[string]any{ "data": r.vm.NewArrayBuffer(data), "segments_processed": processed, }) @@ -528,8 +496,7 @@ func (r *extensionRuntime) decryptCTRSegments(call goja.FunctionCall) goja.Value return fail(err.Error()) } - return r.vm.ToValue(map[string]any{ - "success": true, + return r.jsSuccess(map[string]any{ "data": encoded, "segments_processed": processed, }) diff --git a/go_backend/extension_runtime_ffmpeg.go b/go_backend/extension_runtime_ffmpeg.go index c9eb2124..b47cdffd 100644 --- a/go_backend/extension_runtime_ffmpeg.go +++ b/go_backend/extension_runtime_ffmpeg.go @@ -52,10 +52,7 @@ func ClearFFmpegCommand(commandID string) { func (r *extensionRuntime) ffmpegExecute(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "command is required", - }) + return r.jsError("command is required") } command := call.Arguments[0].String() @@ -97,10 +94,7 @@ func (r *extensionRuntime) ffmpegExecute(call goja.FunctionCall) goja.Value { if time.Since(start) > timeout { ClearFFmpegCommand(cmdID) - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "FFmpeg command timed out", - }) + return r.jsError("FFmpeg command timed out") } time.Sleep(100 * time.Millisecond) @@ -109,24 +103,17 @@ func (r *extensionRuntime) ffmpegExecute(call goja.FunctionCall) goja.Value { func (r *extensionRuntime) ffmpegGetInfo(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "file path is required", - }) + return r.jsError("file path is required") } filePath := call.Arguments[0].String() quality, err := GetAudioQuality(filePath) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } - return r.vm.ToValue(map[string]any{ - "success": true, + return r.jsSuccess(map[string]any{ "bit_depth": quality.BitDepth, "sample_rate": quality.SampleRate, "total_samples": quality.TotalSamples, @@ -137,10 +124,7 @@ func (r *extensionRuntime) ffmpegGetInfo(call goja.FunctionCall) goja.Value { func (r *extensionRuntime) ffmpegConvert(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 2 { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "input and output paths are required", - }) + return r.jsError("input and output paths are required") } inputPath := call.Arguments[0].String() diff --git a/go_backend/extension_runtime_file.go b/go_backend/extension_runtime_file.go index 99d17362..97b284cb 100644 --- a/go_backend/extension_runtime_file.go +++ b/go_backend/extension_runtime_file.go @@ -109,28 +109,19 @@ func (r *extensionRuntime) validatePath(path string) (string, error) { func (r *extensionRuntime) fileDownload(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 2 { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "URL and output path are required", - }) + return r.jsError("URL and output path are required") } urlStr := call.Arguments[0].String() outputPath := call.Arguments[1].String() if err := r.validateDomain(urlStr); err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } fullPath, err := r.validatePath(outputPath) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } var onProgress goja.Callable @@ -187,10 +178,7 @@ func (r *extensionRuntime) fileDownload(call goja.FunctionCall) goja.Value { dir := filepath.Dir(fullPath) if err := os.MkdirAll(dir, 0755); err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("failed to create directory: %v", err), - }) + return r.jsError("failed to create directory: %v", err) } client := r.downloadClient @@ -212,10 +200,7 @@ func (r *extensionRuntime) fileDownload(call goja.FunctionCall) goja.Value { req, err := http.NewRequest("GET", urlStr, nil) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } req = r.bindDownloadCancelContext(req) @@ -228,18 +213,12 @@ func (r *extensionRuntime) fileDownload(call goja.FunctionCall) goja.Value { resp, err := client.Do(req) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("HTTP error: %d", resp.StatusCode), - }) + return r.jsError("HTTP error: %d", resp.StatusCode) } // Stream into a staged sibling and promote via rename on success so a @@ -249,10 +228,7 @@ func (r *extensionRuntime) fileDownload(call goja.FunctionCall) goja.Value { os.Remove(stagedPath) out, err := os.Create(stagedPath) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("failed to create file: %v", err), - }) + return r.jsError("failed to create file: %v", err) } promoted := false defer func() { @@ -293,21 +269,12 @@ func (r *extensionRuntime) fileDownload(call goja.FunctionCall) goja.Value { written += int64(nw) if ew != nil { if ew == ErrDownloadCancelled { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "download cancelled", - }) + return r.jsError("download cancelled") } - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("failed to write file: %v", ew), - }) + return r.jsError("failed to write file: %v", ew) } if nr != nw { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "short write", - }) + return r.jsError("short write") } if onProgress != nil && contentLength > 0 { @@ -316,10 +283,7 @@ func (r *extensionRuntime) fileDownload(call goja.FunctionCall) goja.Value { } if er != nil { if er != io.EOF { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("failed to read response: %v", er), - }) + return r.jsError("failed to read response: %v", er) } break } @@ -336,32 +300,22 @@ func (r *extensionRuntime) fileDownload(call goja.FunctionCall) goja.Value { // Sync before the promote rename so a power loss right after the rename // cannot leave a truncated file under the final name. if err := out.Sync(); err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("failed to sync file: %v", err), - }) + return r.jsError("failed to sync file: %v", err) } if err := out.Close(); err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("failed to finalize file: %v", err), - }) + return r.jsError("failed to finalize file: %v", err) } if err := os.Rename(stagedPath, fullPath); err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("failed to publish file: %v", err), - }) + return r.jsError("failed to publish file: %v", err) } promoted = true syncDir(filepath.Dir(fullPath)) GoLog("[Extension:%s] Downloaded %d bytes to %s\n", r.extensionID, written, fullPath) - return r.vm.ToValue(map[string]any{ - "success": true, - "path": fullPath, - "size": written, + return r.jsSuccess(map[string]any{ + "path": fullPath, + "size": written, }) } @@ -375,10 +329,7 @@ func (r *extensionRuntime) fileDownloadChunked(client *http.Client, urlStr, full // First, get the total content length with a small probe request probeReq, err := http.NewRequest("GET", urlStr, nil) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("chunked: probe request error: %v", err), - }) + return r.jsError("chunked: probe request error: %v", err) } probeReq = r.bindDownloadCancelContext(probeReq) probeReq.Header.Set("User-Agent", ua) @@ -391,19 +342,13 @@ func (r *extensionRuntime) fileDownloadChunked(client *http.Client, urlStr, full probeResp, err := client.Do(probeReq) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("chunked: probe error: %v", err), - }) + return r.jsError("chunked: probe error: %v", err) } io.Copy(io.Discard, probeResp.Body) probeResp.Body.Close() if probeResp.StatusCode != 206 && probeResp.StatusCode != 200 { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("chunked: probe HTTP %d", probeResp.StatusCode), - }) + return r.jsError("chunked: probe HTTP %d", probeResp.StatusCode) } // Parse Content-Range to get total size: "bytes 0-1/TOTAL" @@ -432,10 +377,7 @@ func (r *extensionRuntime) fileDownloadChunked(client *http.Client, urlStr, full os.Remove(stagedPath) out, err := os.Create(stagedPath) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("failed to create file: %v", err), - }) + return r.jsError("failed to create file: %v", err) } promoted := false defer func() { @@ -476,10 +418,7 @@ func (r *extensionRuntime) fileDownloadChunked(client *http.Client, urlStr, full for retry := 0; retry < maxRetries; retry++ { chunkReq, err := http.NewRequest("GET", urlStr, nil) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("chunked: request error at offset %d: %v", offset, err), - }) + return r.jsError("chunked: request error at offset %d: %v", offset, err) } chunkReq = r.bindDownloadCancelContext(chunkReq) chunkReq.Header.Set("User-Agent", ua) @@ -496,10 +435,7 @@ func (r *extensionRuntime) fileDownloadChunked(client *http.Client, urlStr, full time.Sleep(time.Duration(retry+1) * time.Second) continue } - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("chunked: error at offset %d after %d retries: %v", offset, maxRetries, chunkErr), - }) + return r.jsError("chunked: error at offset %d after %d retries: %v", offset, maxRetries, chunkErr) } if chunkResp.StatusCode == 206 || chunkResp.StatusCode == 200 { @@ -516,10 +452,7 @@ func (r *extensionRuntime) fileDownloadChunked(client *http.Client, urlStr, full } } - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("chunked: HTTP %d at offset %d", chunkResp.StatusCode, offset), - }) + return r.jsError("chunked: HTTP %d at offset %d", chunkResp.StatusCode, offset) } chunkWritten := int64(0) @@ -538,22 +471,13 @@ func (r *extensionRuntime) fileDownloadChunked(client *http.Client, urlStr, full if ew != nil { chunkResp.Body.Close() if ew == ErrDownloadCancelled { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "download cancelled", - }) + return r.jsError("download cancelled") } - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("failed to write file: %v", ew), - }) + return r.jsError("failed to write file: %v", ew) } if nr != nw { chunkResp.Body.Close() - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "short write", - }) + return r.jsError("short write") } if onProgress != nil && totalSize > 0 { @@ -563,10 +487,7 @@ func (r *extensionRuntime) fileDownloadChunked(client *http.Client, urlStr, full if er != nil { if er != io.EOF { chunkResp.Body.Close() - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("failed to read chunk at offset %d: %v", offset, er), - }) + return r.jsError("failed to read chunk at offset %d: %v", offset, er) } break } @@ -602,32 +523,22 @@ func (r *extensionRuntime) fileDownloadChunked(client *http.Client, urlStr, full // Sync before the promote rename so a power loss right after the rename // cannot leave a truncated file under the final name. if err := out.Sync(); err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("failed to sync file: %v", err), - }) + return r.jsError("failed to sync file: %v", err) } if err := out.Close(); err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("failed to finalize file: %v", err), - }) + return r.jsError("failed to finalize file: %v", err) } if err := os.Rename(stagedPath, fullPath); err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("failed to publish file: %v", err), - }) + return r.jsError("failed to publish file: %v", err) } promoted = true syncDir(filepath.Dir(fullPath)) GoLog("[Extension:%s] Chunked download complete: %d bytes to %s\n", r.extensionID, totalWritten, fullPath) - return r.vm.ToValue(map[string]any{ - "success": true, - "path": fullPath, - "size": totalWritten, + return r.jsSuccess(map[string]any{ + "path": fullPath, + "size": totalWritten, }) } @@ -648,79 +559,52 @@ func (r *extensionRuntime) fileExists(call goja.FunctionCall) goja.Value { func (r *extensionRuntime) fileDelete(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "path is required", - }) + return r.jsError("path is required") } path := call.Arguments[0].String() fullPath, err := r.validatePath(path) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } if err := os.Remove(fullPath); err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } - return r.vm.ToValue(map[string]any{ - "success": true, - }) + return r.jsSuccess(nil) } func (r *extensionRuntime) fileRead(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "path is required", - }) + return r.jsError("path is required") } path := call.Arguments[0].String() fullPath, err := r.validatePath(path) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } data, err := os.ReadFile(fullPath) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } - return r.vm.ToValue(map[string]any{ - "success": true, - "data": string(data), + return r.jsSuccess(map[string]any{ + "data": string(data), }) } func (r *extensionRuntime) fileReadBytes(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "path is required", - }) + return r.jsError("path is required") } path := call.Arguments[0].String() fullPath, err := r.validatePath(path) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } options := parseRuntimeOptionsArgument(call, 1) @@ -728,26 +612,17 @@ func (r *extensionRuntime) fileReadBytes(call goja.FunctionCall) goja.Value { length := runtimeOptionInt64(options, "length", -1) encoding := runtimeOptionString(options, "encoding", "base64") if offset < 0 { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "offset must be >= 0", - }) + return r.jsError("offset must be >= 0") } file, err := os.Open(fullPath) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } defer file.Close() info, err := file.Stat() if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } size := info.Size() @@ -755,10 +630,7 @@ func (r *extensionRuntime) fileReadBytes(call goja.FunctionCall) goja.Value { offset = size } if _, err := file.Seek(offset, io.SeekStart); err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("failed to seek file: %v", err), - }) + return r.jsError("failed to seek file: %v", err) } var data []byte @@ -769,19 +641,13 @@ func (r *extensionRuntime) fileReadBytes(call goja.FunctionCall) goja.Value { buf := make([]byte, int(length)) n, readErr := file.Read(buf) if readErr != nil && readErr != io.EOF { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("failed to read file: %v", readErr), - }) + return r.jsError("failed to read file: %v", readErr) } data = buf[:n] default: data, err = io.ReadAll(file) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("failed to read file: %v", err), - }) + return r.jsError("failed to read file: %v", err) } } @@ -789,8 +655,7 @@ func (r *extensionRuntime) fileReadBytes(call goja.FunctionCall) goja.Value { strings.EqualFold(strings.TrimSpace(encoding), "raw") { // Return raw bytes as an ArrayBuffer to avoid base64 encode/decode of // large payloads under the goja interpreter. - return r.vm.ToValue(map[string]any{ - "success": true, + return r.jsSuccess(map[string]any{ "data": r.vm.NewArrayBuffer(data), "bytes_read": len(data), "offset": offset, @@ -801,14 +666,10 @@ func (r *extensionRuntime) fileReadBytes(call goja.FunctionCall) goja.Value { encoded, err := encodeRuntimeBytes(data, encoding) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } - return r.vm.ToValue(map[string]any{ - "success": true, + return r.jsSuccess(map[string]any{ "data": encoded, "bytes_read": len(data), "offset": offset, @@ -818,10 +679,7 @@ func (r *extensionRuntime) fileReadBytes(call goja.FunctionCall) goja.Value { } func (r *extensionRuntime) fileWrite(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 2 { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "path and data are required", - }) + return r.jsError("path and data are required") } path := call.Arguments[0].String() @@ -829,18 +687,12 @@ func (r *extensionRuntime) fileWrite(call goja.FunctionCall) goja.Value { fullPath, err := r.validatePath(path) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } dir := filepath.Dir(fullPath) if err := os.MkdirAll(dir, 0755); err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("failed to create directory: %v", err), - }) + return r.jsError("failed to create directory: %v", err) } // Full-content write: stage and rename so a kill mid-write cannot leave @@ -848,40 +700,27 @@ func (r *extensionRuntime) fileWrite(call goja.FunctionCall) goja.Value { stagedPath := stagedDownloadPath(fullPath) if err := os.WriteFile(stagedPath, []byte(data), 0644); err != nil { os.Remove(stagedPath) - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } if err := os.Rename(stagedPath, fullPath); err != nil { os.Remove(stagedPath) - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } - return r.vm.ToValue(map[string]any{ - "success": true, - "path": fullPath, + return r.jsSuccess(map[string]any{ + "path": fullPath, }) } func (r *extensionRuntime) fileWriteBytes(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 2 { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "path and data are required", - }) + return r.jsError("path and data are required") } path := call.Arguments[0].String() fullPath, err := r.validatePath(path) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } options := parseRuntimeOptionsArgument(call, 2) @@ -892,32 +731,20 @@ func (r *extensionRuntime) fileWriteBytes(call goja.FunctionCall) goja.Value { encoding := runtimeOptionString(options, "encoding", "base64") if appendMode && hasOffset { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "append and offset cannot be used together", - }) + return r.jsError("append and offset cannot be used together") } if offset < 0 { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "offset must be >= 0", - }) + return r.jsError("offset must be >= 0") } data, err := decodeRuntimeBytesValue(call.Arguments[1].Export(), encoding) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } dir := filepath.Dir(fullPath) if err := os.MkdirAll(dir, 0755); err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("failed to create directory: %v", err), - }) + return r.jsError("failed to create directory: %v", err) } flags := os.O_CREATE | os.O_WRONLY @@ -930,28 +757,19 @@ func (r *extensionRuntime) fileWriteBytes(call goja.FunctionCall) goja.Value { file, err := os.OpenFile(fullPath, flags, 0644) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } defer file.Close() if hasOffset && !appendMode { if _, err := file.Seek(offset, io.SeekStart); err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("failed to seek file: %v", err), - }) + return r.jsError("failed to seek file: %v", err) } } written, err := file.Write(data) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } info, statErr := file.Stat() @@ -960,8 +778,7 @@ func (r *extensionRuntime) fileWriteBytes(call goja.FunctionCall) goja.Value { size = info.Size() } - return r.vm.ToValue(map[string]any{ - "success": true, + return r.jsSuccess(map[string]any{ "path": fullPath, "bytes_written": written, "size": size, @@ -970,10 +787,7 @@ func (r *extensionRuntime) fileWriteBytes(call goja.FunctionCall) goja.Value { func (r *extensionRuntime) fileCopy(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 2 { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "source and destination paths are required", - }) + return r.jsError("source and destination paths are required") } srcPath := call.Arguments[0].String() @@ -981,72 +795,47 @@ func (r *extensionRuntime) fileCopy(call goja.FunctionCall) goja.Value { fullSrc, err := r.validatePath(srcPath) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } fullDst, err := r.validatePath(dstPath) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } srcFile, err := os.Open(fullSrc) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("failed to read source: %v", err), - }) + return r.jsError("failed to read source: %v", err) } defer srcFile.Close() dir := filepath.Dir(fullDst) if err := os.MkdirAll(dir, 0755); err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("failed to create directory: %v", err), - }) + return r.jsError("failed to create directory: %v", err) } dstFile, err := os.OpenFile(fullDst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("failed to open destination: %v", err), - }) + return r.jsError("failed to open destination: %v", err) } if _, err := io.Copy(dstFile, srcFile); err != nil { _ = dstFile.Close() - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("failed to copy file: %v", err), - }) + return r.jsError("failed to copy file: %v", err) } if err := dstFile.Close(); err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("failed to finalize destination: %v", err), - }) + return r.jsError("failed to finalize destination: %v", err) } - return r.vm.ToValue(map[string]any{ - "success": true, - "path": fullDst, + return r.jsSuccess(map[string]any{ + "path": fullDst, }) } func (r *extensionRuntime) fileMove(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 2 { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "source and destination paths are required", - }) + return r.jsError("source and destination paths are required") } srcPath := call.Arguments[0].String() @@ -1054,68 +843,45 @@ func (r *extensionRuntime) fileMove(call goja.FunctionCall) goja.Value { fullSrc, err := r.validatePath(srcPath) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } fullDst, err := r.validatePath(dstPath) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } dir := filepath.Dir(fullDst) if err := os.MkdirAll(dir, 0755); err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("failed to create directory: %v", err), - }) + return r.jsError("failed to create directory: %v", err) } if err := os.Rename(fullSrc, fullDst); err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": fmt.Sprintf("failed to move file: %v", err), - }) + return r.jsError("failed to move file: %v", err) } - return r.vm.ToValue(map[string]any{ - "success": true, - "path": fullDst, + return r.jsSuccess(map[string]any{ + "path": fullDst, }) } func (r *extensionRuntime) fileGetSize(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "path is required", - }) + return r.jsError("path is required") } path := call.Arguments[0].String() fullPath, err := r.validatePath(path) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } info, err := os.Stat(fullPath) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } - return r.vm.ToValue(map[string]any{ - "success": true, - "size": info.Size(), + return r.jsSuccess(map[string]any{ + "size": info.Size(), }) } diff --git a/go_backend/extension_runtime_http.go b/go_backend/extension_runtime_http.go index b6c383a9..05210f9b 100644 --- a/go_backend/extension_runtime_http.go +++ b/go_backend/extension_runtime_http.go @@ -35,6 +35,12 @@ func readExtensionHTTPResponseBody(resp *http.Response) ([]byte, error) { return body, nil } +func setDefaultExtensionUA(req *http.Request) { + if req.Header.Get("User-Agent") == "" { + req.Header.Set("User-Agent", "Spotiflac-Extension/1.0") + } +} + func (r *extensionRuntime) validateDomain(urlStr string) error { parsed, err := url.Parse(urlStr) if err != nil { @@ -106,9 +112,7 @@ func (r *extensionRuntime) httpGet(call goja.FunctionCall) goja.Value { req.Header.Set(k, v) } - if req.Header.Get("User-Agent") == "" { - req.Header.Set("User-Agent", "Spotiflac-Extension/1.0") - } + setDefaultExtensionUA(req) resp, err := r.httpClient.Do(req) if err != nil { @@ -201,9 +205,7 @@ func (r *extensionRuntime) httpPost(call goja.FunctionCall) goja.Value { req.Header.Set(k, v) } - if req.Header.Get("User-Agent") == "" { - req.Header.Set("User-Agent", "Spotiflac-Extension/1.0") - } + setDefaultExtensionUA(req) if req.Header.Get("Content-Type") == "" { req.Header.Set("Content-Type", "application/json") } @@ -311,9 +313,7 @@ func (r *extensionRuntime) httpRequest(call goja.FunctionCall) goja.Value { req.Header.Set(k, v) } - if req.Header.Get("User-Agent") == "" { - req.Header.Set("User-Agent", "Spotiflac-Extension/1.0") - } + setDefaultExtensionUA(req) if bodyStr != "" && req.Header.Get("Content-Type") == "" { req.Header.Set("Content-Type", "application/json") } @@ -437,9 +437,7 @@ func (r *extensionRuntime) httpMethodShortcut(method string, call goja.FunctionC for k, v := range headers { req.Header.Set(k, v) } - if req.Header.Get("User-Agent") == "" { - req.Header.Set("User-Agent", "Spotiflac-Extension/1.0") - } + setDefaultExtensionUA(req) if bodyStr != "" && req.Header.Get("Content-Type") == "" { req.Header.Set("Content-Type", "application/json") } diff --git a/go_backend/extension_runtime_storage.go b/go_backend/extension_runtime_storage.go index 099a17b2..acdc52c4 100644 --- a/go_backend/extension_runtime_storage.go +++ b/go_backend/extension_runtime_storage.go @@ -405,10 +405,7 @@ func (r *extensionRuntime) saveCredentials(creds map[string]any) error { func (r *extensionRuntime) credentialsStore(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 2 { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "key and value are required", - }) + return r.jsError("key and value are required") } key := call.Arguments[0].String() @@ -416,10 +413,7 @@ func (r *extensionRuntime) credentialsStore(call goja.FunctionCall) goja.Value { if err := r.ensureCredentialsLoaded(); err != nil { GoLog("[Extension:%s] Credentials load error: %v\n", r.extensionID, err) - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } r.credentialsMu.RLock() @@ -429,15 +423,10 @@ func (r *extensionRuntime) credentialsStore(call goja.FunctionCall) goja.Value { if err := r.saveCredentials(nextCreds); err != nil { GoLog("[Extension:%s] Credentials save error: %v\n", r.extensionID, err) - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } - return r.vm.ToValue(map[string]any{ - "success": true, - }) + return r.jsSuccess(nil) } func (r *extensionRuntime) credentialsGet(call goja.FunctionCall) goja.Value { diff --git a/go_backend/extension_runtime_utils.go b/go_backend/extension_runtime_utils.go index f7271e02..325133fe 100644 --- a/go_backend/extension_runtime_utils.go +++ b/go_backend/extension_runtime_utils.go @@ -16,6 +16,24 @@ import ( "github.com/dop251/goja" ) +// jsError returns the standard {"success": false, "error": ...} extension +// response. +func (r *extensionRuntime) jsError(format string, args ...any) goja.Value { + return r.vm.ToValue(map[string]any{ + "success": false, + "error": fmt.Sprintf(format, args...), + }) +} + +// jsSuccess returns kv with "success": true added. +func (r *extensionRuntime) jsSuccess(kv map[string]any) goja.Value { + if kv == nil { + kv = map[string]any{} + } + kv["success"] = true + return r.vm.ToValue(kv) +} + func (r *extensionRuntime) base64Encode(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return r.vm.ToValue("") @@ -162,10 +180,7 @@ func (r *extensionRuntime) stringifyJSON(call goja.FunctionCall) goja.Value { func (r *extensionRuntime) cryptoEncrypt(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 2 { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "plaintext and key are required", - }) + return r.jsError("plaintext and key are required") } plaintext := call.Arguments[0].String() @@ -175,24 +190,17 @@ func (r *extensionRuntime) cryptoEncrypt(call goja.FunctionCall) goja.Value { encrypted, err := encryptAES([]byte(plaintext), keyHash[:]) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } - return r.vm.ToValue(map[string]any{ - "success": true, - "data": base64.StdEncoding.EncodeToString(encrypted), + return r.jsSuccess(map[string]any{ + "data": base64.StdEncoding.EncodeToString(encrypted), }) } func (r *extensionRuntime) cryptoDecrypt(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 2 { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "ciphertext and key are required", - }) + return r.jsError("ciphertext and key are required") } ciphertextB64 := call.Arguments[0].String() @@ -200,25 +208,18 @@ func (r *extensionRuntime) cryptoDecrypt(call goja.FunctionCall) goja.Value { ciphertext, err := base64.StdEncoding.DecodeString(ciphertextB64) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "invalid base64 ciphertext", - }) + return r.jsError("invalid base64 ciphertext") } keyHash := sha256.Sum256([]byte(keyStr)) decrypted, err := decryptAES(ciphertext, keyHash[:]) if err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": "invalid base64 ciphertext", - }) + return r.jsError("invalid base64 ciphertext") } - return r.vm.ToValue(map[string]any{ - "success": true, - "data": string(decrypted), + return r.jsSuccess(map[string]any{ + "data": string(decrypted), }) } @@ -232,16 +233,12 @@ func (r *extensionRuntime) cryptoGenerateKey(call goja.FunctionCall) goja.Value key := make([]byte, length) if _, err := rand.Read(key); err != nil { - return r.vm.ToValue(map[string]any{ - "success": false, - "error": err.Error(), - }) + return r.jsError("%s", err.Error()) } - return r.vm.ToValue(map[string]any{ - "success": true, - "key": base64.StdEncoding.EncodeToString(key), - "hex": hex.EncodeToString(key), + return r.jsSuccess(map[string]any{ + "key": base64.StdEncoding.EncodeToString(key), + "hex": hex.EncodeToString(key), }) } diff --git a/go_backend/json_util.go b/go_backend/json_util.go new file mode 100644 index 00000000..43d19219 --- /dev/null +++ b/go_backend/json_util.go @@ -0,0 +1,13 @@ +package gobackend + +import "encoding/json" + +// marshalJSONString marshals v and returns it as a string, the shape every +// gomobile export returns. +func marshalJSONString(v any) (string, error) { + jsonBytes, err := json.Marshal(v) + if err != nil { + return "", err + } + return string(jsonBytes), nil +} diff --git a/go_backend/metadata.go b/go_backend/metadata.go index 64aa39e2..4cb675db 100644 --- a/go_backend/metadata.go +++ b/go_backend/metadata.go @@ -846,8 +846,7 @@ func joinVorbisCommentValues(values []string) string { } func fileExists(path string) bool { - _, err := os.Stat(path) - return err == nil + return CheckFileExists(path) } func ExtractCoverArt(filePath string) ([]byte, error) {