From 4c4553913f269c2c5b4482b433672c5c983d379f Mon Sep 17 00:00:00 2001 From: zarzet Date: Sat, 4 Apr 2026 21:30:11 +0700 Subject: [PATCH] fix: harden gomobile extension bindings and m4a cover retention --- go_backend/exports.go | 64 ++++++------ go_backend/extension_manager.go | 78 +++++++------- go_backend/extension_providers.go | 104 +++++++++---------- go_backend/extension_providers_test.go | 2 +- go_backend/extension_runtime.go | 18 ++-- go_backend/extension_runtime_auth.go | 20 ++-- go_backend/extension_runtime_ffmpeg.go | 6 +- go_backend/extension_runtime_file.go | 18 ++-- go_backend/extension_runtime_http.go | 18 ++-- go_backend/extension_runtime_matching.go | 6 +- go_backend/extension_runtime_polyfills.go | 14 +-- go_backend/extension_runtime_storage.go | 46 ++++---- go_backend/extension_runtime_storage_test.go | 14 +-- go_backend/extension_runtime_utils.go | 40 +++---- go_backend/extension_store.go | 2 +- go_backend/extension_test.go | 20 ++-- go_backend/extension_timeout.go | 4 +- go_backend/lyrics.go | 4 +- lib/services/ffmpeg_service.dart | 34 +++--- 19 files changed, 261 insertions(+), 251 deletions(-) diff --git a/go_backend/exports.go b/go_backend/exports.go index 6e7deba1..51392007 100644 --- a/go_backend/exports.go +++ b/go_backend/exports.go @@ -2302,7 +2302,7 @@ func ReEnrichFile(requestJSON string) (string, error) { deezerClient := GetDeezerClient() GoLog("[ReEnrich] Trying metadata providers in configured priority...\n") - manager := GetExtensionManager() + manager := getExtensionManager() if identifierTrack, err := resolveReEnrichTrackFromIdentifiers(req); err == nil && identifierTrack != nil { GoLog("[ReEnrich] Identifier-first metadata match (%s): %s - %s (album: %s, date: %s)\n", identifierTrack.ProviderID, identifierTrack.Name, identifierTrack.Artists, identifierTrack.AlbumName, identifierTrack.ReleaseDate) @@ -2542,7 +2542,7 @@ func ReEnrichFile(requestJSON string) (string, error) { } func InitExtensionSystem(extensionsDir, dataDir string) error { - manager := GetExtensionManager() + manager := getExtensionManager() if err := manager.SetDirectories(extensionsDir, dataDir); err != nil { return err } @@ -2556,7 +2556,7 @@ func InitExtensionSystem(extensionsDir, dataDir string) error { } func LoadExtensionsFromDir(dirPath string) (string, error) { - manager := GetExtensionManager() + manager := getExtensionManager() loaded, errors := manager.LoadExtensionsFromDirectory(dirPath) result := map[string]interface{}{ @@ -2577,7 +2577,7 @@ func LoadExtensionsFromDir(dirPath string) (string, error) { } func LoadExtensionFromPath(filePath string) (string, error) { - manager := GetExtensionManager() + manager := getExtensionManager() ext, err := manager.LoadExtensionFromFile(filePath) if err != nil { return "", err @@ -2600,17 +2600,17 @@ func LoadExtensionFromPath(filePath string) (string, error) { } func UnloadExtensionByID(extensionID string) error { - manager := GetExtensionManager() + manager := getExtensionManager() return manager.UnloadExtension(extensionID) } func RemoveExtensionByID(extensionID string) error { - manager := GetExtensionManager() + manager := getExtensionManager() return manager.RemoveExtension(extensionID) } func UpgradeExtensionFromPath(filePath string) (string, error) { - manager := GetExtensionManager() + manager := getExtensionManager() ext, err := manager.UpgradeExtension(filePath) if err != nil { return "", err @@ -2632,17 +2632,17 @@ func UpgradeExtensionFromPath(filePath string) (string, error) { } func CheckExtensionUpgradeFromPath(filePath string) (string, error) { - manager := GetExtensionManager() + manager := getExtensionManager() return manager.CheckExtensionUpgradeJSON(filePath) } func GetInstalledExtensions() (string, error) { - manager := GetExtensionManager() + manager := getExtensionManager() return manager.GetInstalledExtensionsJSON() } func SetExtensionEnabledByID(extensionID string, enabled bool) error { - manager := GetExtensionManager() + manager := getExtensionManager() return manager.SetExtensionEnabled(extensionID, enabled) } @@ -2707,12 +2707,12 @@ func SetExtensionSettingsJSON(extensionID, settingsJSON string) error { return err } - manager := GetExtensionManager() + manager := getExtensionManager() return manager.InitializeExtension(extensionID, settings) } func SearchTracksWithExtensionsJSON(query string, limit int) (string, error) { - manager := GetExtensionManager() + manager := getExtensionManager() tracks, err := manager.SearchTracksWithExtensions(query, limit) if err != nil { return "", err @@ -2727,7 +2727,7 @@ func SearchTracksWithExtensionsJSON(query string, limit int) (string, error) { } func SearchTracksWithMetadataProvidersJSON(query string, limit int, includeExtensions bool) (string, error) { - manager := GetExtensionManager() + manager := getExtensionManager() tracks, err := manager.SearchTracksWithMetadataProviders(query, limit, includeExtensions) if err != nil { return "", err @@ -2774,12 +2774,12 @@ func DownloadWithExtensionsJSON(requestJSON string) (string, error) { } func CleanupExtensions() { - manager := GetExtensionManager() + manager := getExtensionManager() manager.UnloadAllExtensions() } func InvokeExtensionActionJSON(extensionID, actionName string) (string, error) { - manager := GetExtensionManager() + manager := getExtensionManager() result, err := manager.InvokeAction(extensionID, actionName) if err != nil { return "", err @@ -2916,7 +2916,7 @@ func GetAllPendingFFmpegCommandsJSON() (string, error) { } func EnrichTrackWithExtensionJSON(extensionID, trackJSON string) (string, error) { - manager := GetExtensionManager() + manager := getExtensionManager() ext, err := manager.GetExtension(extensionID) if err != nil { return trackJSON, nil @@ -2931,7 +2931,7 @@ func EnrichTrackWithExtensionJSON(extensionID, trackJSON string) (string, error) return trackJSON, fmt.Errorf("failed to parse track: %w", err) } - provider := NewExtensionProviderWrapper(ext) + provider := newExtensionProviderWrapper(ext) enrichedTrack, err := provider.EnrichTrack(&track) if err != nil { return trackJSON, nil @@ -2946,7 +2946,7 @@ func EnrichTrackWithExtensionJSON(extensionID, trackJSON string) (string, error) } func CustomSearchWithExtensionJSON(extensionID, query string, optionsJSON string) (string, error) { - manager := GetExtensionManager() + manager := getExtensionManager() ext, err := manager.GetExtension(extensionID) if err != nil { return "", err @@ -2963,7 +2963,7 @@ func CustomSearchWithExtensionJSON(extensionID, query string, optionsJSON string } } - provider := NewExtensionProviderWrapper(ext) + provider := newExtensionProviderWrapper(ext) tracks, err := provider.CustomSearch(query, options) if err != nil { return "", err @@ -3001,7 +3001,7 @@ func CustomSearchWithExtensionJSON(extensionID, query string, optionsJSON string } func GetSearchProvidersJSON() (string, error) { - manager := GetExtensionManager() + manager := getExtensionManager() providers := manager.GetSearchProviders() result := make([]map[string]interface{}, 0, len(providers)) @@ -3024,7 +3024,7 @@ func GetSearchProvidersJSON() (string, error) { } func HandleURLWithExtensionJSON(url string) (string, error) { - manager := GetExtensionManager() + manager := getExtensionManager() resultWithID, err := manager.HandleURLWithExtension(url) if err != nil { return "", err @@ -3194,7 +3194,7 @@ func HandleURLWithExtensionJSON(url string) (string, error) { } func FindURLHandlerJSON(url string) string { - manager := GetExtensionManager() + manager := getExtensionManager() handler := manager.FindURLHandler(url) if handler == nil { return "" @@ -3203,7 +3203,7 @@ func FindURLHandlerJSON(url string) string { } func GetAlbumWithExtensionJSON(extensionID, albumID string) (string, error) { - manager := GetExtensionManager() + manager := getExtensionManager() ext, err := manager.GetExtension(extensionID) if err != nil { return "", err @@ -3216,7 +3216,7 @@ func GetAlbumWithExtensionJSON(extensionID, albumID string) (string, error) { return "", fmt.Errorf("extension '%s' is disabled", extensionID) } - provider := NewExtensionProviderWrapper(ext) + provider := newExtensionProviderWrapper(ext) album, err := provider.GetAlbum(albumID) if err != nil { return "", err @@ -3279,7 +3279,7 @@ func GetAlbumWithExtensionJSON(extensionID, albumID string) (string, error) { } func GetPlaylistWithExtensionJSON(extensionID, playlistID string) (string, error) { - manager := GetExtensionManager() + manager := getExtensionManager() ext, err := manager.GetExtension(extensionID) if err != nil { return "", err @@ -3380,7 +3380,7 @@ func GetPlaylistWithExtensionJSON(extensionID, playlistID string) (string, error } func GetArtistWithExtensionJSON(extensionID, artistID string) (string, error) { - manager := GetExtensionManager() + manager := getExtensionManager() ext, err := manager.GetExtension(extensionID) if err != nil { return "", err @@ -3390,7 +3390,7 @@ func GetArtistWithExtensionJSON(extensionID, artistID string) (string, error) { return "", fmt.Errorf("extension '%s' is not a metadata provider", extensionID) } - provider := NewExtensionProviderWrapper(ext) + provider := newExtensionProviderWrapper(ext) artist, err := provider.GetArtist(artistID) if err != nil { return "", err @@ -3485,7 +3485,7 @@ func GetArtistWithExtensionJSON(extensionID, artistID string) (string, error) { } func GetURLHandlersJSON() (string, error) { - manager := GetExtensionManager() + manager := getExtensionManager() handlers := manager.GetURLHandlers() result := make([]map[string]interface{}, 0, len(handlers)) @@ -3513,7 +3513,7 @@ func RunPostProcessingJSON(filePath, metadataJSON string) (string, error) { } } - manager := GetExtensionManager() + manager := getExtensionManager() result, err := manager.RunPostProcessing(filePath, metadata) if err != nil { return "", err @@ -3542,7 +3542,7 @@ func RunPostProcessingV2JSON(inputJSON, metadataJSON string) (string, error) { } } - manager := GetExtensionManager() + manager := getExtensionManager() result, err := manager.RunPostProcessingV2(input, metadata) if err != nil { return "", err @@ -3557,7 +3557,7 @@ func RunPostProcessingV2JSON(inputJSON, metadataJSON string) (string, error) { } func GetPostProcessingProvidersJSON() (string, error) { - manager := GetExtensionManager() + manager := getExtensionManager() providers := manager.GetPostProcessingProviders() result := make([]map[string]interface{}, 0, len(providers)) @@ -3723,7 +3723,7 @@ func ClearStoreCacheJSON() error { } func callExtensionFunctionJSON(extensionID, functionName string, timeout time.Duration) (string, error) { - manager := GetExtensionManager() + manager := getExtensionManager() ext, err := manager.GetExtension(extensionID) if err != nil { return "", err diff --git a/go_backend/extension_manager.go b/go_backend/extension_manager.go index 38656584..557bdb63 100644 --- a/go_backend/extension_manager.go +++ b/go_backend/extension_manager.go @@ -43,12 +43,12 @@ func compareVersions(v1, v2 string) int { return 0 } -type LoadedExtension struct { +type loadedExtension struct { ID string `json:"id"` Manifest *ExtensionManifest `json:"manifest"` VM *goja.Runtime `json:"-"` VMMu sync.Mutex `json:"-"` - runtime *ExtensionRuntime + runtime *extensionRuntime initialized bool Enabled bool `json:"enabled"` Error string `json:"error,omitempty"` @@ -73,7 +73,7 @@ func getExtensionInitSettings(extensionID string) map[string]interface{} { return filtered } -func ensureRuntimeReadyLocked(ext *LoadedExtension, applyStoredSettings bool) error { +func ensureRuntimeReadyLocked(ext *loadedExtension, applyStoredSettings bool) error { if ext.VM == nil || ext.runtime == nil { if err := initializeVMLocked(ext); err != nil { ext.Error = err.Error() @@ -100,14 +100,14 @@ func ensureRuntimeReadyLocked(ext *LoadedExtension, applyStoredSettings bool) er return nil } -func (ext *LoadedExtension) ensureRuntimeReady() error { +func (ext *loadedExtension) ensureRuntimeReady() error { ext.VMMu.Lock() defer ext.VMMu.Unlock() return ensureRuntimeReadyLocked(ext, true) } -func (ext *LoadedExtension) lockReadyVM() (*goja.Runtime, error) { +func (ext *loadedExtension) lockReadyVM() (*goja.Runtime, error) { ext.VMMu.Lock() if err := ensureRuntimeReadyLocked(ext, true); err != nil { ext.VMMu.Unlock() @@ -116,28 +116,28 @@ func (ext *LoadedExtension) lockReadyVM() (*goja.Runtime, error) { return ext.VM, nil } -type ExtensionManager struct { +type extensionManager struct { mu sync.RWMutex - extensions map[string]*LoadedExtension + extensions map[string]*loadedExtension extensionsDir string dataDir string } var ( - globalExtManager *ExtensionManager + globalExtManager *extensionManager globalExtManagerOnce sync.Once ) -func GetExtensionManager() *ExtensionManager { +func getExtensionManager() *extensionManager { globalExtManagerOnce.Do(func() { - globalExtManager = &ExtensionManager{ - extensions: make(map[string]*LoadedExtension), + globalExtManager = &extensionManager{ + extensions: make(map[string]*loadedExtension), } }) return globalExtManager } -func (m *ExtensionManager) SetDirectories(extensionsDir, dataDir string) error { +func (m *extensionManager) SetDirectories(extensionsDir, dataDir string) error { m.mu.Lock() defer m.mu.Unlock() @@ -154,7 +154,7 @@ func (m *ExtensionManager) SetDirectories(extensionsDir, dataDir string) error { return nil } -func (m *ExtensionManager) LoadExtensionFromFile(filePath string) (*LoadedExtension, error) { +func (m *extensionManager) LoadExtensionFromFile(filePath string) (*loadedExtension, error) { if !strings.HasSuffix(strings.ToLower(filePath), ".spotiflac-ext") { return nil, fmt.Errorf("Invalid file format. Please select a .spotiflac-ext file") } @@ -272,7 +272,7 @@ func (m *ExtensionManager) LoadExtensionFromFile(filePath string) (*LoadedExtens return nil, fmt.Errorf("failed to create extension data directory: %w", err) } - ext := &LoadedExtension{ + ext := &loadedExtension{ ID: manifest.Name, Manifest: manifest, Enabled: false, // New extensions start disabled @@ -292,7 +292,7 @@ func (m *ExtensionManager) LoadExtensionFromFile(filePath string) (*LoadedExtens return ext, nil } -func initializeVMLocked(ext *LoadedExtension) error { +func initializeVMLocked(ext *loadedExtension) error { ext.VM = nil ext.runtime = nil ext.initialized = false @@ -305,7 +305,7 @@ func initializeVMLocked(ext *LoadedExtension) error { return fmt.Errorf("failed to read index.js: %w", err) } - runtime := NewExtensionRuntime(ext) + runtime := newExtensionRuntime(ext) ext.runtime = runtime runtime.RegisterAPIs(vm) runtime.RegisterGoBackendAPIs(vm) @@ -342,14 +342,14 @@ func initializeVMLocked(ext *LoadedExtension) error { return nil } -func (m *ExtensionManager) initializeVM(ext *LoadedExtension) error { +func (m *extensionManager) initializeVM(ext *loadedExtension) error { ext.VMMu.Lock() defer ext.VMMu.Unlock() return initializeVMLocked(ext) } func initializeExtensionWithSettingsLocked( - ext *LoadedExtension, + ext *loadedExtension, settings map[string]interface{}, ) error { if ext.VM == nil { @@ -405,7 +405,7 @@ func initializeExtensionWithSettingsLocked( return nil } -func runCleanupLocked(ext *LoadedExtension) error { +func runCleanupLocked(ext *loadedExtension) error { if ext.VM != nil { script := ` (function() { @@ -446,7 +446,7 @@ func runCleanupLocked(ext *LoadedExtension) error { return nil } -func teardownVMLocked(ext *LoadedExtension) { +func teardownVMLocked(ext *loadedExtension) { if err := runCleanupLocked(ext); err != nil { GoLog("[Extension] Error calling cleanup for %s: %v\n", ext.ID, err) } @@ -461,7 +461,7 @@ func teardownVMLocked(ext *LoadedExtension) { ext.initialized = false } -func validateExtensionLoad(ext *LoadedExtension) error { +func validateExtensionLoad(ext *loadedExtension) error { ext.VMMu.Lock() defer ext.VMMu.Unlock() @@ -472,7 +472,7 @@ func validateExtensionLoad(ext *LoadedExtension) error { return nil } -func (m *ExtensionManager) UnloadExtension(extensionID string) error { +func (m *extensionManager) UnloadExtension(extensionID string) error { m.mu.Lock() defer m.mu.Unlock() @@ -491,7 +491,7 @@ func (m *ExtensionManager) UnloadExtension(extensionID string) error { return nil } -func (m *ExtensionManager) GetExtension(extensionID string) (*LoadedExtension, error) { +func (m *extensionManager) GetExtension(extensionID string) (*loadedExtension, error) { m.mu.RLock() defer m.mu.RUnlock() @@ -502,18 +502,18 @@ func (m *ExtensionManager) GetExtension(extensionID string) (*LoadedExtension, e return ext, nil } -func (m *ExtensionManager) GetAllExtensions() []*LoadedExtension { +func (m *extensionManager) GetAllExtensions() []*loadedExtension { m.mu.RLock() defer m.mu.RUnlock() - result := make([]*LoadedExtension, 0, len(m.extensions)) + result := make([]*loadedExtension, 0, len(m.extensions)) for _, ext := range m.extensions { result = append(result, ext) } return result } -func (m *ExtensionManager) SetExtensionEnabled(extensionID string, enabled bool) error { +func (m *extensionManager) SetExtensionEnabled(extensionID string, enabled bool) error { m.mu.Lock() defer m.mu.Unlock() @@ -547,7 +547,7 @@ func (m *ExtensionManager) SetExtensionEnabled(extensionID string, enabled bool) return nil } -func (m *ExtensionManager) LoadExtensionsFromDirectory(dirPath string) ([]string, []error) { +func (m *extensionManager) LoadExtensionsFromDirectory(dirPath string) ([]string, []error) { var loaded []string var errors []error @@ -585,7 +585,7 @@ func (m *ExtensionManager) LoadExtensionsFromDirectory(dirPath string) ([]string return loaded, errors } -func (m *ExtensionManager) loadExtensionFromDirectory(dirPath string) (*LoadedExtension, error) { +func (m *extensionManager) loadExtensionFromDirectory(dirPath string) (*loadedExtension, error) { m.mu.Lock() defer m.mu.Unlock() @@ -615,7 +615,7 @@ func (m *ExtensionManager) loadExtensionFromDirectory(dirPath string) (*LoadedEx return nil, fmt.Errorf("failed to create extension data directory: %w", err) } - ext := &LoadedExtension{ + ext := &loadedExtension{ ID: manifest.Name, Manifest: manifest, Enabled: false, // Will be restored from settings store @@ -643,7 +643,7 @@ func (m *ExtensionManager) loadExtensionFromDirectory(dirPath string) (*LoadedEx return ext, nil } -func (m *ExtensionManager) RemoveExtension(extensionID string) error { +func (m *extensionManager) RemoveExtension(extensionID string) error { ext, err := m.GetExtension(extensionID) if err != nil { return err @@ -663,7 +663,7 @@ func (m *ExtensionManager) RemoveExtension(extensionID string) error { } // Only allows upgrades (new version > current version), not downgrades -func (m *ExtensionManager) UpgradeExtension(filePath string) (*LoadedExtension, error) { +func (m *extensionManager) UpgradeExtension(filePath string) (*loadedExtension, error) { if !strings.HasSuffix(strings.ToLower(filePath), ".spotiflac-ext") { return nil, fmt.Errorf("Invalid file format. Please select a .spotiflac-ext file") } @@ -777,7 +777,7 @@ func (m *ExtensionManager) UpgradeExtension(filePath string) (*LoadedExtension, } } - ext := &LoadedExtension{ + ext := &loadedExtension{ ID: newManifest.Name, Manifest: newManifest, Enabled: wasEnabled, // Preserve enabled state from before upgrade @@ -812,7 +812,7 @@ type ExtensionUpgradeInfo struct { IsInstalled bool `json:"is_installed"` } -func (m *ExtensionManager) checkExtensionUpgradeInternal(filePath string) (*ExtensionUpgradeInfo, error) { +func (m *extensionManager) checkExtensionUpgradeInternal(filePath string) (*ExtensionUpgradeInfo, error) { if !strings.HasSuffix(strings.ToLower(filePath), ".spotiflac-ext") { return nil, fmt.Errorf("Invalid file format. Please select a .spotiflac-ext file") } @@ -871,7 +871,7 @@ func (m *ExtensionManager) checkExtensionUpgradeInternal(filePath string) (*Exte return info, nil } -func (m *ExtensionManager) CheckExtensionUpgradeJSON(filePath string) (string, error) { +func (m *extensionManager) CheckExtensionUpgradeJSON(filePath string) (string, error) { info, err := m.checkExtensionUpgradeInternal(filePath) if err != nil { return "", err @@ -885,7 +885,7 @@ func (m *ExtensionManager) CheckExtensionUpgradeJSON(filePath string) (string, e return string(jsonBytes), nil } -func (m *ExtensionManager) GetInstalledExtensionsJSON() (string, error) { +func (m *extensionManager) GetInstalledExtensionsJSON() (string, error) { extensions := m.GetAllExtensions() type ExtensionInfo struct { @@ -982,7 +982,7 @@ func (m *ExtensionManager) GetInstalledExtensionsJSON() (string, error) { return string(jsonBytes), nil } -func (m *ExtensionManager) InitializeExtension(extensionID string, settings map[string]interface{}) error { +func (m *extensionManager) InitializeExtension(extensionID string, settings map[string]interface{}) error { m.mu.Lock() defer m.mu.Unlock() @@ -1000,7 +1000,7 @@ func (m *ExtensionManager) InitializeExtension(extensionID string, settings map[ return initializeExtensionWithSettingsLocked(ext, settings) } -func (m *ExtensionManager) CleanupExtension(extensionID string) error { +func (m *extensionManager) CleanupExtension(extensionID string) error { m.mu.Lock() defer m.mu.Unlock() @@ -1022,7 +1022,7 @@ func (m *ExtensionManager) CleanupExtension(extensionID string) error { return nil } -func (m *ExtensionManager) UnloadAllExtensions() { +func (m *extensionManager) UnloadAllExtensions() { m.mu.Lock() extensionIDs := make([]string, 0, len(m.extensions)) for id := range m.extensions { @@ -1037,7 +1037,7 @@ func (m *ExtensionManager) UnloadAllExtensions() { GoLog("[Extension] All extensions unloaded\n") } -func (m *ExtensionManager) InvokeAction(extensionID string, actionName string) (map[string]interface{}, error) { +func (m *extensionManager) InvokeAction(extensionID string, actionName string) (map[string]interface{}, error) { m.mu.Lock() defer m.mu.Unlock() diff --git a/go_backend/extension_providers.go b/go_backend/extension_providers.go index 4573628e..1821d8f3 100644 --- a/go_backend/extension_providers.go +++ b/go_backend/extension_providers.go @@ -116,19 +116,19 @@ type ExtDownloadResult struct { DecryptionKey string `json:"decryption_key,omitempty"` } -type ExtensionProviderWrapper struct { - extension *LoadedExtension +type extensionProviderWrapper struct { + extension *loadedExtension vm *goja.Runtime } -func NewExtensionProviderWrapper(ext *LoadedExtension) *ExtensionProviderWrapper { - return &ExtensionProviderWrapper{ +func newExtensionProviderWrapper(ext *loadedExtension) *extensionProviderWrapper { + return &extensionProviderWrapper{ extension: ext, vm: ext.VM, } } -func (p *ExtensionProviderWrapper) lockReadyVM() error { +func (p *extensionProviderWrapper) lockReadyVM() error { vm, err := p.extension.lockReadyVM() if err != nil { return err @@ -137,7 +137,7 @@ func (p *ExtensionProviderWrapper) lockReadyVM() error { return nil } -func (p *ExtensionProviderWrapper) SearchTracks(query string, limit int) (*ExtSearchResult, error) { +func (p *extensionProviderWrapper) SearchTracks(query string, limit int) (*ExtSearchResult, error) { if !p.extension.Manifest.IsMetadataProvider() { return nil, fmt.Errorf("extension '%s' is not a metadata provider", p.extension.ID) } @@ -197,7 +197,7 @@ func (p *ExtensionProviderWrapper) SearchTracks(query string, limit int) (*ExtSe return &searchResult, nil } -func (p *ExtensionProviderWrapper) GetTrack(trackID string) (*ExtTrackMetadata, error) { +func (p *extensionProviderWrapper) GetTrack(trackID string) (*ExtTrackMetadata, error) { if !p.extension.Manifest.IsMetadataProvider() { return nil, fmt.Errorf("extension '%s' is not a metadata provider", p.extension.ID) } @@ -246,7 +246,7 @@ func (p *ExtensionProviderWrapper) GetTrack(trackID string) (*ExtTrackMetadata, return &track, nil } -func (p *ExtensionProviderWrapper) GetAlbum(albumID string) (*ExtAlbumMetadata, error) { +func (p *extensionProviderWrapper) GetAlbum(albumID string) (*ExtAlbumMetadata, error) { if !p.extension.Manifest.IsMetadataProvider() { return nil, fmt.Errorf("extension '%s' is not a metadata provider", p.extension.ID) } @@ -298,7 +298,7 @@ func (p *ExtensionProviderWrapper) GetAlbum(albumID string) (*ExtAlbumMetadata, return &album, nil } -func (p *ExtensionProviderWrapper) GetArtist(artistID string) (*ExtArtistMetadata, error) { +func (p *extensionProviderWrapper) GetArtist(artistID string) (*ExtArtistMetadata, error) { if !p.extension.Manifest.IsMetadataProvider() { return nil, fmt.Errorf("extension '%s' is not a metadata provider", p.extension.ID) } @@ -353,7 +353,7 @@ func (p *ExtensionProviderWrapper) GetArtist(artistID string) (*ExtArtistMetadat return &artist, nil } -func (p *ExtensionProviderWrapper) EnrichTrack(track *ExtTrackMetadata) (*ExtTrackMetadata, error) { +func (p *extensionProviderWrapper) EnrichTrack(track *ExtTrackMetadata) (*ExtTrackMetadata, error) { if !p.extension.Manifest.IsMetadataProvider() { return track, nil } @@ -415,7 +415,7 @@ func (p *ExtensionProviderWrapper) EnrichTrack(track *ExtTrackMetadata) (*ExtTra return &enrichedTrack, nil } -func (p *ExtensionProviderWrapper) CheckAvailability(isrc, trackName, artistName, spotifyID, deezerID string) (*ExtAvailabilityResult, error) { +func (p *extensionProviderWrapper) CheckAvailability(isrc, trackName, artistName, spotifyID, deezerID string) (*ExtAvailabilityResult, error) { if !p.extension.Manifest.IsDownloadProvider() { return nil, fmt.Errorf("extension '%s' is not a download provider", p.extension.ID) } @@ -463,7 +463,7 @@ func (p *ExtensionProviderWrapper) CheckAvailability(isrc, trackName, artistName return &availability, nil } -func (p *ExtensionProviderWrapper) GetDownloadURL(trackID, quality string) (*ExtDownloadURLResult, error) { +func (p *extensionProviderWrapper) GetDownloadURL(trackID, quality string) (*ExtDownloadURLResult, error) { if !p.extension.Manifest.IsDownloadProvider() { return nil, fmt.Errorf("extension '%s' is not a download provider", p.extension.ID) } @@ -513,7 +513,7 @@ func (p *ExtensionProviderWrapper) GetDownloadURL(trackID, quality string) (*Ext const ExtDownloadTimeout = DownloadTimeout -func (p *ExtensionProviderWrapper) Download(trackID, quality, outputPath, itemID string, onProgress func(percent int)) (*ExtDownloadResult, error) { +func (p *extensionProviderWrapper) Download(trackID, quality, outputPath, itemID string, onProgress func(percent int)) (*ExtDownloadResult, error) { if !p.extension.Manifest.IsDownloadProvider() { return nil, fmt.Errorf("extension '%s' is not a download provider", p.extension.ID) } @@ -604,40 +604,40 @@ func (p *ExtensionProviderWrapper) Download(trackID, quality, outputPath, itemID return &downloadResult, nil } -func (m *ExtensionManager) GetMetadataProviders() []*ExtensionProviderWrapper { +func (m *extensionManager) GetMetadataProviders() []*extensionProviderWrapper { m.mu.RLock() defer m.mu.RUnlock() - var providers []*ExtensionProviderWrapper + var providers []*extensionProviderWrapper for _, ext := range m.extensions { if ext.Enabled && ext.Manifest.IsMetadataProvider() && ext.Error == "" { - providers = append(providers, NewExtensionProviderWrapper(ext)) + providers = append(providers, newExtensionProviderWrapper(ext)) } } return providers } -func (m *ExtensionManager) GetDownloadProviders() []*ExtensionProviderWrapper { +func (m *extensionManager) GetDownloadProviders() []*extensionProviderWrapper { m.mu.RLock() defer m.mu.RUnlock() - var providers []*ExtensionProviderWrapper + var providers []*extensionProviderWrapper for _, ext := range m.extensions { if ext.Enabled && ext.Manifest.IsDownloadProvider() && ext.Error == "" { - providers = append(providers, NewExtensionProviderWrapper(ext)) + providers = append(providers, newExtensionProviderWrapper(ext)) } } return providers } -func (m *ExtensionManager) SearchTracksWithExtensions(query string, limit int) ([]ExtTrackMetadata, error) { +func (m *extensionManager) SearchTracksWithExtensions(query string, limit int) ([]ExtTrackMetadata, error) { providers := m.GetMetadataProviders() if len(providers) == 0 { return nil, nil } - providerByID := make(map[string]*ExtensionProviderWrapper, len(providers)) - orderedProviders := make([]*ExtensionProviderWrapper, 0, len(providers)) + providerByID := make(map[string]*extensionProviderWrapper, len(providers)) + orderedProviders := make([]*extensionProviderWrapper, 0, len(providers)) for _, provider := range providers { providerByID[provider.extension.ID] = provider } @@ -830,13 +830,13 @@ func searchBuiltInMetadataTracks(providerID, query string, limit int) ([]ExtTrac } } -func (m *ExtensionManager) SearchTracksWithMetadataProviders(query string, limit int, includeExtensions bool) ([]ExtTrackMetadata, error) { +func (m *extensionManager) SearchTracksWithMetadataProviders(query string, limit int, includeExtensions bool) ([]ExtTrackMetadata, error) { priority := GetMetadataProviderPriority() if limit <= 0 { limit = 20 } - extensionProviders := make(map[string]*ExtensionProviderWrapper) + extensionProviders := make(map[string]*extensionProviderWrapper) if includeExtensions { for _, provider := range m.GetMetadataProviders() { extensionProviders[provider.extension.ID] = provider @@ -916,7 +916,7 @@ func (m *ExtensionManager) SearchTracksWithMetadataProviders(query string, limit func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, error) { priority := GetProviderPriority() - extManager := GetExtensionManager() + extManager := getExtensionManager() strictMode := !req.UseFallback selectedProvider := strings.TrimSpace(req.Service) @@ -971,7 +971,7 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro if err == nil && ext.Enabled && ext.Error == "" && ext.Manifest.IsMetadataProvider() { GoLog("[DownloadWithExtensionFallback] Enriching track from extension '%s'...\n", req.Source) - provider := NewExtensionProviderWrapper(ext) + provider := newExtensionProviderWrapper(ext) trackMeta := &ExtTrackMetadata{ ID: req.SpotifyID, Name: req.TrackName, @@ -1155,7 +1155,7 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro if err == nil && ext.Enabled && ext.Error == "" && ext.Manifest.IsDownloadProvider() { skipBuiltIn = ext.Manifest.SkipBuiltInFallback - provider := NewExtensionProviderWrapper(ext) + provider := newExtensionProviderWrapper(ext) trackID := req.SpotifyID @@ -1376,7 +1376,7 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro continue } - provider := NewExtensionProviderWrapper(ext) + provider := newExtensionProviderWrapper(ext) availability, err := provider.CheckAvailability(req.ISRC, req.TrackName, req.ArtistName, req.SpotifyID, req.DeezerID) if err != nil || !availability.Available { @@ -1657,7 +1657,7 @@ func buildOutputPath(req DownloadRequest) string { return filepath.Join(outputDir, filename+ext) } -func buildOutputPathForExtension(req DownloadRequest, ext *LoadedExtension) string { +func buildOutputPathForExtension(req DownloadRequest, ext *loadedExtension) string { if strings.TrimSpace(req.OutputPath) != "" { return strings.TrimSpace(req.OutputPath) } @@ -1703,7 +1703,7 @@ func buildOutputPathForExtension(req DownloadRequest, ext *LoadedExtension) stri return filepath.Join(tempDir, filename+outputExt) } -func (p *ExtensionProviderWrapper) CustomSearch(query string, options map[string]interface{}) ([]ExtTrackMetadata, error) { +func (p *extensionProviderWrapper) CustomSearch(query string, options map[string]interface{}) ([]ExtTrackMetadata, error) { if !p.extension.Manifest.HasCustomSearch() { return nil, fmt.Errorf("extension '%s' does not support custom search", p.extension.ID) } @@ -1785,7 +1785,7 @@ type ExtURLHandleResult struct { CoverURL string `json:"cover_url,omitempty"` } -func (p *ExtensionProviderWrapper) HandleURL(url string) (*ExtURLHandleResult, error) { +func (p *extensionProviderWrapper) HandleURL(url string) (*ExtURLHandleResult, error) { if !p.extension.Manifest.HasURLHandler() { return nil, fmt.Errorf("extension '%s' does not support URL handling", p.extension.ID) } @@ -1871,7 +1871,7 @@ type MatchTrackResult struct { Reason string `json:"reason,omitempty"` } -func (p *ExtensionProviderWrapper) MatchTrack(sourceTrack map[string]interface{}, candidates []map[string]interface{}) (*MatchTrackResult, error) { +func (p *extensionProviderWrapper) MatchTrack(sourceTrack map[string]interface{}, candidates []map[string]interface{}) (*MatchTrackResult, error) { if !p.extension.Manifest.HasCustomMatching() { return nil, fmt.Errorf("extension '%s' does not support custom matching", p.extension.ID) } @@ -1942,7 +1942,7 @@ type PostProcessInput struct { const PostProcessTimeout = 2 * time.Minute -func (p *ExtensionProviderWrapper) PostProcess(filePath string, metadata map[string]interface{}, hookID string) (*PostProcessResult, error) { +func (p *extensionProviderWrapper) PostProcess(filePath string, metadata map[string]interface{}, hookID string) (*PostProcessResult, error) { if !p.extension.Manifest.HasPostProcessing() { return nil, fmt.Errorf("extension '%s' does not support post-processing", p.extension.ID) } @@ -2005,7 +2005,7 @@ func (p *ExtensionProviderWrapper) PostProcess(filePath string, metadata map[str return &postResult, nil } -func (p *ExtensionProviderWrapper) PostProcessV2(input PostProcessInput, metadata map[string]interface{}, hookID string) (*PostProcessResult, error) { +func (p *extensionProviderWrapper) PostProcessV2(input PostProcessInput, metadata map[string]interface{}, hookID string) (*PostProcessResult, error) { if !p.extension.Manifest.HasPostProcessing() { return nil, fmt.Errorf("extension '%s' does not support post-processing", p.extension.ID) } @@ -2075,39 +2075,39 @@ func (p *ExtensionProviderWrapper) PostProcessV2(input PostProcessInput, metadat return &postResult, nil } -func (m *ExtensionManager) GetSearchProviders() []*ExtensionProviderWrapper { +func (m *extensionManager) GetSearchProviders() []*extensionProviderWrapper { m.mu.RLock() defer m.mu.RUnlock() - var providers []*ExtensionProviderWrapper + var providers []*extensionProviderWrapper for _, ext := range m.extensions { if ext.Enabled && ext.Manifest.HasCustomSearch() && ext.Error == "" { - providers = append(providers, NewExtensionProviderWrapper(ext)) + providers = append(providers, newExtensionProviderWrapper(ext)) } } return providers } -func (m *ExtensionManager) GetURLHandlers() []*ExtensionProviderWrapper { +func (m *extensionManager) GetURLHandlers() []*extensionProviderWrapper { m.mu.RLock() defer m.mu.RUnlock() - var providers []*ExtensionProviderWrapper + var providers []*extensionProviderWrapper for _, ext := range m.extensions { if ext.Enabled && ext.Manifest.HasURLHandler() && ext.Error == "" { - providers = append(providers, NewExtensionProviderWrapper(ext)) + providers = append(providers, newExtensionProviderWrapper(ext)) } } return providers } -func (m *ExtensionManager) FindURLHandler(url string) *ExtensionProviderWrapper { +func (m *extensionManager) FindURLHandler(url string) *extensionProviderWrapper { m.mu.RLock() defer m.mu.RUnlock() for _, ext := range m.extensions { if ext.Enabled && ext.Manifest.MatchesURL(url) && ext.Error == "" { - return NewExtensionProviderWrapper(ext) + return newExtensionProviderWrapper(ext) } } return nil @@ -2118,7 +2118,7 @@ type ExtURLHandleResultWithExtID struct { ExtensionID string } -func (m *ExtensionManager) HandleURLWithExtension(url string) (*ExtURLHandleResultWithExtID, error) { +func (m *extensionManager) HandleURLWithExtension(url string) (*ExtURLHandleResultWithExtID, error) { handler := m.FindURLHandler(url) if handler == nil { return nil, fmt.Errorf("no extension found to handle URL: %s", url) @@ -2138,20 +2138,20 @@ func (m *ExtensionManager) HandleURLWithExtension(url string) (*ExtURLHandleResu }, nil } -func (m *ExtensionManager) GetPostProcessingProviders() []*ExtensionProviderWrapper { +func (m *extensionManager) GetPostProcessingProviders() []*extensionProviderWrapper { m.mu.RLock() defer m.mu.RUnlock() - var providers []*ExtensionProviderWrapper + var providers []*extensionProviderWrapper for _, ext := range m.extensions { if ext.Enabled && ext.Manifest.HasPostProcessing() && ext.Error == "" { - providers = append(providers, NewExtensionProviderWrapper(ext)) + providers = append(providers, newExtensionProviderWrapper(ext)) } } return providers } -func (m *ExtensionManager) RunPostProcessing(filePath string, metadata map[string]interface{}) (*PostProcessResult, error) { +func (m *extensionManager) RunPostProcessing(filePath string, metadata map[string]interface{}) (*PostProcessResult, error) { providers := m.GetPostProcessingProviders() if len(providers) == 0 { return &PostProcessResult{Success: true, NewFilePath: filePath}, nil @@ -2196,7 +2196,7 @@ func (m *ExtensionManager) RunPostProcessing(filePath string, metadata map[strin return &PostProcessResult{Success: true, NewFilePath: currentPath}, nil } -func (m *ExtensionManager) RunPostProcessingV2(input PostProcessInput, metadata map[string]interface{}) (*PostProcessResult, error) { +func (m *extensionManager) RunPostProcessingV2(input PostProcessInput, metadata map[string]interface{}) (*PostProcessResult, error) { providers := m.GetPostProcessingProviders() if len(providers) == 0 { return &PostProcessResult{Success: true, NewFilePath: input.Path, NewFileURI: input.URI}, nil @@ -2264,7 +2264,7 @@ type ExtLyricsLine struct { EndTimeMs int64 `json:"endTimeMs"` } -func (p *ExtensionProviderWrapper) FetchLyrics(trackName, artistName, albumName string, durationSec float64) (*LyricsResponse, error) { +func (p *extensionProviderWrapper) FetchLyrics(trackName, artistName, albumName string, durationSec float64) (*LyricsResponse, error) { if !p.extension.Manifest.IsLyricsProvider() { return nil, fmt.Errorf("extension '%s' is not a lyrics provider", p.extension.ID) } @@ -2362,14 +2362,14 @@ func (p *ExtensionProviderWrapper) FetchLyrics(trackName, artistName, albumName return response, nil } -func (m *ExtensionManager) GetLyricsProviders() []*ExtensionProviderWrapper { +func (m *extensionManager) GetLyricsProviders() []*extensionProviderWrapper { m.mu.RLock() defer m.mu.RUnlock() - var providers []*ExtensionProviderWrapper + var providers []*extensionProviderWrapper for _, ext := range m.extensions { if ext.Enabled && ext.Manifest.IsLyricsProvider() && ext.Error == "" { - providers = append(providers, NewExtensionProviderWrapper(ext)) + providers = append(providers, newExtensionProviderWrapper(ext)) } } diff --git a/go_backend/extension_providers_test.go b/go_backend/extension_providers_test.go index 3112de44..5c59f962 100644 --- a/go_backend/extension_providers_test.go +++ b/go_backend/extension_providers_test.go @@ -51,7 +51,7 @@ func TestSearchTracksWithMetadataProvidersUsesPriorityAndDedupes(t *testing.T) { } } - manager := GetExtensionManager() + manager := getExtensionManager() tracks, err := manager.SearchTracksWithMetadataProviders("query", 3, false) if err != nil { t.Fatalf("SearchTracksWithMetadataProviders returned error: %v", err) diff --git a/go_backend/extension_runtime.go b/go_backend/extension_runtime.go index 7f2c0848..2bb18cf1 100644 --- a/go_backend/extension_runtime.go +++ b/go_backend/extension_runtime.go @@ -80,7 +80,7 @@ func SetExtensionTokens(extensionID string, accessToken, refreshToken string, ex state.IsAuthenticated = accessToken != "" } -type ExtensionRuntime struct { +type extensionRuntime struct { extensionID string manifest *ExtensionManifest settings map[string]interface{} @@ -123,10 +123,10 @@ var ( privateIPCacheMu sync.RWMutex ) -func NewExtensionRuntime(ext *LoadedExtension) *ExtensionRuntime { +func newExtensionRuntime(ext *loadedExtension) *extensionRuntime { jar, _ := newSimpleCookieJar() - runtime := &ExtensionRuntime{ + runtime := &extensionRuntime{ extensionID: ext.ID, manifest: ext.Manifest, settings: make(map[string]interface{}), @@ -142,25 +142,25 @@ func NewExtensionRuntime(ext *LoadedExtension) *ExtensionRuntime { return runtime } -func (r *ExtensionRuntime) setActiveDownloadItemID(itemID string) { +func (r *extensionRuntime) setActiveDownloadItemID(itemID string) { r.activeDownloadMu.Lock() defer r.activeDownloadMu.Unlock() r.activeDownloadItemID = strings.TrimSpace(itemID) } -func (r *ExtensionRuntime) clearActiveDownloadItemID() { +func (r *extensionRuntime) clearActiveDownloadItemID() { r.activeDownloadMu.Lock() defer r.activeDownloadMu.Unlock() r.activeDownloadItemID = "" } -func (r *ExtensionRuntime) getActiveDownloadItemID() string { +func (r *extensionRuntime) getActiveDownloadItemID() string { r.activeDownloadMu.RLock() defer r.activeDownloadMu.RUnlock() return r.activeDownloadItemID } -func newExtensionHTTPClient(ext *LoadedExtension, jar http.CookieJar, timeout time.Duration) *http.Client { +func newExtensionHTTPClient(ext *loadedExtension, jar http.CookieJar, timeout time.Duration) *http.Client { // Extension sandbox enforces HTTPS-only domains. Do not apply global // allow_http scheme downgrade here, because some extension APIs (e.g. // spotify-web) will redirect http -> https and can end up in 301 loops. @@ -329,11 +329,11 @@ func (j *simpleCookieJar) Cookies(u *url.URL) []*http.Cookie { return j.cookies[u.Host] } -func (r *ExtensionRuntime) SetSettings(settings map[string]interface{}) { +func (r *extensionRuntime) SetSettings(settings map[string]interface{}) { r.settings = settings } -func (r *ExtensionRuntime) RegisterAPIs(vm *goja.Runtime) { +func (r *extensionRuntime) RegisterAPIs(vm *goja.Runtime) { r.vm = vm httpObj := vm.NewObject() diff --git a/go_backend/extension_runtime_auth.go b/go_backend/extension_runtime_auth.go index d334c8b6..4be66422 100644 --- a/go_backend/extension_runtime_auth.go +++ b/go_backend/extension_runtime_auth.go @@ -52,7 +52,7 @@ func summarizeURLForLog(urlStr string) string { return fmt.Sprintf("%s://%s%s", parsed.Scheme, parsed.Host, parsed.Path) } -func (r *ExtensionRuntime) authOpenUrl(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) authOpenUrl(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return r.vm.ToValue(map[string]interface{}{ "success": false, @@ -99,7 +99,7 @@ func (r *ExtensionRuntime) authOpenUrl(call goja.FunctionCall) goja.Value { }) } -func (r *ExtensionRuntime) authGetCode(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) authGetCode(call goja.FunctionCall) goja.Value { extensionAuthStateMu.RLock() defer extensionAuthStateMu.RUnlock() @@ -111,7 +111,7 @@ func (r *ExtensionRuntime) authGetCode(call goja.FunctionCall) goja.Value { return r.vm.ToValue(state.AuthCode) } -func (r *ExtensionRuntime) authSetCode(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) authSetCode(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return r.vm.ToValue(false) } @@ -149,7 +149,7 @@ func (r *ExtensionRuntime) authSetCode(call goja.FunctionCall) goja.Value { return r.vm.ToValue(true) } -func (r *ExtensionRuntime) authClear(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) authClear(call goja.FunctionCall) goja.Value { extensionAuthStateMu.Lock() delete(extensionAuthState, r.extensionID) extensionAuthStateMu.Unlock() @@ -162,7 +162,7 @@ func (r *ExtensionRuntime) authClear(call goja.FunctionCall) goja.Value { return r.vm.ToValue(true) } -func (r *ExtensionRuntime) authIsAuthenticated(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) authIsAuthenticated(call goja.FunctionCall) goja.Value { extensionAuthStateMu.RLock() defer extensionAuthStateMu.RUnlock() @@ -178,7 +178,7 @@ func (r *ExtensionRuntime) authIsAuthenticated(call goja.FunctionCall) goja.Valu return r.vm.ToValue(state.IsAuthenticated) } -func (r *ExtensionRuntime) authGetTokens(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) authGetTokens(call goja.FunctionCall) goja.Value { extensionAuthStateMu.RLock() defer extensionAuthStateMu.RUnlock() @@ -228,7 +228,7 @@ func generatePKCEChallenge(verifier string) string { return base64.RawURLEncoding.EncodeToString(hash[:]) } -func (r *ExtensionRuntime) authGeneratePKCE(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) authGeneratePKCE(call goja.FunctionCall) goja.Value { length := 64 if len(call.Arguments) > 0 && !goja.IsUndefined(call.Arguments[0]) { if l, ok := call.Arguments[0].Export().(float64); ok && l >= 43 && l <= 128 { @@ -265,7 +265,7 @@ func (r *ExtensionRuntime) authGeneratePKCE(call goja.FunctionCall) goja.Value { }) } -func (r *ExtensionRuntime) authGetPKCE(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) authGetPKCE(call goja.FunctionCall) goja.Value { extensionAuthStateMu.RLock() defer extensionAuthStateMu.RUnlock() @@ -281,7 +281,7 @@ func (r *ExtensionRuntime) authGetPKCE(call goja.FunctionCall) goja.Value { }) } -func (r *ExtensionRuntime) authStartOAuthWithPKCE(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]interface{}{ "success": false, @@ -385,7 +385,7 @@ func (r *ExtensionRuntime) authStartOAuthWithPKCE(call goja.FunctionCall) goja.V }) } -func (r *ExtensionRuntime) authExchangeCodeWithPKCE(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) authExchangeCodeWithPKCE(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return r.vm.ToValue(map[string]interface{}{ "success": false, diff --git a/go_backend/extension_runtime_ffmpeg.go b/go_backend/extension_runtime_ffmpeg.go index 7c1e8d69..8269e919 100644 --- a/go_backend/extension_runtime_ffmpeg.go +++ b/go_backend/extension_runtime_ffmpeg.go @@ -50,7 +50,7 @@ func ClearFFmpegCommand(commandID string) { delete(ffmpegCommands, commandID) } -func (r *ExtensionRuntime) ffmpegExecute(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) ffmpegExecute(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return r.vm.ToValue(map[string]interface{}{ "success": false, @@ -107,7 +107,7 @@ func (r *ExtensionRuntime) ffmpegExecute(call goja.FunctionCall) goja.Value { } } -func (r *ExtensionRuntime) ffmpegGetInfo(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]interface{}{ "success": false, @@ -134,7 +134,7 @@ func (r *ExtensionRuntime) ffmpegGetInfo(call goja.FunctionCall) goja.Value { }) } -func (r *ExtensionRuntime) ffmpegConvert(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]interface{}{ "success": false, diff --git a/go_backend/extension_runtime_file.go b/go_backend/extension_runtime_file.go index 9e442aea..401f2944 100644 --- a/go_backend/extension_runtime_file.go +++ b/go_backend/extension_runtime_file.go @@ -71,7 +71,7 @@ func isPathWithinBase(baseDir, targetPath string) bool { return true } -func (r *ExtensionRuntime) validatePath(path string) (string, error) { +func (r *extensionRuntime) validatePath(path string) (string, error) { if !r.manifest.Permissions.File { return "", fmt.Errorf("file access denied: extension does not have 'file' permission") } @@ -106,7 +106,7 @@ func (r *ExtensionRuntime) validatePath(path string) (string, error) { return absPath, nil } -func (r *ExtensionRuntime) fileDownload(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) fileDownload(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 2 { return r.vm.ToValue(map[string]interface{}{ "success": false, @@ -271,7 +271,7 @@ func (r *ExtensionRuntime) fileDownload(call goja.FunctionCall) goja.Value { }) } -func (r *ExtensionRuntime) fileExists(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) fileExists(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return r.vm.ToValue(false) } @@ -286,7 +286,7 @@ func (r *ExtensionRuntime) fileExists(call goja.FunctionCall) goja.Value { return r.vm.ToValue(err == nil) } -func (r *ExtensionRuntime) fileDelete(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]interface{}{ "success": false, @@ -315,7 +315,7 @@ func (r *ExtensionRuntime) fileDelete(call goja.FunctionCall) goja.Value { }) } -func (r *ExtensionRuntime) fileRead(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) fileRead(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return r.vm.ToValue(map[string]interface{}{ "success": false, @@ -346,7 +346,7 @@ func (r *ExtensionRuntime) fileRead(call goja.FunctionCall) goja.Value { }) } -func (r *ExtensionRuntime) fileWrite(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]interface{}{ "success": false, @@ -386,7 +386,7 @@ func (r *ExtensionRuntime) fileWrite(call goja.FunctionCall) goja.Value { }) } -func (r *ExtensionRuntime) fileCopy(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]interface{}{ "success": false, @@ -459,7 +459,7 @@ func (r *ExtensionRuntime) fileCopy(call goja.FunctionCall) goja.Value { }) } -func (r *ExtensionRuntime) fileMove(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) fileMove(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 2 { return r.vm.ToValue(map[string]interface{}{ "success": false, @@ -507,7 +507,7 @@ func (r *ExtensionRuntime) fileMove(call goja.FunctionCall) goja.Value { }) } -func (r *ExtensionRuntime) fileGetSize(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) fileGetSize(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return r.vm.ToValue(map[string]interface{}{ "success": false, diff --git a/go_backend/extension_runtime_http.go b/go_backend/extension_runtime_http.go index 424a35bb..35552b3d 100644 --- a/go_backend/extension_runtime_http.go +++ b/go_backend/extension_runtime_http.go @@ -17,7 +17,7 @@ type HTTPResponse struct { Headers map[string]string `json:"headers"` } -func (r *ExtensionRuntime) validateDomain(urlStr string) error { +func (r *extensionRuntime) validateDomain(urlStr string) error { parsed, err := url.Parse(urlStr) if err != nil { return fmt.Errorf("invalid URL: %w", err) @@ -49,7 +49,7 @@ func (r *ExtensionRuntime) validateDomain(urlStr string) error { return nil } -func (r *ExtensionRuntime) httpGet(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) httpGet(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return r.vm.ToValue(map[string]interface{}{ "error": "URL is required", @@ -124,7 +124,7 @@ func (r *ExtensionRuntime) httpGet(call goja.FunctionCall) goja.Value { }) } -func (r *ExtensionRuntime) httpPost(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) httpPost(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return r.vm.ToValue(map[string]interface{}{ "error": "URL is required", @@ -221,7 +221,7 @@ func (r *ExtensionRuntime) httpPost(call goja.FunctionCall) goja.Value { }) } -func (r *ExtensionRuntime) httpRequest(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) httpRequest(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return r.vm.ToValue(map[string]interface{}{ "error": "URL is required", @@ -330,19 +330,19 @@ func (r *ExtensionRuntime) httpRequest(call goja.FunctionCall) goja.Value { }) } -func (r *ExtensionRuntime) httpPut(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) httpPut(call goja.FunctionCall) goja.Value { return r.httpMethodShortcut("PUT", call) } -func (r *ExtensionRuntime) httpDelete(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) httpDelete(call goja.FunctionCall) goja.Value { return r.httpMethodShortcut("DELETE", call) } -func (r *ExtensionRuntime) httpPatch(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) httpPatch(call goja.FunctionCall) goja.Value { return r.httpMethodShortcut("PATCH", call) } -func (r *ExtensionRuntime) httpMethodShortcut(method string, call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) httpMethodShortcut(method string, call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return r.vm.ToValue(map[string]interface{}{ "error": "URL is required", @@ -455,7 +455,7 @@ func (r *ExtensionRuntime) httpMethodShortcut(method string, call goja.FunctionC }) } -func (r *ExtensionRuntime) httpClearCookies(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) httpClearCookies(call goja.FunctionCall) goja.Value { if jar, ok := r.cookieJar.(*simpleCookieJar); ok { jar.mu.Lock() jar.cookies = make(map[string][]*http.Cookie) diff --git a/go_backend/extension_runtime_matching.go b/go_backend/extension_runtime_matching.go index 30b61e7b..883ee9cf 100644 --- a/go_backend/extension_runtime_matching.go +++ b/go_backend/extension_runtime_matching.go @@ -6,7 +6,7 @@ import ( "github.com/dop251/goja" ) -func (r *ExtensionRuntime) matchingCompareStrings(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) matchingCompareStrings(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 2 { return r.vm.ToValue(0.0) } @@ -22,7 +22,7 @@ func (r *ExtensionRuntime) matchingCompareStrings(call goja.FunctionCall) goja.V return r.vm.ToValue(similarity) } -func (r *ExtensionRuntime) matchingCompareDuration(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) matchingCompareDuration(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 2 { return r.vm.ToValue(false) } @@ -43,7 +43,7 @@ func (r *ExtensionRuntime) matchingCompareDuration(call goja.FunctionCall) goja. return r.vm.ToValue(diff <= tolerance) } -func (r *ExtensionRuntime) matchingNormalizeString(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) matchingNormalizeString(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return r.vm.ToValue("") } diff --git a/go_backend/extension_runtime_polyfills.go b/go_backend/extension_runtime_polyfills.go index 3068ace3..d70467aa 100644 --- a/go_backend/extension_runtime_polyfills.go +++ b/go_backend/extension_runtime_polyfills.go @@ -12,7 +12,7 @@ import ( "github.com/dop251/goja" ) -func (r *ExtensionRuntime) fetchPolyfill(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) fetchPolyfill(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return r.createFetchError("URL is required") } @@ -133,7 +133,7 @@ func (r *ExtensionRuntime) fetchPolyfill(call goja.FunctionCall) goja.Value { return responseObj } -func (r *ExtensionRuntime) createFetchError(message string) goja.Value { +func (r *extensionRuntime) createFetchError(message string) goja.Value { errorObj := r.vm.NewObject() errorObj.Set("ok", false) errorObj.Set("status", 0) @@ -148,7 +148,7 @@ func (r *ExtensionRuntime) createFetchError(message string) goja.Value { return errorObj } -func (r *ExtensionRuntime) atobPolyfill(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) atobPolyfill(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return r.vm.ToValue("") } @@ -164,7 +164,7 @@ func (r *ExtensionRuntime) atobPolyfill(call goja.FunctionCall) goja.Value { return r.vm.ToValue(string(decoded)) } -func (r *ExtensionRuntime) btoaPolyfill(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) btoaPolyfill(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return r.vm.ToValue("") } @@ -172,7 +172,7 @@ func (r *ExtensionRuntime) btoaPolyfill(call goja.FunctionCall) goja.Value { return r.vm.ToValue(base64.StdEncoding.EncodeToString([]byte(input))) } -func (r *ExtensionRuntime) registerTextEncoderDecoder(vm *goja.Runtime) { +func (r *extensionRuntime) registerTextEncoderDecoder(vm *goja.Runtime) { vm.Set("TextEncoder", func(call goja.ConstructorCall) *goja.Object { encoder := call.This encoder.Set("encoding", "utf-8") @@ -252,7 +252,7 @@ func (r *ExtensionRuntime) registerTextEncoderDecoder(vm *goja.Runtime) { }) } -func (r *ExtensionRuntime) registerURLClass(vm *goja.Runtime) { +func (r *extensionRuntime) registerURLClass(vm *goja.Runtime) { vm.Set("URL", func(call goja.ConstructorCall) *goja.Object { urlObj := call.This @@ -416,7 +416,7 @@ func (r *ExtensionRuntime) registerURLClass(vm *goja.Runtime) { }) } -func (r *ExtensionRuntime) registerJSONGlobal(vm *goja.Runtime) { +func (r *extensionRuntime) registerJSONGlobal(vm *goja.Runtime) { jsonScript := ` if (typeof JSON === 'undefined') { var JSON = { diff --git a/go_backend/extension_runtime_storage.go b/go_backend/extension_runtime_storage.go index 50815b40..541e67ba 100644 --- a/go_backend/extension_runtime_storage.go +++ b/go_backend/extension_runtime_storage.go @@ -21,7 +21,7 @@ const ( storageFlushRetryDelay = 2 * time.Second ) -func (r *ExtensionRuntime) getStoragePath() string { +func (r *extensionRuntime) getStoragePath() string { return filepath.Join(r.dataDir, "storage.json") } @@ -36,7 +36,7 @@ func cloneInterfaceMap(src map[string]interface{}) map[string]interface{} { return dst } -func (r *ExtensionRuntime) ensureStorageLoaded() error { +func (r *extensionRuntime) ensureStorageLoaded() error { r.storageMu.RLock() if r.storageLoaded { r.storageMu.RUnlock() @@ -74,7 +74,7 @@ func (r *ExtensionRuntime) ensureStorageLoaded() error { return nil } -func (r *ExtensionRuntime) loadStorage() (map[string]interface{}, error) { +func (r *extensionRuntime) loadStorage() (map[string]interface{}, error) { if err := r.ensureStorageLoaded(); err != nil { return nil, err } @@ -84,7 +84,7 @@ func (r *ExtensionRuntime) loadStorage() (map[string]interface{}, error) { return cloneInterfaceMap(r.storageCache), nil } -func (r *ExtensionRuntime) queueStorageFlushLocked(delay time.Duration) { +func (r *extensionRuntime) queueStorageFlushLocked(delay time.Duration) { if r.storageClosed { return } @@ -94,7 +94,7 @@ func (r *ExtensionRuntime) queueStorageFlushLocked(delay time.Duration) { r.storageTimer = time.AfterFunc(delay, r.flushStorageDirtyAsync) } -func (r *ExtensionRuntime) persistStorageSnapshot(storage map[string]interface{}) error { +func (r *extensionRuntime) persistStorageSnapshot(storage map[string]interface{}) error { data, err := json.Marshal(storage) if err != nil { return err @@ -106,13 +106,13 @@ func (r *ExtensionRuntime) persistStorageSnapshot(storage map[string]interface{} return os.WriteFile(r.getStoragePath(), data, 0600) } -func (r *ExtensionRuntime) flushStorageDirtyAsync() { +func (r *extensionRuntime) flushStorageDirtyAsync() { if err := r.flushStorageDirty(); err != nil { GoLog("[Extension:%s] Storage flush error: %v\n", r.extensionID, err) } } -func (r *ExtensionRuntime) flushStorageDirty() error { +func (r *extensionRuntime) flushStorageDirty() error { r.storageMu.Lock() if r.storageClosed { r.storageTimer = nil @@ -140,7 +140,7 @@ func (r *ExtensionRuntime) flushStorageDirty() error { return nil } -func (r *ExtensionRuntime) flushStorageNow() error { +func (r *extensionRuntime) flushStorageNow() error { r.storageMu.Lock() if r.storageTimer != nil { r.storageTimer.Stop() @@ -157,7 +157,7 @@ func (r *ExtensionRuntime) flushStorageNow() error { return r.persistStorageSnapshot(snapshot) } -func (r *ExtensionRuntime) closeStorageFlusher() { +func (r *extensionRuntime) closeStorageFlusher() { r.storageMu.Lock() r.storageClosed = true r.storageDirty = false @@ -168,7 +168,7 @@ func (r *ExtensionRuntime) closeStorageFlusher() { r.storageMu.Unlock() } -func (r *ExtensionRuntime) storageGet(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) storageGet(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return goja.Undefined() } @@ -193,7 +193,7 @@ func (r *ExtensionRuntime) storageGet(call goja.FunctionCall) goja.Value { return r.vm.ToValue(value) } -func (r *ExtensionRuntime) storageSet(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) storageSet(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 2 { return r.vm.ToValue(false) } @@ -225,7 +225,7 @@ func (r *ExtensionRuntime) storageSet(call goja.FunctionCall) goja.Value { return r.vm.ToValue(true) } -func (r *ExtensionRuntime) storageRemove(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) storageRemove(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return r.vm.ToValue(false) } @@ -254,15 +254,15 @@ func (r *ExtensionRuntime) storageRemove(call goja.FunctionCall) goja.Value { return r.vm.ToValue(true) } -func (r *ExtensionRuntime) getCredentialsPath() string { +func (r *extensionRuntime) getCredentialsPath() string { return filepath.Join(r.dataDir, ".credentials.enc") } -func (r *ExtensionRuntime) getSaltPath() string { +func (r *extensionRuntime) getSaltPath() string { return filepath.Join(r.dataDir, ".cred_salt") } -func (r *ExtensionRuntime) getOrCreateSalt() ([]byte, error) { +func (r *extensionRuntime) getOrCreateSalt() ([]byte, error) { saltPath := r.getSaltPath() salt, err := os.ReadFile(saltPath) @@ -282,7 +282,7 @@ func (r *ExtensionRuntime) getOrCreateSalt() ([]byte, error) { return salt, nil } -func (r *ExtensionRuntime) getEncryptionKey() ([]byte, error) { +func (r *extensionRuntime) getEncryptionKey() ([]byte, error) { salt, err := r.getOrCreateSalt() if err != nil { return nil, err @@ -293,7 +293,7 @@ func (r *ExtensionRuntime) getEncryptionKey() ([]byte, error) { return hash[:], nil } -func (r *ExtensionRuntime) ensureCredentialsLoaded() error { +func (r *extensionRuntime) ensureCredentialsLoaded() error { r.credentialsMu.RLock() if r.credentialsLoaded { r.credentialsMu.RUnlock() @@ -340,7 +340,7 @@ func (r *ExtensionRuntime) ensureCredentialsLoaded() error { return nil } -func (r *ExtensionRuntime) loadCredentials() (map[string]interface{}, error) { +func (r *extensionRuntime) loadCredentials() (map[string]interface{}, error) { if err := r.ensureCredentialsLoaded(); err != nil { return nil, err } @@ -350,7 +350,7 @@ func (r *ExtensionRuntime) loadCredentials() (map[string]interface{}, error) { return cloneInterfaceMap(r.credentialsCache), nil } -func (r *ExtensionRuntime) saveCredentials(creds map[string]interface{}) error { +func (r *extensionRuntime) saveCredentials(creds map[string]interface{}) error { data, err := json.Marshal(creds) if err != nil { return err @@ -377,7 +377,7 @@ func (r *ExtensionRuntime) saveCredentials(creds map[string]interface{}) error { return nil } -func (r *ExtensionRuntime) credentialsStore(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) credentialsStore(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 2 { return r.vm.ToValue(map[string]interface{}{ "success": false, @@ -414,7 +414,7 @@ func (r *ExtensionRuntime) credentialsStore(call goja.FunctionCall) goja.Value { }) } -func (r *ExtensionRuntime) credentialsGet(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) credentialsGet(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return goja.Undefined() } @@ -439,7 +439,7 @@ func (r *ExtensionRuntime) credentialsGet(call goja.FunctionCall) goja.Value { return r.vm.ToValue(value) } -func (r *ExtensionRuntime) credentialsRemove(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) credentialsRemove(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return r.vm.ToValue(false) } @@ -464,7 +464,7 @@ func (r *ExtensionRuntime) credentialsRemove(call goja.FunctionCall) goja.Value return r.vm.ToValue(true) } -func (r *ExtensionRuntime) credentialsHas(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) credentialsHas(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return r.vm.ToValue(false) } diff --git a/go_backend/extension_runtime_storage_test.go b/go_backend/extension_runtime_storage_test.go index dad6781b..433ade6b 100644 --- a/go_backend/extension_runtime_storage_test.go +++ b/go_backend/extension_runtime_storage_test.go @@ -11,7 +11,7 @@ import ( "github.com/dop251/goja" ) -func setStorageValue(t *testing.T, runtime *ExtensionRuntime, key string, value interface{}) { +func setStorageValue(t *testing.T, runtime *extensionRuntime, key string, value interface{}) { t.Helper() result := runtime.storageSet(goja.FunctionCall{ Arguments: []goja.Value{ @@ -39,7 +39,7 @@ func readStorageMap(t *testing.T, storagePath string) map[string]interface{} { } func TestExtensionRuntimeStorage_DebouncedWriteCompactJSON(t *testing.T) { - ext := &LoadedExtension{ + ext := &loadedExtension{ ID: "storage-test", Manifest: &ExtensionManifest{ Name: "storage-test", @@ -47,7 +47,7 @@ func TestExtensionRuntimeStorage_DebouncedWriteCompactJSON(t *testing.T) { DataDir: t.TempDir(), } - runtime := NewExtensionRuntime(ext) + runtime := newExtensionRuntime(ext) runtime.storageFlushDelay = 25 * time.Millisecond runtime.RegisterAPIs(goja.New()) @@ -86,7 +86,7 @@ func TestExtensionRuntimeStorage_DebouncedWriteCompactJSON(t *testing.T) { } func TestUnloadExtension_FlushesPendingStorage(t *testing.T) { - ext := &LoadedExtension{ + ext := &loadedExtension{ ID: "unload-storage-test", Manifest: &ExtensionManifest{ Name: "unload-storage-test", @@ -95,13 +95,13 @@ func TestUnloadExtension_FlushesPendingStorage(t *testing.T) { VM: goja.New(), } - runtime := NewExtensionRuntime(ext) + runtime := newExtensionRuntime(ext) runtime.storageFlushDelay = time.Hour runtime.RegisterAPIs(ext.VM) ext.runtime = runtime - manager := &ExtensionManager{ - extensions: map[string]*LoadedExtension{ + manager := &extensionManager{ + extensions: map[string]*loadedExtension{ ext.ID: ext, }, } diff --git a/go_backend/extension_runtime_utils.go b/go_backend/extension_runtime_utils.go index f91918ff..ede6c4b4 100644 --- a/go_backend/extension_runtime_utils.go +++ b/go_backend/extension_runtime_utils.go @@ -16,7 +16,7 @@ import ( "github.com/dop251/goja" ) -func (r *ExtensionRuntime) base64Encode(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) base64Encode(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return r.vm.ToValue("") } @@ -24,7 +24,7 @@ func (r *ExtensionRuntime) base64Encode(call goja.FunctionCall) goja.Value { return r.vm.ToValue(base64.StdEncoding.EncodeToString([]byte(input))) } -func (r *ExtensionRuntime) base64Decode(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) base64Decode(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return r.vm.ToValue("") } @@ -36,7 +36,7 @@ func (r *ExtensionRuntime) base64Decode(call goja.FunctionCall) goja.Value { return r.vm.ToValue(string(decoded)) } -func (r *ExtensionRuntime) md5Hash(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) md5Hash(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return r.vm.ToValue("") } @@ -45,7 +45,7 @@ func (r *ExtensionRuntime) md5Hash(call goja.FunctionCall) goja.Value { return r.vm.ToValue(hex.EncodeToString(hash[:])) } -func (r *ExtensionRuntime) sha256Hash(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) sha256Hash(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return r.vm.ToValue("") } @@ -54,7 +54,7 @@ func (r *ExtensionRuntime) sha256Hash(call goja.FunctionCall) goja.Value { return r.vm.ToValue(hex.EncodeToString(hash[:])) } -func (r *ExtensionRuntime) hmacSHA256(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) hmacSHA256(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 2 { return r.vm.ToValue("") } @@ -66,7 +66,7 @@ func (r *ExtensionRuntime) hmacSHA256(call goja.FunctionCall) goja.Value { return r.vm.ToValue(hex.EncodeToString(mac.Sum(nil))) } -func (r *ExtensionRuntime) hmacSHA256Base64(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) hmacSHA256Base64(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 2 { return r.vm.ToValue("") } @@ -78,7 +78,7 @@ func (r *ExtensionRuntime) hmacSHA256Base64(call goja.FunctionCall) goja.Value { return r.vm.ToValue(base64.StdEncoding.EncodeToString(mac.Sum(nil))) } -func (r *ExtensionRuntime) hmacSHA1(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) hmacSHA1(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 2 { return r.vm.ToValue([]byte{}) } @@ -130,7 +130,7 @@ func (r *ExtensionRuntime) hmacSHA1(call goja.FunctionCall) goja.Value { return r.vm.ToValue(jsArray) } -func (r *ExtensionRuntime) parseJSON(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) parseJSON(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return goja.Undefined() } @@ -145,7 +145,7 @@ func (r *ExtensionRuntime) parseJSON(call goja.FunctionCall) goja.Value { return r.vm.ToValue(result) } -func (r *ExtensionRuntime) stringifyJSON(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) stringifyJSON(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return r.vm.ToValue("") } @@ -160,7 +160,7 @@ func (r *ExtensionRuntime) stringifyJSON(call goja.FunctionCall) goja.Value { return r.vm.ToValue(string(data)) } -func (r *ExtensionRuntime) cryptoEncrypt(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]interface{}{ "success": false, @@ -187,7 +187,7 @@ func (r *ExtensionRuntime) cryptoEncrypt(call goja.FunctionCall) goja.Value { }) } -func (r *ExtensionRuntime) cryptoDecrypt(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) cryptoDecrypt(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 2 { return r.vm.ToValue(map[string]interface{}{ "success": false, @@ -222,7 +222,7 @@ func (r *ExtensionRuntime) cryptoDecrypt(call goja.FunctionCall) goja.Value { }) } -func (r *ExtensionRuntime) cryptoGenerateKey(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) cryptoGenerateKey(call goja.FunctionCall) goja.Value { length := 32 if len(call.Arguments) > 0 && !goja.IsUndefined(call.Arguments[0]) { if l, ok := call.Arguments[0].Export().(float64); ok { @@ -245,35 +245,35 @@ func (r *ExtensionRuntime) cryptoGenerateKey(call goja.FunctionCall) goja.Value }) } -func (r *ExtensionRuntime) randomUserAgent(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) randomUserAgent(call goja.FunctionCall) goja.Value { return r.vm.ToValue(getRandomUserAgent()) } -func (r *ExtensionRuntime) logDebug(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) logDebug(call goja.FunctionCall) goja.Value { msg := r.formatLogArgs(call.Arguments) GoLog("[Extension:%s:DEBUG] %s\n", r.extensionID, msg) return goja.Undefined() } -func (r *ExtensionRuntime) logInfo(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) logInfo(call goja.FunctionCall) goja.Value { msg := r.formatLogArgs(call.Arguments) GoLog("[Extension:%s:INFO] %s\n", r.extensionID, msg) return goja.Undefined() } -func (r *ExtensionRuntime) logWarn(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) logWarn(call goja.FunctionCall) goja.Value { msg := r.formatLogArgs(call.Arguments) GoLog("[Extension:%s:WARN] %s\n", r.extensionID, msg) return goja.Undefined() } -func (r *ExtensionRuntime) logError(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) logError(call goja.FunctionCall) goja.Value { msg := r.formatLogArgs(call.Arguments) GoLog("[Extension:%s:ERROR] %s\n", r.extensionID, msg) return goja.Undefined() } -func (r *ExtensionRuntime) formatLogArgs(args []goja.Value) string { +func (r *extensionRuntime) formatLogArgs(args []goja.Value) string { parts := make([]string, len(args)) for i, arg := range args { parts[i] = fmt.Sprintf("%v", arg.Export()) @@ -281,7 +281,7 @@ func (r *ExtensionRuntime) formatLogArgs(args []goja.Value) string { return strings.Join(parts, " ") } -func (r *ExtensionRuntime) sanitizeFilenameWrapper(call goja.FunctionCall) goja.Value { +func (r *extensionRuntime) sanitizeFilenameWrapper(call goja.FunctionCall) goja.Value { if len(call.Arguments) < 1 { return r.vm.ToValue("") } @@ -289,7 +289,7 @@ func (r *ExtensionRuntime) sanitizeFilenameWrapper(call goja.FunctionCall) goja. return r.vm.ToValue(sanitizeFilename(input)) } -func (r *ExtensionRuntime) RegisterGoBackendAPIs(vm *goja.Runtime) { +func (r *extensionRuntime) RegisterGoBackendAPIs(vm *goja.Runtime) { gobackendObj := vm.Get("gobackend") if gobackendObj == nil || goja.IsUndefined(gobackendObj) { gobackendObj = vm.NewObject() diff --git a/go_backend/extension_store.go b/go_backend/extension_store.go index e5daa0cf..be8cce8b 100644 --- a/go_backend/extension_store.go +++ b/go_backend/extension_store.go @@ -305,7 +305,7 @@ func (s *extensionStore) getExtensionsWithStatus(forceRefresh bool) ([]storeExte return nil, err } - manager := GetExtensionManager() + manager := getExtensionManager() installed := make(map[string]string) // id -> version if manager != nil { diff --git a/go_backend/extension_test.go b/go_backend/extension_test.go index 1b9df2c8..75cde774 100644 --- a/go_backend/extension_test.go +++ b/go_backend/extension_test.go @@ -99,7 +99,7 @@ func TestIsDomainAllowed(t *testing.T) { func TestExtensionRuntime_NetworkSandbox(t *testing.T) { // Create a mock extension with limited network permissions - ext := &LoadedExtension{ + ext := &loadedExtension{ ID: "test-ext", Manifest: &ExtensionManifest{ Name: "test-ext", @@ -110,7 +110,7 @@ func TestExtensionRuntime_NetworkSandbox(t *testing.T) { DataDir: t.TempDir(), } - runtime := NewExtensionRuntime(ext) + runtime := newExtensionRuntime(ext) if err := runtime.validateDomain("https://api.allowed.com/path"); err != nil { t.Errorf("Expected api.allowed.com to be allowed, got error: %v", err) @@ -132,7 +132,7 @@ func TestExtensionRuntime_NetworkSandbox(t *testing.T) { func TestExtensionRuntime_FileSandbox(t *testing.T) { tempDir := t.TempDir() - ext := &LoadedExtension{ + ext := &loadedExtension{ ID: "test-ext", Manifest: &ExtensionManifest{ Name: "test-ext", @@ -143,7 +143,7 @@ func TestExtensionRuntime_FileSandbox(t *testing.T) { DataDir: tempDir, } - runtime := NewExtensionRuntime(ext) + runtime := newExtensionRuntime(ext) validPath, err := runtime.validatePath("test.txt") if err != nil { @@ -177,7 +177,7 @@ func TestExtensionRuntime_FileSandbox(t *testing.T) { t.Error("Expected absolute path to be blocked") } - extNoFile := &LoadedExtension{ + extNoFile := &loadedExtension{ ID: "test-ext-no-file", Manifest: &ExtensionManifest{ Name: "test-ext-no-file", @@ -187,7 +187,7 @@ func TestExtensionRuntime_FileSandbox(t *testing.T) { }, DataDir: tempDir, } - runtimeNoFile := NewExtensionRuntime(extNoFile) + runtimeNoFile := newExtensionRuntime(extNoFile) _, err = runtimeNoFile.validatePath("test.txt") if err == nil { t.Error("Expected file access to be denied without file permission") @@ -195,7 +195,7 @@ func TestExtensionRuntime_FileSandbox(t *testing.T) { } func TestExtensionRuntime_UtilityFunctions(t *testing.T) { - ext := &LoadedExtension{ + ext := &loadedExtension{ ID: "test-ext", Manifest: &ExtensionManifest{ Name: "test-ext", @@ -203,7 +203,7 @@ func TestExtensionRuntime_UtilityFunctions(t *testing.T) { DataDir: t.TempDir(), } - runtime := NewExtensionRuntime(ext) + runtime := newExtensionRuntime(ext) vm := goja.New() runtime.RegisterAPIs(vm) @@ -243,7 +243,7 @@ func TestExtensionRuntime_UtilityFunctions(t *testing.T) { func TestExtensionRuntime_SSRFProtection(t *testing.T) { // Create extension with limited network permissions - ext := &LoadedExtension{ + ext := &loadedExtension{ ID: "test-ext", Manifest: &ExtensionManifest{ Name: "test-ext", @@ -254,7 +254,7 @@ func TestExtensionRuntime_SSRFProtection(t *testing.T) { DataDir: t.TempDir(), } - runtime := NewExtensionRuntime(ext) + runtime := newExtensionRuntime(ext) privateIPs := []string{ "http://localhost/admin", diff --git a/go_backend/extension_timeout.go b/go_backend/extension_timeout.go index 78f3d2db..d3fbf876 100644 --- a/go_backend/extension_timeout.go +++ b/go_backend/extension_timeout.go @@ -53,7 +53,7 @@ func RunWithTimeout(vm *goja.Runtime, script string, timeout time.Duration) (goj IsTimeout: true, }} } else { - GoLog("[ExtensionRuntime] panic during JS execution: %v\n%s\n", r, string(debug.Stack())) + GoLog("[extensionRuntime] panic during JS execution: %v\n%s\n", r, string(debug.Stack())) resultCh <- result{nil, fmt.Errorf("panic during execution: %v", r)} } } @@ -90,7 +90,7 @@ func RunWithTimeout(vm *goja.Runtime, script string, timeout time.Duration) (goj case <-time.After(60 * time.Second): // Goroutine is truly stuck (e.g. HTTP read with no timeout). // Log a warning — the VM should NOT be reused after this. - GoLog("[ExtensionRuntime] WARNING: JS goroutine did not exit within 60s after interrupt, VM may be unsafe\n") + GoLog("[extensionRuntime] WARNING: JS goroutine did not exit within 60s after interrupt, VM may be unsafe\n") return nil, &JSExecutionError{ Message: "execution timeout exceeded (force)", IsTimeout: true, diff --git a/go_backend/lyrics.go b/go_backend/lyrics.go index a00849b7..ce065d4c 100644 --- a/go_backend/lyrics.go +++ b/go_backend/lyrics.go @@ -385,8 +385,8 @@ func (c *LyricsClient) FetchLyricsAllSources(spotifyID, trackName, artistName st primaryArtist := normalizeArtistName(artistName) fetchOptions := GetLyricsFetchOptions() - extManager := GetExtensionManager() - var extensionProviders []*ExtensionProviderWrapper + extManager := getExtensionManager() + var extensionProviders []*extensionProviderWrapper if extManager != nil { extensionProviders = extManager.GetLyricsProviders() } diff --git a/lib/services/ffmpeg_service.dart b/lib/services/ffmpeg_service.dart index ec2f05ce..8160a1ca 100644 --- a/lib/services/ffmpeg_service.dart +++ b/lib/services/ffmpeg_service.dart @@ -1311,28 +1311,38 @@ class FFmpegService { cmdBuffer.write('-v error -hide_banner '); cmdBuffer.write('-i "$m4aPath" '); - final hasCover = coverPath != null && await File(coverPath).exists(); + final normalizedCoverPath = coverPath?.trim(); + final hasCover = + normalizedCoverPath != null && + normalizedCoverPath.isNotEmpty && + await File(normalizedCoverPath).exists(); if (hasCover) { - cmdBuffer.write('-i "$coverPath" '); + cmdBuffer.write('-i "$normalizedCoverPath" '); } - cmdBuffer.write('-map 0:a '); + final preserveExistingStreams = preserveMetadata && !hasCover; + if (preserveExistingStreams) { + // When no replacement cover is provided, preserve all input streams so + // the existing attached artwork is not dropped during the metadata rewrite. + cmdBuffer.write('-map 0 -c copy '); + } else { + cmdBuffer.write('-map 0:a -c:a copy '); + } cmdBuffer.write( preserveMetadata ? '-map_metadata 0 ' : '-map_metadata -1 ', ); - // For M4A/MP4, cover art is mapped as a video stream and stored in the - // 'covr' atom automatically by FFmpeg. The '-disposition attached_pic' - // flag is only valid for Matroska/WebM containers and must NOT be used here. - // Force the mp4 muxer when cover art is present because the default ipod - // muxer (auto-selected for .m4a) does not register a codec tag for mjpeg, - // causing "codec not currently supported in container" on FFmpeg 8.0+. + // For M4A cover replacements, mark the image as an attached picture so the + // mp4 muxer writes a proper covr atom instead of a generic MJPEG video track. + // Force the mp4 muxer because the default ipod muxer (auto-selected for .m4a) + // does not register a codec tag for mjpeg on FFmpeg 8.0+. if (hasCover) { - cmdBuffer.write('-map 1:v -c:v copy -f mp4 '); + cmdBuffer.write('-map 1:v -c:v copy -disposition:v:0 attached_pic '); + cmdBuffer.write('-metadata:s:v title="Album cover" '); + cmdBuffer.write('-metadata:s:v comment="Cover (front)" '); + cmdBuffer.write('-f mp4 '); } - cmdBuffer.write('-c:a copy '); - if (metadata != null) { final m4aMetadata = _convertToM4aTags(metadata); for (final entry in m4aMetadata.entries) {