OverviewFilesCommitsIssuesDocsReleasesPeers

hyperident / lib / debug-manager.js

424 lines · javascript ref:
/**
 * Debug Manager - Debugging and diagnostic tools for hyperident
 * Provides identity inspection, network diagnostics, and troubleshooting utilities
 */

import { IdentityManager } from './identity-manager.js'
import { DeviceMeshManager } from './device-mesh-manager.js'
import { DeviceLinkingManager } from './device-linking-manager.js'
import { FileManager } from './file-ops.js'
import { CryptoManager } from './crypto.js'

export class DebugManager {
  /**
   * Check if network dependencies are available
   * @returns {Promise<boolean>} True if network dependencies are available
   */
  static async isNetworkAvailable() {
    try {
      await import('hyperswarm')
      await import('hypercore-id-encoding')
      await import('b4a')
      return true
    } catch (error) {
      return false
    }
  }

  /**
   * Get comprehensive identity inspection data
   * @param {string} passphrase - Optional passphrase for encrypted identities
   * @returns {Promise<Object>} Identity inspection data
   */
  static async inspectIdentity(passphrase = null) {
    const inspection = {
      timestamp: new Date().toISOString(),
      identity: null,
      deviceChain: null,
      profiles: null,
      files: {
        identity: false,
        identityPub: false,
        profiles: false,
        deviceKey: false,
        devicePub: false,
        deviceProof: false,
        deviceJson: false,
        bootstrapProof: false
      },
      errors: []
    }

    try {
      // Check file existence
      const hyperidentPath = FileManager.getHyperidentPath()
      
      try {
        inspection.files.identity = await FileManager.fileExists(`${hyperidentPath}/identity`)
      } catch (e) { inspection.errors.push(`Identity file check: ${e.message}`) }
      
      try {
        inspection.files.identityPub = await FileManager.fileExists(`${hyperidentPath}/identity.pub`)
      } catch (e) { inspection.errors.push(`Identity.pub file check: ${e.message}`) }
      
      try {
        inspection.files.profiles = await FileManager.fileExists(`${hyperidentPath}/profiles.json`)
      } catch (e) { inspection.errors.push(`Profiles file check: ${e.message}`) }
      
      try {
        inspection.files.deviceKey = await FileManager.fileExists(`${hyperidentPath}/device.key`)
      } catch (e) { inspection.errors.push(`Device.key file check: ${e.message}`) }
      
      try {
        inspection.files.devicePub = await FileManager.fileExists(`${hyperidentPath}/device.pub`)
      } catch (e) { inspection.errors.push(`Device.pub file check: ${e.message}`) }
      
      try {
        inspection.files.deviceProof = await FileManager.fileExists(`${hyperidentPath}/device.proof`)
      } catch (e) { inspection.errors.push(`Device.proof file check: ${e.message}`) }
      
      try {
        inspection.files.deviceJson = await FileManager.fileExists(`${hyperidentPath}/device.json`)
      } catch (e) { inspection.errors.push(`Device.json file check: ${e.message}`) }
      
      try {
        inspection.files.bootstrapProof = await FileManager.fileExists(`${hyperidentPath}/bootstrap.proof`)
      } catch (e) { inspection.errors.push(`Bootstrap.proof file check: ${e.message}`) }

      // Load identity data
      try {
        inspection.identity = await IdentityManager.loadIdentity(passphrase)
      } catch (e) { 
        inspection.errors.push(`Identity load: ${e.message}`)
      }

      // Load device chain
      try {
        if (await IdentityManager.deviceChainExists()) {
          inspection.deviceChain = await IdentityManager.loadDeviceChain()
        }
      } catch (e) { 
        inspection.errors.push(`Device chain load: ${e.message}`)
      }

      // Load profiles
      try {
        inspection.profiles = await IdentityManager.listProfiles()
      } catch (e) { 
        inspection.errors.push(`Profiles load: ${e.message}`)
      }

    } catch (error) {
      inspection.errors.push(`General inspection error: ${error.message}`)
    }

    return inspection
  }

