Implement InjectionService for Vivaldi Mod Manager HTML injection#15
Conversation
…nd full unit tests Co-authored-by: tomasforsman <39048588+tomasforsman@users.noreply.github.com>
…alidation fixes Co-authored-by: tomasforsman <39048588+tomasforsman@users.noreply.github.com>
There was a problem hiding this comment.
Pull Request Overview
This PR implements the core InjectionService for the Vivaldi Mod Manager, providing complete functionality for injecting mod loader stubs into Vivaldi HTML files. The implementation enables the final component in the mod management workflow chain: Detection → Generation → Injection → Execution.
Key changes:
- Complete
InjectionServiceimplementation with fingerprint-based tamper detection - Atomic file operations with backup/restore capabilities
- Comprehensive status tracking and validation system
Reviewed Changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/VivaldiModManager.Core/Services/IInjectionService.cs | Service interface defining injection management contract |
| src/VivaldiModManager.Core/Services/InjectionService.cs | Full implementation with Windows platform targeting and comprehensive injection functionality |
| src/VivaldiModManager.Core/Models/InjectionStatus.cs | Status tracking models for detailed injection validation |
| src/VivaldiModManager.Core/Exceptions/InjectionExceptions.cs | Specialized exception hierarchy for injection operations |
| src/VivaldiModManager.Core/Constants/ManifestConstants.cs | Added injection-related constants |
| tests/VivaldiModManager.Core.Tests/Services/InjectionServiceTests.cs | Unit tests with 22 test methods covering all scenarios |
| tests/VivaldiModManager.Core.Tests/Services/InjectionServiceIntegrationTests.cs | Integration tests validating real service interactions |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| /// <param name="cancellationToken">A cancellation token.</param> | ||
| /// <returns>The validation status for the target.</returns> | ||
| private async Task<InjectionTargetStatus> ValidateTargetInjectionAsync(string targetPath, string? expectedFingerprint, CancellationToken cancellationToken) | ||
| { | ||
| var status = new InjectionTargetStatus | ||
| { | ||
| FilePath = targetPath, | ||
| HasBackup = File.Exists(GenerateBackupPath(targetPath, "")) |
There was a problem hiding this comment.
The GenerateBackupPath method is called with an empty string for installationId, but the method uses this parameter to generate unique backup file names. This will result in incorrect backup path validation since the actual backup files are created with the installation's ID.
| /// <param name="cancellationToken">A cancellation token.</param> | |
| /// <returns>The validation status for the target.</returns> | |
| private async Task<InjectionTargetStatus> ValidateTargetInjectionAsync(string targetPath, string? expectedFingerprint, CancellationToken cancellationToken) | |
| { | |
| var status = new InjectionTargetStatus | |
| { | |
| FilePath = targetPath, | |
| HasBackup = File.Exists(GenerateBackupPath(targetPath, "")) | |
| /// <param name="installationId">The installation ID used for backup file naming.</param> | |
| /// <param name="cancellationToken">A cancellation token.</param> | |
| /// <returns>The validation status for the target.</returns> | |
| private async Task<InjectionTargetStatus> ValidateTargetInjectionAsync(string targetPath, string? expectedFingerprint, string installationId, CancellationToken cancellationToken) | |
| { | |
| var status = new InjectionTargetStatus | |
| { | |
| FilePath = targetPath, | |
| HasBackup = File.Exists(GenerateBackupPath(targetPath, installationId)) |
| var htmlWithInjection = @"<html><head></head><body> | ||
| <script type=""module""> | ||
| /* Vivaldi Mod Manager - Injection Stub v1.0 */ | ||
| /* Fingerprint: abcd1234 */ | ||
| /* Generated: 2025-01-21T10:30:00Z */ | ||
| (async function() { | ||
| try { | ||
| const loaderPath = './vivaldi-mods/loader.js'; | ||
| await import(loaderPath); | ||
| console.log('Vivaldi Mod Manager: Mods loaded successfully'); | ||
| } catch (error) { | ||
| console.error('Vivaldi Mod Manager: Failed to load mods:', error); | ||
| } | ||
| })(); | ||
| </script> |
There was a problem hiding this comment.
[nitpick] This hardcoded HTML string should use the same GenerateInjectionStub method that the production code uses to ensure consistency between tests and implementation. Consider creating the HTML dynamically using the actual injection method.
| var htmlWithInjection = @"<html><head></head><body> | |
| <script type=""module""> | |
| /* Vivaldi Mod Manager - Injection Stub v1.0 */ | |
| /* Fingerprint: abcd1234 */ | |
| /* Generated: 2025-01-21T10:30:00Z */ | |
| (async function() { | |
| try { | |
| const loaderPath = './vivaldi-mods/loader.js'; | |
| await import(loaderPath); | |
| console.log('Vivaldi Mod Manager: Mods loaded successfully'); | |
| } catch (error) { | |
| console.error('Vivaldi Mod Manager: Failed to load mods:', error); | |
| } | |
| })(); | |
| </script> | |
| var fingerprint = "abcd1234"; | |
| var generatedStub = InjectionService.GenerateInjectionStub(fingerprint, "./vivaldi-mods/loader.js", DateTime.Parse("2025-01-21T10:30:00Z")); | |
| var htmlWithInjection = $@"<html><head></head><body> | |
| {generatedStub} |
This PR implements the core
InjectionServicethat enables injecting mod loader stubs into Vivaldi HTML files, completing the mod management workflow chain: Detection → Generation → Injection → Execution.Summary
The
InjectionServiceprovides a complete solution for safely injecting minimal JavaScript stubs into Vivaldi'swindow.htmlandbrowser.htmlfiles, enabling dynamic mod loading while maintaining file integrity and providing rollback capabilities.Key Features
🎯 Minimal JavaScript Injection
Injects clean, lightweight stub code (~200 bytes) that dynamically imports the generated loader:
🔐 Fingerprint-Based Tamper Detection
Each injection includes a unique fingerprint generated from installation context and timestamp, enabling detection of:
🛡️ Atomic Operations with Rollback
All file operations use atomic writes with temporary files and proper cleanup:
📊 Comprehensive Status Tracking
Provides detailed injection status with validation:
Implementation Details
Core Components Added
IInjectionService- Interface defining injection management contractInjectionService- Full implementation with Windows platform targetingInjectionStatus- Detailed status model with per-target validationInjectionExceptionhierarchy - Specialized exception handlingService Integration
FindInjectionTargetsAsync()to locate HTML filesSafety Features
Testing
Comprehensive Test Coverage
HashServiceTest Scenarios Covered
Usage Example
Breaking Changes
None - This is a new service implementation that doesn't modify existing APIs.
Files Changed
src/VivaldiModManager.Core/Services/IInjectionService.cs- Service interfacesrc/VivaldiModManager.Core/Services/InjectionService.cs- Implementationsrc/VivaldiModManager.Core/Models/InjectionStatus.cs- Status tracking modelssrc/VivaldiModManager.Core/Exceptions/InjectionExceptions.cs- Exception hierarchysrc/VivaldiModManager.Core/Constants/ManifestConstants.cs- Added injection constantstests/VivaldiModManager.Core.Tests/Services/InjectionServiceTests.cs- Unit teststests/VivaldiModManager.Core.Tests/Services/InjectionServiceIntegrationTests.cs- Integration testsCloses #[issue-number]
Original prompt
This section details on the original issue you should resolve
<issue_title>[Feature]: Implement Injection Service</issue_title>
<issue_description># [Feature]: Implement Injection Service
Task Description
Implement the
IInjectionServiceandInjectionServiceclasses in the Core library to handle the injection of mod loader stubs into Vivaldi HTML files. This service is the critical component that bridges the gap between loader generation and actual mod execution, enabling the system to deploy generated JavaScript into Vivaldi's browser interface.The injection service should:
Expected Outcome
After completion, the application should have:
IInjectionServiceinterface defining the injection management contractInjectionServiceimplementation with full injection and restoration functionalityVivaldiServicefor target file discoveryLoaderServicefor deploying generated loadersRelevant Context
Core Requirements
src/VivaldiModManager.Core/Services/IVivaldiService,ILoaderService,IHashService,ILogger<InjectionService>VivaldiInstallation,ManifestDatamodelsKey Functionality Needed
Injection Operations
Service Methods Required
InjectAsync(VivaldiInstallation installation, string loaderPath)- Inject stub into all targetsRemoveInjectionAsync(VivaldiInstallation installation)- Remove stub from all targetsValidateInjectionAsync(VivaldiInstallation installation)- Check injection integrityBackupTargetFilesAsync(VivaldiInstallation installation)- Backup original HTML filesRestoreTargetFilesAsync(VivaldiInstallation installation)- Restore from backupsGetInjectionStatusAsync(VivaldiInstallation installation)- Get current injection stateRepairInjectionAsync(VivaldiInstallation installation, string loaderPath)- Fix broken injectionsIntegration Points
IVivaldiService.FindInjectionTargetsAsync()to locate HTML filesILoaderService.GenerateLoaderAsync()to create loader.js in target directoryIHashServicefor fingerprint generation and content integrityVivaldiInstallation.LastInjectionAt,LastInjectionStatus,InjectionFingerprintTechnical Requirements
Stub Injection Strategy
Fingerprint System
File System Operations
Backup Management
Er...
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.