proofmode 0.9.0

Capture, share, and preserve verifiable photos and videos
Documentation
// ProofMode Web Worker
// Handles WASM operations in a separate thread to avoid blocking the UI

let proofmodeModule = null;

// Initialize the WASM module
async function initializeWasm() {
  try {
    // Import the WASM module for web target
    const wasmModule = await import('/wasm/proofmode.js');
    // Initialize the WASM module - the default export is the init function
    await wasmModule.default();
    proofmodeModule = wasmModule;
    
    self.postMessage({ type: 'initialized', success: true });
  } catch (error) {
    console.error('Failed to initialize WASM:', error);
    self.postMessage({ 
      type: 'initialized', 
      success: false, 
      error: error.message 
    });
  }
}

// Handle proof checking
async function checkProof(fileData, fileName) {
  if (!proofmodeModule) {
    throw new Error('ProofMode WASM not initialized');
  }

  try {
    // Create a Uint8Array from the file data
    const uint8Array = new Uint8Array(fileData);
    
    // For now, we'll use checkFiles as check_bytes doesn't exist
    // This will need to be updated when the WASM API is finalized
    const files = [{ name: fileName, data: uint8Array }];
    const result = await proofmodeModule.checkFiles(files, (msg) => {
      console.log('Check progress:', msg);
    });
    
    return {
      success: true,
      result: JSON.parse(result)
    };
  } catch (error) {
    return {
      success: false,
      error: error.message
    };
  }
}

// Handle proof generation
async function generateProof(fileData, fileName, options) {
  console.log('generateProof called with:', { fileName, options });
  
  if (!proofmodeModule) {
    throw new Error('ProofMode WASM not initialized');
  }

  try {
    const uint8Array = new Uint8Array(fileData);
    console.log('File data converted to Uint8Array, length:', uint8Array.length);
    
    // Variables to capture the proof data
    let capturedProofData = null;
    let capturedCsvData = null;
    
    // Prepare device info from browser context
    const deviceInfo = {
      manufacturer: options.deviceInfo?.manufacturer || 'Web Browser',
      model: options.deviceInfo?.model || navigator.userAgent,
      os_version: options.deviceInfo?.osVersion || navigator.platform,
      ...options.deviceInfo
    };
    
    // Prepare location info if provided
    const locationInfo = options.locationInfo || null;
    
    // The WASM function expects metadata as a flat HashMap<String, String>
    // So we need to flatten our nested structure
    const metadata = {
      fileName: fileName,
      passphrase: options.passphrase || '',
      manufacturer: deviceInfo.manufacturer,
      model: deviceInfo.model,
      osVersion: deviceInfo.osVersion,
      platform: deviceInfo.platform || '',
      userAgent: deviceInfo.userAgent || '',
      screenResolution: deviceInfo.screenResolution || '',
      language: deviceInfo.language || '',
      timezone: deviceInfo.timezone || '',
      // Add location if available
      ...(locationInfo && {
        latitude: locationInfo.latitude.toString(),
        longitude: locationInfo.longitude.toString(),
        accuracy: locationInfo.accuracy ? locationInfo.accuracy.toString() : '',
        locationProvider: locationInfo.provider || 'browser_geolocation'
      })
    };
    
    console.log('Metadata prepared (flattened):', metadata);
    console.log('Calling generate_proof_wasm...');
    
    // The generate_proof_wasm function expects different parameters
    // Let's check what it actually needs
    if (!proofmodeModule.generate_proof_wasm) {
      console.error('generate_proof_wasm function not found in module');
      throw new Error('WASM function generate_proof_wasm not available');
    }
    
    // Based on the Rust code, create callbacks object with all required methods
    const callbacks = {
      getDeviceInfo: () => {
        console.log('getDeviceInfo callback called');
        return deviceInfo; // Return as object, not JSON string
      },
      getLocationInfo: () => {
        console.log('getLocationInfo callback called');
        return locationInfo; // Return object or null
      },
      getNetworkInfo: () => {
        console.log('getNetworkInfo callback called');
        // Return null for now as we don't collect network info in browser
        return null;
      },
      saveData: (hash, filename, data) => {
        console.log('saveData callback:', { hash, filename, dataLength: data.length });
        // In browser context, store in memory or return true
        return true;
      },
      saveText: (hash, filename, text) => {
        console.log('saveText callback:', { hash, filename, textLength: text.length });
        // Capture the proof JSON data
        if (filename.endsWith('.proof.json')) {
          capturedProofData = JSON.parse(text);
        } else if (filename.endsWith('.proof.csv')) {
          capturedCsvData = text;
        }
        return true;
      },
      signData: (data) => {
        console.log('signData callback called, data length:', data.length);
        // Return null as we don't have PGP signing in browser
        return null;
      },
      notarizeHash: (hash) => {
        console.log('notarizeHash callback called for hash:', hash);
        // Return null as we don't do notarization in browser
        return null;
      }
    };
    
    console.log('Calling generate_proof_wasm with callbacks...');
    const result = proofmodeModule.generate_proof_wasm(
      uint8Array,
      JSON.stringify(metadata),
      callbacks
    );
    
    console.log('WASM function returned:', result);
    
    // Return the complete proof data that was captured
    if (capturedProofData) {
      return {
        success: true,
        result: capturedProofData
      };
    } else {
      // Fallback if proof data wasn't captured
      return {
        success: true,
        result: {
          file_hash_sha256: result,
          timestamp: new Date().toISOString(),
          proofPath: `${result}.proof.json`
        }
      };
    }
  } catch (error) {
    console.error('Error in generateProof:', error);
    console.error('Error stack:', error.stack);
    return {
      success: false,
      error: error.message || 'Unknown error occurred'
    };
  }
}

// Message handler
self.addEventListener('message', async (event) => {
  const { type, payload } = event.data;
  console.log('Worker received message:', type, payload);
  
  switch (type) {
    case 'init':
      await initializeWasm();
      break;
      
    case 'check':
      try {
        const checkResult = await checkProof(payload.fileData, payload.fileName);
        self.postMessage({
          type: 'checkResult',
          id: payload.id,
          ...checkResult
        });
      } catch (error) {
        console.error('Check error:', error);
        self.postMessage({
          type: 'checkResult',
          id: payload.id,
          success: false,
          error: error.message
        });
      }
      break;
      
    case 'generate':
      try {
        console.log('Processing generate request...');
        const generateResult = await generateProof(
          payload.fileData,
          payload.fileName,
          payload.options
        );
        console.log('Generate result:', generateResult);
        self.postMessage({
          type: 'generateResult',
          id: payload.id,
          ...generateResult
        });
      } catch (error) {
        console.error('Generate error:', error);
        self.postMessage({
          type: 'generateResult',
          id: payload.id,
          success: false,
          error: error.message
        });
      }
      break;
      
    default:
      console.warn('Unknown message type:', type);
  }
});