Skip to content

Implement InjectionService for Vivaldi Mod Manager HTML injection#15

Merged
tomasforsman merged 3 commits intomainfrom
copilot/fix-74ddf8e6-df79-4be6-b1b4-8c07edbfc2a1
Sep 25, 2025
Merged

Implement InjectionService for Vivaldi Mod Manager HTML injection#15
tomasforsman merged 3 commits intomainfrom
copilot/fix-74ddf8e6-df79-4be6-b1b4-8c07edbfc2a1

Conversation

Copy link
Contributor

Copilot AI commented Sep 25, 2025

This PR implements the core InjectionService that enables injecting mod loader stubs into Vivaldi HTML files, completing the mod management workflow chain: Detection → Generation → Injection → Execution.

Summary

The InjectionService provides a complete solution for safely injecting minimal JavaScript stubs into Vivaldi's window.html and browser.html files, 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:

<script type="module">
/* Vivaldi Mod Manager - Injection Stub v1.0 */
/* Fingerprint: abcd1234efgh5678 */
/* Generated: 2025-01-21T15: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>

🔐 Fingerprint-Based Tamper Detection

Each injection includes a unique fingerprint generated from installation context and timestamp, enabling detection of:

  • Manual removal of injections
  • Corruption or modification of injected code
  • Version mismatches after Vivaldi updates

🛡️ Atomic Operations with Rollback

All file operations use atomic writes with temporary files and proper cleanup:

  • Creates backups before any modification
  • Supports partial injection recovery (some targets succeed, others fail)
  • Complete rollback capability on injection failure
  • Handles file locking and permission issues gracefully

📊 Comprehensive Status Tracking

Provides detailed injection status with validation:

var status = await injectionService.GetInjectionStatusAsync(installation);
// Returns: IsInjected, ValidationStatus, InjectedTargetCount, 
//          ValidationErrors, per-target status, etc.

Implementation Details

Core Components Added

  • IInjectionService - Interface defining injection management contract
  • InjectionService - Full implementation with Windows platform targeting
  • InjectionStatus - Detailed status model with per-target validation
  • InjectionException hierarchy - Specialized exception handling

Service Integration

  • VivaldiService: Uses FindInjectionTargetsAsync() to locate HTML files
  • LoaderService: Coordinates with loader deployment workflow
  • HashService: Generates secure fingerprints for tamper detection
  • Logging: Comprehensive structured logging throughout

Safety Features

  • Validates HTML file structure before injection
  • Uses regex-based detection for reliable injection/removal
  • Atomic file operations with proper error handling
  • Maintains file permissions and timestamps where possible
  • Supports dry-run validation without actual file modification

Testing

Comprehensive Test Coverage

  • 22 Unit Tests: Full method coverage including edge cases and error scenarios
  • 4 Integration Tests: Validates interaction with real HashService
  • All 29 injection-related tests passing

Test Scenarios Covered

  • Constructor validation and dependency injection
  • Successful injection into multiple target files
  • Injection removal and cleanup
  • Fingerprint validation and mismatch detection
  • Backup creation and restoration workflows
  • Error handling for file access and permission issues
  • Integration with service dependencies

Usage Example

// Complete injection workflow
var installations = await vivaldiService.DetectInstallationsAsync();
var activeInstallation = installations.First(i => i.IsActive);

// Generate loader for current manifest
var loaderPath = Path.Combine(activeInstallation.InstallationPath, "vivaldi-mods", "loader.js");
await loaderService.GenerateLoaderAsync(manifest, loaderPath);

// Inject stub into Vivaldi HTML files
await injectionService.InjectAsync(activeInstallation, loaderPath);

// Validate injection integrity
var isValid = await injectionService.ValidateInjectionAsync(activeInstallation);

// Later: Remove for Safe Mode
await injectionService.RemoveInjectionAsync(activeInstallation);

// Or restore original files
await injectionService.RestoreTargetFilesAsync(activeInstallation);

Breaking Changes

None - This is a new service implementation that doesn't modify existing APIs.

Files Changed

  • src/VivaldiModManager.Core/Services/IInjectionService.cs - Service interface
  • src/VivaldiModManager.Core/Services/InjectionService.cs - Implementation
  • src/VivaldiModManager.Core/Models/InjectionStatus.cs - Status tracking models
  • src/VivaldiModManager.Core/Exceptions/InjectionExceptions.cs - Exception hierarchy
  • src/VivaldiModManager.Core/Constants/ManifestConstants.cs - Added injection constants
  • tests/VivaldiModManager.Core.Tests/Services/InjectionServiceTests.cs - Unit tests
  • tests/VivaldiModManager.Core.Tests/Services/InjectionServiceIntegrationTests.cs - Integration tests

Closes #[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 IInjectionService and InjectionService classes 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:

  • Inject minimal stub code into Vivaldi HTML files (window.html, browser.html)
  • Generate and manage injection fingerprints for tamper detection
  • Create and restore backups of original HTML files before modification
  • Validate injection integrity and detect when injections are removed
  • Support atomic injection operations with rollback capability
  • Integrate with LoaderService for deploying generated loader.js files
  • Handle multiple injection targets per installation

Expected Outcome

After completion, the application should have:

  • IInjectionService interface defining the injection management contract
  • InjectionService implementation with full injection and restoration functionality
  • Stub JavaScript injection into HTML files with minimal footprint
  • Fingerprint-based injection validation and tamper detection
  • Atomic injection operations with automatic rollback on failure
  • Integration with VivaldiService for target file discovery
  • Integration with LoaderService for deploying generated loaders
  • Comprehensive backup and restore system for HTML files
  • Full unit test coverage for all injection scenarios
  • Error handling for file access, permission, and integrity issues

Relevant Context

Core Requirements

  • File Location: src/VivaldiModManager.Core/Services/
  • Dependencies: IVivaldiService, ILoaderService, IHashService, ILogger<InjectionService>
  • Models: Uses existing VivaldiInstallation, ManifestData models
  • Target Files: Vivaldi's window.html and browser.html files

Key Functionality Needed

Injection Operations

// Example stub code to inject:
<script>
/* Vivaldi Mod Manager - Injection Stub */
/* Fingerprint: abc123def456 */
/* Generated: 2025-01-21T10:30:00Z */
(async function() {
  try {
    await import('./vivaldi-mods/loader.js');
  } catch (error) {
    console.error('Vivaldi Mod Manager: Failed to load mods:', error);
  }
})();
</script>

