mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-07-28 23:08:59 +02:00
refactor(backend): dedupe response builders and MusicBrainz fetch
marshalJSONString collapses 62 identical marshal-and-return blocks in the gomobile exports; jsError/jsSuccess collapse 135 goja extension-response maps; the two MusicBrainz fetchers share one request+retry helper; the extension-HTTP default User-Agent block and fileExists get single homes. No exported signature changes.
This commit is contained in:
+2
-12
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user