  /**
   * Run network diagnostics
   * @returns {Promise<Object>} Network diagnostic results
   */
  static async runNetworkDiagnostics() {
    const diagnostics = {
      timestamp: new Date().toISOString(),
      networkAvailable: false,
      dependencies: {
        hyperswarm: false,
        hypercoreIdEncoding: false,
        b4a: false
      },
      meshStatus: null,
      linkingStatus: null,
      errors: []
    }

    // Check network dependencies
    try {
      await import('hyperswarm')
      diagnostics.dependencies.hyperswarm = true
    } catch (e) {
      diagnostics.errors.push(`Hyperswarm dependency: ${e.message}`)
    }

    try {
      await import('hypercore-id-encoding')
      diagnostics.dependencies.hypercoreIdEncoding = true
    } catch (e) {
      diagnostics.errors.push(`Hypercore-id-encoding dependency: ${e.message}`)
    }

    try {
      await import('b4a')
      diagnostics.dependencies.b4a = true
    } catch (e) {
      diagnostics.errors.push(`B4a dependency: ${e.message}`)
    }

    diagnostics.networkAvailable = diagnostics.dependencies.hyperswarm && 
                                   diagnostics.dependencies.hypercoreIdEncoding && 
                                   diagnostics.dependencies.b4a

    if (diagnostics.networkAvailable) {
      // Test mesh manager
      try {
        const meshManager = new DeviceMeshManager()
        diagnostics.meshStatus = await meshManager.getMeshStatus()
        await meshManager.cleanup()
      } catch (e) {
        diagnostics.errors.push(`Mesh status check: ${e.message}`)
      }

      // Test linking manager
      try {
        const linkingManager = new DeviceLinkingManager()
        diagnostics.linkingStatus = {
          available: await DeviceLinkingManager.isNetworkAvailable(),
          initialized: true
        }
        await linkingManager.cleanup()
      } catch (e) {
        diagnostics.errors.push(`Linking manager check: ${e.message}`)
      }
    }

    return diagnostics
  }

  /**
   * Verify trust relationships for identity and devices
   * @param {string} passphrase - Optional passphrase for encrypted identities
   * @returns {Promise<Object>} Trust verification results
   */
  static async verifyTrust(passphrase = null) {
    const verification = {
      timestamp: new Date().toISOString(),
      identityValid: false,
      deviceChainValid: false,
      bootstrapProofValid: false,
      signingCapable: false,
      trustChain: [],
      errors: []
    }

    try {
      // Verify identity exists and is loadable
      if (IdentityManager.identityExists()) {
        try {
          const identity = await IdentityManager.loadIdentity(passphrase)
          verification.identityValid = true
          verification.trustChain.push({
            type: 'identity',
            publicKey: identity.publicKey,
            valid: true,
            created: identity.created
          })
        } catch (e) {
          verification.errors.push(`Identity verification: ${e.message}`)
        }
      } else {
        verification.errors.push('Identity does not exist')
      }

      // Verify device chain
      try {
        if (await IdentityManager.deviceChainExists()) {
          const deviceChain = await IdentityManager.loadDeviceChain()
          verification.deviceChainValid = true
          verification.trustChain.push({
            type: 'device',
            publicKey: deviceChain.metadata.publicKey,
            name: deviceChain.metadata.name,
            valid: true,
            created: deviceChain.metadata.created
          })
        } else {
          verification.errors.push('Device chain does not exist')
        }
      } catch (e) {
        verification.errors.push(`Device chain verification: ${e.message}`)
      }

      // Verify bootstrap proof
      try {
        const hyperidentPath = FileManager.getHyperidentPath()
        if (await FileManager.fileExists(`${hyperidentPath}/bootstrap.proof`)) {
          verification.bootstrapProofValid = true
          verification.trustChain.push({
            type: 'bootstrap_proof',
            valid: true,
            purpose: 'signing_operations'
          })
        } else {
          verification.errors.push('Bootstrap proof does not exist')
        }
      } catch (e) {
        verification.errors.push(`Bootstrap proof verification: ${e.message}`)
      }

      // Test signing capability
      try {
        if (verification.identityValid && verification.deviceChainValid && verification.bootstrapProofValid) {
          // Test with small data
          const testData = Buffer.from('hyperident-trust-test')
          const signature = await IdentityManager.signData(testData, passphrase)
          const verificationResult = await IdentityManager.verifySignature(signature, testData)
          
          if (verificationResult) {
            verification.signingCapable = true
            verification.trustChain.push({
              type: 'signing_test',
              valid: true,
              tested: true
            })
          } else {
            verification.errors.push('Signing test failed - signature did not verify')
          }
        } else {
          verification.errors.push('Cannot test signing - missing required components')
        }
      } catch (e) {
        verification.errors.push(`Signing capability test: ${e.message}`)
      }

    } catch (error) {
      verification.errors.push(`General trust verification error: ${error.message}`)
    }

    return verification
  }