Service Methods Required

  • InjectAsync(VivaldiInstallation installation, string loaderPath) - Inject stub into all targets
  • RemoveInjectionAsync(VivaldiInstallation installation) - Remove stub from all targets
  • ValidateInjectionAsync(VivaldiInstallation installation) - Check injection integrity
  • BackupTargetFilesAsync(VivaldiInstallation installation) - Backup original HTML files
  • RestoreTargetFilesAsync(VivaldiInstallation installation) - Restore from backups
  • GetInjectionStatusAsync(VivaldiInstallation installation) - Get current injection state
  • RepairInjectionAsync(VivaldiInstallation installation, string loaderPath) - Fix broken injections

Integration Points

  • Use IVivaldiService.FindInjectionTargetsAsync() to locate HTML files
  • Use ILoaderService.GenerateLoaderAsync() to create loader.js in target directory
  • Use IHashService for fingerprint generation and content integrity
  • Update VivaldiInstallation.LastInjectionAt, LastInjectionStatus, InjectionFingerprint

Technical Requirements

Stub Injection Strategy

<!-- Inject before closing </body> tag or at end of <head> -->
<script type="module">
/* Vivaldi Mod Manager - Injection Stub v1.0 */
/* Fingerprint: [unique-fingerprint] */  
/* Generated: [timestamp] */
(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>

Fingerprint System

  • Generate unique fingerprints for each injection
  • Include fingerprint in both stub code and VivaldiInstallation metadata
  • Use fingerprints to detect duplicate injections and tampering
  • Validate fingerprint matches during integrity checking

File System Operations

  • Atomic operations: write to temp files, then atomic rename
  • Preserve original file permissions and timestamps where possible
  • Handle file locking and sharing issues gracefully
  • Support injecting into multiple HTML files per installation

Backup Management

  • Create timestamped backups before any modifications
  • Store backups in installation-specific directories
  • Maintain backup metadata with original file hashes
  • Support selective restore of individual files
  • Automatic cleanup of old backups based on retention policy

Er...

Fixes #14

💡 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.

Copilot AI and others added 2 commits September 25, 2025 05:53
…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>
Copilot AI changed the title [WIP] [Feature]: Implement Injection Service Implement InjectionService for Vivaldi Mod Manager HTML injection Sep 25, 2025
Copilot AI requested a review from tomasforsman September 25, 2025 06:00
@tomasforsman tomasforsman marked this pull request as ready for review September 25, 2025 06:33
Copilot AI review requested due to automatic review settings September 25, 2025 06:33
@tomasforsman tomasforsman merged commit b980a70 into main Sep 25, 2025
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 InjectionService implementation 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.

Comment on lines +541 to +548
/// <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, ""))
Copy link

Copilot AI Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
/// <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))

Copilot uses AI. Check for mistakes.
Comment on lines +210 to +224
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>
Copy link

Copilot AI Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Suggested change
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}

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Implement Injection Service

3 participants