  /**
   * Get system information relevant to hyperident
   * @returns {Promise<Object>} System information
   */
  static async getSystemInfo() {
    const systemInfo = {
      timestamp: new Date().toISOString(),
      node: {
        version: process.version,
        platform: process.platform,
        arch: process.arch
      },
      hyperident: {
        version: '0.1.0', // TODO: Read from package.json
        workingDirectory: process.cwd(),
        hyperidentPath: FileManager.getHyperidentPath()
      },
      environment: {
        debug: !!process.env.DEBUG,
        nodeEnv: process.env.NODE_ENV || 'development'
      },
      memory: process.memoryUsage(),
      uptime: process.uptime()
    }

    return systemInfo
  }

  /**
   * Run comprehensive health check
   * @param {string} passphrase - Optional passphrase for encrypted identities
   * @returns {Promise<Object>} Health check results
   */
  static async healthCheck(passphrase = null) {
    const health = {
      timestamp: new Date().toISOString(),
      overall: 'unknown',
      checks: {
        identity: { status: 'unknown', details: null },
        network: { status: 'unknown', details: null },
        trust: { status: 'unknown', details: null },
        system: { status: 'unknown', details: null }
      },
      summary: {
        passed: 0,
        failed: 0,
        warnings: 0
      }
    }

    // Identity check
    try {
      const identityInspection = await this.inspectIdentity(passphrase)
      if (identityInspection.errors.length === 0 && identityInspection.identity) {
        health.checks.identity.status = 'pass'
      } else if (identityInspection.identity) {
        health.checks.identity.status = 'warning'
        health.summary.warnings++
      } else {
        health.checks.identity.status = 'fail'
        health.summary.failed++
      }
      health.checks.identity.details = identityInspection
    } catch (e) {
      health.checks.identity.status = 'fail'
      health.checks.identity.details = { error: e.message }
      health.summary.failed++
    }

    // Network check
    try {
      const networkDiagnostics = await this.runNetworkDiagnostics()
      if (networkDiagnostics.networkAvailable && networkDiagnostics.errors.length === 0) {
        health.checks.network.status = 'pass'
        health.summary.passed++
      } else if (networkDiagnostics.networkAvailable) {
        health.checks.network.status = 'warning'
        health.summary.warnings++
      } else {
        health.checks.network.status = 'fail'
        health.summary.failed++
      }
      health.checks.network.details = networkDiagnostics
    } catch (e) {
      health.checks.network.status = 'fail'
      health.checks.network.details = { error: e.message }
      health.summary.failed++
    }

    // Trust check
    try {
      const trustVerification = await this.verifyTrust(passphrase)
      if (trustVerification.signingCapable && trustVerification.errors.length === 0) {
        health.checks.trust.status = 'pass'
        health.summary.passed++
      } else if (trustVerification.identityValid) {
        health.checks.trust.status = 'warning'
        health.summary.warnings++
      } else {
        health.checks.trust.status = 'fail'
        health.summary.failed++
      }
      health.checks.trust.details = trustVerification
    } catch (e) {
      health.checks.trust.status = 'fail'
      health.checks.trust.details = { error: e.message }
      health.summary.failed++
    }

    // System check
    try {
      const systemInfo = await this.getSystemInfo()
      health.checks.system.status = 'pass'
      health.checks.system.details = systemInfo
      health.summary.passed++
    } catch (e) {
      health.checks.system.status = 'fail'
      health.checks.system.details = { error: e.message }
      health.summary.failed++
    }

    // Determine overall health
    if (health.summary.failed === 0 && health.summary.warnings === 0) {
      health.overall = 'healthy'
    } else if (health.summary.failed === 0) {
      health.overall = 'warning'
    } else {
      health.overall = 'unhealthy'
    }

    return health
  }
}