chromium/ash/webui/common/resources/network_health/network_diagnostics.ts

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import '//resources/ash/common/cr_elements/cr_button/cr_button.js';
import './routine_group.js';

import {I18nMixin} from '//resources/ash/common/cr_elements/i18n_mixin.js';
import {ArcDnsResolutionProblem, ArcHttpProblem, ArcPingProblem, CaptivePortalProblem, DnsLatencyProblem, DnsResolutionProblem, DnsResolverPresentProblem, GatewayCanBePingedProblem, HasSecureWiFiConnectionProblem, HttpFirewallProblem, HttpsFirewallProblem, HttpsLatencyProblem, RoutineCallSource, RoutineProblems, RoutineType, RoutineVerdict, SignalStrengthProblem, VideoConferencingProblem} from '//resources/mojo/chromeos/services/network_health/public/mojom/network_diagnostics.mojom-webui.js';
import {PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import {getNetworkDiagnosticsService} from './mojo_interface_provider.js';
import {getTemplate} from './network_diagnostics.html.js';
import {Routine, RoutineGroup, RoutineResponse} from './network_diagnostics_types.js';

/**
 * @fileoverview Polymer element for interacting with Network Diagnostics.
 */

/**
 * Helper function to create a routine object.
 */
function createRoutine(
    name: string, type: RoutineType, group: RoutineGroup,
    func: () => Promise<RoutineResponse>): Routine {
  return {
    name: name,
    type: type,
    group: group,
    func: func,
    running: false,
    resultMsg: '',
    result: null,
    ariaDescription: '',
  };
}

const NetworkDiagnosticsElementBase = I18nMixin(PolymerElement);

export class NetworkDiagnosticsElement extends NetworkDiagnosticsElementBase {
  static get is() {
    return 'network-diagnostics' as const;
  }

  static get template() {
    return getTemplate();
  }

  static get properties() {
    return {
      /**
       * List of Diagnostics Routines
       */
      routines_: {
        type: Array,
        value: function() {
          const routineGroups = [
            {
              group: RoutineGroup.CONNECTION,
              routines: [
                {
                  name: 'NetworkDiagnosticsLanConnectivity',
                  type: RoutineType.kLanConnectivity,
                  func: () => getNetworkDiagnosticsService().runLanConnectivity(
                      RoutineCallSource.kChromeNetworkPage),
                },
              ],
            },
            {
              group: RoutineGroup.WIFI,
              routines: [
                {
                  name: 'NetworkDiagnosticsSignalStrength',
                  type: RoutineType.kSignalStrength,
                  func: () => getNetworkDiagnosticsService().runSignalStrength(
                      RoutineCallSource.kChromeNetworkPage),
                },
                {
                  name: 'NetworkDiagnosticsHasSecureWiFiConnection',
                  type: RoutineType.kHasSecureWiFiConnection,
                  func: () =>
                      getNetworkDiagnosticsService().runHasSecureWiFiConnection(
                          RoutineCallSource.kChromeNetworkPage),
                },
              ],
            },
            {
              group: RoutineGroup.PORTAL,
              routines: [
                {
                  name: 'NetworkDiagnosticsCaptivePortal',
                  type: RoutineType.kCaptivePortal,
                  func: () => getNetworkDiagnosticsService().runCaptivePortal(
                      RoutineCallSource.kChromeNetworkPage),
                },
              ],
            },
            {
              group: RoutineGroup.GATEWAY,
              routines: [
                {
                  name: 'NetworkDiagnosticsGatewayCanBePinged',
                  type: RoutineType.kGatewayCanBePinged,
                  func: () =>
                      getNetworkDiagnosticsService().runGatewayCanBePinged(
                          RoutineCallSource.kChromeNetworkPage),
                },
              ],
            },
            {
              group: RoutineGroup.FIREWALL,
              routines: [
                {
                  name: 'NetworkDiagnosticsHttpFirewall',
                  type: RoutineType.kHttpFirewall,
                  func: () => getNetworkDiagnosticsService().runHttpFirewall(
                      RoutineCallSource.kChromeNetworkPage),
                },
                {
                  name: 'NetworkDiagnosticsHttpsFirewall',
                  type: RoutineType.kHttpsFirewall,
                  func: () => getNetworkDiagnosticsService().runHttpsFirewall(
                      RoutineCallSource.kChromeNetworkPage),

                },
                {
                  name: 'NetworkDiagnosticsHttpsLatency',
                  type: RoutineType.kHttpsLatency,
                  func: () => getNetworkDiagnosticsService().runHttpsLatency(
                      RoutineCallSource.kChromeNetworkPage),
                },
              ],
            },
            {
              group: RoutineGroup.DNS,
              routines: [
                {
                  name: 'NetworkDiagnosticsDnsResolverPresent',
                  type: RoutineType.kDnsResolverPresent,
                  func: () =>
                      getNetworkDiagnosticsService().runDnsResolverPresent(
                          RoutineCallSource.kChromeNetworkPage),
                },
                {
                  name: 'NetworkDiagnosticsDnsLatency',
                  type: RoutineType.kDnsLatency,
                  func: () => getNetworkDiagnosticsService().runDnsLatency(
                      RoutineCallSource.kChromeNetworkPage),
                },
                {
                  name: 'NetworkDiagnosticsDnsResolution',
                  type: RoutineType.kDnsResolution,
                  func: () => getNetworkDiagnosticsService().runDnsResolution(
                      RoutineCallSource.kChromeNetworkPage),
                },
              ],
            },
            {
              group: RoutineGroup.GOOGLE_SERVICES,
              routines: [
                {
                  name: 'NetworkDiagnosticsVideoConferencing',
                  type: RoutineType.kVideoConferencing,
                  // A null stun_server_hostname will use the routine
                  // default.
                  func: () =>
                      getNetworkDiagnosticsService().runVideoConferencing(
                          /*stun_server_hostname=*/ null,
                          RoutineCallSource.kChromeNetworkPage),
                },
              ],
            },
            {
              group: RoutineGroup.ARC,
              routines: [
                {
                  name: 'ArcNetworkDiagnosticsPing',
                  type: RoutineType.kArcPing,
                  func: () => getNetworkDiagnosticsService().runArcPing(
                      RoutineCallSource.kChromeNetworkPage),
                },
                {
                  name: 'ArcNetworkDiagnosticsHttp',
                  type: RoutineType.kArcHttp,
                  func: () => getNetworkDiagnosticsService().runArcHttp(
                      RoutineCallSource.kChromeNetworkPage),
                },
                {
                  name: 'ArcNetworkDiagnosticsDnsResolution',
                  type: RoutineType.kArcDnsResolution,
                  func: () =>
                      getNetworkDiagnosticsService().runArcDnsResolution(
                          RoutineCallSource.kChromeNetworkPage),
                },
              ],
            },
          ];
          const routines: Routine[] = [];

          for (const group of routineGroups) {
            for (const routine of group.routines) {
              routines[routine.type] = createRoutine(
                  routine.name, routine.type, group.group, routine.func);
            }
          }

          return routines;
        },
      },
      /**
       * Enum of Routine Groups
       */
      routineGroupEnum_: {
        type: Object,
        value: RoutineGroup,
      },
    };
  }

  private routines_: Routine[];

  /**
   * Runs all supported network diagnostics routines.
   */
  runAllRoutines() {
    for (const routine of this.routines_) {
      this.runRoutine_(routine.type);
    }
  }

  /**
   * Runs all supported network diagnostics routines.
   */
  private getRoutineGroup_(routines: {base: Routine[]}, group: RoutineGroup):
      Routine[] {
    return routines.base.filter(r => r.group === group);
  }

  private runRoutine_(type: RoutineType) {
    this.set(`routines_.${type}.running`, true);
    this.set(`routines_.${type}.resultMsg`, '');
    this.set(`routines_.${type}.result`, null);
    this.set(
        `routines_.${type}.ariaDescription`,
        this.i18n('NetworkDiagnosticsRunning'));

    this.routines_[type].func().then(
        (result: RoutineResponse) => this.evaluateRoutine_(type, result));
  }

  private evaluateRoutine_(type: RoutineType, response: RoutineResponse) {
    const routine = `routines_.${type}`;
    this.set(routine + '.running', false);
    this.set(routine + '.result', response.result);

    const resultMsg = this.getRoutineResult_(this.routines_[type]);
    this.set(routine + '.resultMsg', resultMsg);
    this.set(routine + '.ariaDescription', resultMsg);
  }

  /**
   * Helper function to generate the routine result string.
   */
  private getRoutineResult_(routine: Routine): string {
    let verdict = '';

    if (routine.result === null) {
      return '';
    }

    switch (routine.result.verdict) {
      case RoutineVerdict.kNoProblem:
        verdict = this.i18n('NetworkDiagnosticsPassed');
        break;
      case RoutineVerdict.kProblem:
        verdict = this.i18n('NetworkDiagnosticsFailed');
        break;
      case RoutineVerdict.kNotRun:
        verdict = this.i18n('NetworkDiagnosticsNotRun');
        break;
    }

    const problemStrings = this.getRoutineProblemsString_(
        routine.type, routine.result.problems, true);

    if (problemStrings.length) {
      return this.i18n(
          'NetworkDiagnosticsResultPlaceholder', verdict, ...problemStrings);
    } else if (routine.result) {
      return verdict;
    }

    return '';
  }

  private getRoutineProblemsString_(
      type: RoutineType, problems: RoutineProblems,
      translate: boolean): string[] {
    const getString = (s: string) => translate ? this.i18n(s) : s;

    let problemStrings: string[] = [];
    switch (type) {
      case RoutineType.kSignalStrength:
        if (!problems.signalStrengthProblems) {
          break;
        }

        for (const problem of problems.signalStrengthProblems) {
          switch (problem) {
            case SignalStrengthProblem.kWeakSignal:
              problemStrings.push(getString('SignalStrengthProblem_Weak'));
              break;
          }
        }
        break;

      case RoutineType.kGatewayCanBePinged:
        if (!problems.gatewayCanBePingedProblems) {
          break;
        }

        for (const problem of problems.gatewayCanBePingedProblems) {
          switch (problem) {
            case GatewayCanBePingedProblem.kUnreachableGateway:
              problemStrings.push(getString('GatewayPingProblem_Unreachable'));
              break;
            case GatewayCanBePingedProblem.kFailedToPingDefaultNetwork:
              problemStrings.push(
                  getString('GatewayPingProblem_NoDefaultPing'));
              break;
            case GatewayCanBePingedProblem.kDefaultNetworkAboveLatencyThreshold:
              problemStrings.push(
                  getString('GatewayPingProblem_DefaultLatency'));
              break;
            case GatewayCanBePingedProblem.kUnsuccessfulNonDefaultNetworksPings:
              problemStrings.push(
                  getString('GatewayPingProblem_NoNonDefaultPing'));
              break;
            case GatewayCanBePingedProblem
                .kNonDefaultNetworksAboveLatencyThreshold:
              problemStrings.push(
                  getString('GatewayPingProblem_NonDefaultLatency'));
              break;
          }
        }
        break;

      case RoutineType.kHasSecureWiFiConnection:
        if (!problems.hasSecureWifiConnectionProblems) {
          break;
        }

        for (const problem of problems.hasSecureWifiConnectionProblems) {
          switch (problem) {
            case HasSecureWiFiConnectionProblem.kSecurityTypeNone:
              problemStrings.push(getString('SecureWifiProblem_None'));
              break;
            case HasSecureWiFiConnectionProblem.kSecurityTypeWep8021x:
              problemStrings.push(getString('SecureWifiProblem_8021x'));
              break;
            case HasSecureWiFiConnectionProblem.kSecurityTypeWepPsk:
              problemStrings.push(getString('SecureWifiProblem_PSK'));
              break;
            case HasSecureWiFiConnectionProblem.kUnknownSecurityType:
              problemStrings.push(getString('SecureWifiProblem_Unknown'));
              break;
          }
        }
        break;

      case RoutineType.kDnsResolverPresent:
        if (!problems.dnsResolverPresentProblems) {
          break;
        }

        for (const problem of problems.dnsResolverPresentProblems) {
          switch (problem) {
            case DnsResolverPresentProblem.kNoNameServersFound:
              problemStrings.push(
                  getString('DnsResolverProblem_NoNameServers'));
              break;
            case DnsResolverPresentProblem.kMalformedNameServers:
              problemStrings.push(
                  getString('DnsResolverProblem_MalformedNameServers'));
              break;
          }
        }
        break;

      case RoutineType.kDnsLatency:
        if (!problems.dnsLatencyProblems) {
          break;
        }

        for (const problem of problems.dnsLatencyProblems) {
          switch (problem) {
            case DnsLatencyProblem.kHostResolutionFailure:
              problemStrings.push(
                  getString('DnsLatencyProblem_FailedResolveHosts'));
              break;
            case DnsLatencyProblem.kSlightlyAboveThreshold:
              problemStrings.push(
                  getString('DnsLatencyProblem_LatencySlightlyAbove'));
              break;
            case DnsLatencyProblem.kSignificantlyAboveThreshold:
              problemStrings.push(
                  getString('DnsLatencyProblem_LatencySignificantlyAbove'));
              break;
          }
        }
        break;

      case RoutineType.kDnsResolution:
        if (!problems.dnsResolutionProblems) {
          break;
        }

        for (const problem of problems.dnsResolutionProblems) {
          switch (problem) {
            case DnsResolutionProblem.kFailedToResolveHost:
              problemStrings.push(
                  getString('DnsResolutionProblem_FailedResolve'));
              break;
          }
        }
        break;

      case RoutineType.kHttpFirewall:
        if (!problems.httpFirewallProblems) {
          break;
        }

        for (const problem of problems.httpFirewallProblems) {
          switch (problem) {
            case HttpFirewallProblem.kDnsResolutionFailuresAboveThreshold:
              problemStrings.push(
                  getString('FirewallProblem_DnsResolutionFailureRate'));
              break;
            case HttpFirewallProblem.kFirewallDetected:
              problemStrings.push(
                  getString('FirewallProblem_FirewallDetected'));
              break;
            case HttpFirewallProblem.kPotentialFirewall:
              problemStrings.push(
                  getString('FirewallProblem_FirewallSuspected'));
              break;
          }
        }
        break;

      case RoutineType.kHttpsFirewall:
        if (!problems.httpsFirewallProblems) {
          break;
        }

        for (const problem of problems.httpsFirewallProblems) {
          switch (problem) {
            case HttpsFirewallProblem.kHighDnsResolutionFailureRate:
              problemStrings.push(
                  getString('FirewallProblem_DnsResolutionFailureRate'));
              break;
            case HttpsFirewallProblem.kFirewallDetected:
              problemStrings.push(
                  getString('FirewallProblem_FirewallDetected'));
              break;
            case HttpsFirewallProblem.kPotentialFirewall:
              problemStrings.push(
                  getString('FirewallProblem_FirewallSuspected'));
              break;
          }
        }
        break;

      case RoutineType.kHttpsLatency:
        if (!problems.httpsLatencyProblems) {
          break;
        }

        for (const problem of problems.httpsLatencyProblems) {
          switch (problem) {
            case HttpsLatencyProblem.kFailedDnsResolutions:
              problemStrings.push(
                  getString('HttpsLatencyProblem_FailedDnsResolution'));
              break;
            case HttpsLatencyProblem.kFailedHttpsRequests:
              problemStrings.push(
                  getString('HttpsLatencyProblem_FailedHttpsRequests'));
              break;
            case HttpsLatencyProblem.kHighLatency:
              problemStrings.push(getString('HttpsLatencyProblem_HighLatency'));
              break;
            case HttpsLatencyProblem.kVeryHighLatency:
              problemStrings.push(
                  getString('HttpsLatencyProblem_VeryHighLatency'));
              break;
          }
        }
        break;

      case RoutineType.kCaptivePortal:
        if (!problems.captivePortalProblems) {
          break;
        }

        for (const problem of problems.captivePortalProblems) {
          switch (problem) {
            case CaptivePortalProblem.kNoActiveNetworks:
              problemStrings.push(
                  getString('CaptivePortalProblem_NoActiveNetworks'));
              break;
            case CaptivePortalProblem.kUnknownPortalState:
              problemStrings.push(
                  getString('CaptivePortalProblem_UnknownPortalState'));
              break;
            case CaptivePortalProblem.kPortalSuspected:
              problemStrings.push(
                  getString('CaptivePortalProblem_PortalSuspected'));
              break;
            case CaptivePortalProblem.kPortal:
              problemStrings.push(getString('CaptivePortalProblem_Portal'));
              break;
            case CaptivePortalProblem.kProxyAuthRequired:
              problemStrings.push(
                  getString('CaptivePortalProblem_ProxyAuthRequired'));
              break;
            case CaptivePortalProblem.kNoInternet:
              problemStrings.push(getString('CaptivePortalProblem_NoInternet'));
              break;
          }
        }
        break;

      case RoutineType.kVideoConferencing:
        if (!problems.videoConferencingProblems) {
          break;
        }

        for (const problem of problems.videoConferencingProblems) {
          switch (problem) {
            case VideoConferencingProblem.kUdpFailure:
              problemStrings.push(
                  getString('VideoConferencingProblem_UdpFailure'));
              break;
            case VideoConferencingProblem.kTcpFailure:
              problemStrings.push(
                  getString('VideoConferencingProblem_TcpFailure'));
              break;
            case VideoConferencingProblem.kMediaFailure:
              problemStrings.push(
                  getString('VideoConferencingProblem_MediaFailure'));
              break;
          }
        }
        break;

      case RoutineType.kArcPing:
        if (!problems.arcPingProblems) {
          break;
        }
        problemStrings = problemStrings.concat(
            this.getArcPingProblemStringIds_(problems.arcPingProblems)
                .map(getString));
        break;

      case RoutineType.kArcDnsResolution:
        if (!problems.arcDnsResolutionProblems) {
          break;
        }
        problemStrings = problemStrings.concat(
            this.getArcDnsProblemStringIds_(problems.arcDnsResolutionProblems)
                .map(getString));
        break;

      case RoutineType.kArcHttp:
        if (!problems.arcHttpProblems) {
          break;
        }
        problemStrings = problemStrings.concat(
            this.getArcHttpProblemStringIds_(problems.arcHttpProblems)
                .map(getString));
        break;
    }

    return problemStrings;
  }

  /**
   * Converts a collection ArcPingProblem into string identifiers for display.
   */
  private getArcPingProblemStringIds_(problems: ArcPingProblem[]): string[] {
    const problemStringIds = [];

    for (const problem of problems) {
      switch (problem) {
        case ArcPingProblem.kFailedToGetArcServiceManager:
        case ArcPingProblem.kGetManagedPropertiesTimeoutFailure:
          problemStringIds.push('ArcRoutineProblem_InternalError');
          break;
        case ArcPingProblem.kFailedToGetNetInstanceForPingTest:
          problemStringIds.push('ArcRoutineProblem_ArcNotRunning');
          break;
        case ArcPingProblem.kUnreachableGateway:
          problemStringIds.push('GatewayPingProblem_Unreachable');
          break;
        case ArcPingProblem.kFailedToPingDefaultNetwork:
          problemStringIds.push('GatewayPingProblem_NoDefaultPing');
          break;
        case ArcPingProblem.kDefaultNetworkAboveLatencyThreshold:
          problemStringIds.push('GatewayPingProblem_DefaultLatency');
          break;
        case ArcPingProblem.kUnsuccessfulNonDefaultNetworksPings:
          problemStringIds.push('GatewayPingProblem_NoNonDefaultPing');
          break;
        case ArcPingProblem.kNonDefaultNetworksAboveLatencyThreshold:
          problemStringIds.push('GatewayPingProblem_NonDefaultLatency');
      }
    }

    return problemStringIds;
  }

  /**
   * Converts a collection ArcDnsResolutionProblem into string identifiers for
   * display.
   */
  private getArcDnsProblemStringIds_(problems: ArcDnsResolutionProblem[]):
      string[] {
    const problemStringIds = [];

    for (const problem of problems) {
      switch (problem) {
        case ArcDnsResolutionProblem.kFailedToGetArcServiceManager:
          problemStringIds.push('ArcRoutineProblem_InternalError');
          break;
        case ArcDnsResolutionProblem
            .kFailedToGetNetInstanceForDnsResolutionTest:
          problemStringIds.push('ArcRoutineProblem_ArcNotRunning');
          break;
        case ArcDnsResolutionProblem.kHighLatency:
          problemStringIds.push('DnsLatencyProblem_LatencySlightlyAbove');
          break;
        case ArcDnsResolutionProblem.kVeryHighLatency:
          problemStringIds.push('DnsLatencyProblem_LatencySignificantlyAbove');
          break;
        case ArcDnsResolutionProblem.kFailedDnsQueries:
          problemStringIds.push('DnsResolutionProblem_FailedResolve');
          break;
      }
    }

    return problemStringIds;
  }

  /**
   * Converts a collection ArcHttpProblem into string identifiers for display.
   */
  private getArcHttpProblemStringIds_(problems: ArcHttpProblem[]): string[] {
    const problemStringIds = [];

    for (const problem of problems) {
      switch (problem) {
        case ArcHttpProblem.kFailedToGetArcServiceManager:
          problemStringIds.push('ArcRoutineProblem_InternalError');
          break;
        case ArcHttpProblem.kFailedToGetNetInstanceForHttpTest:
          problemStringIds.push('ArcRoutineProblem_ArcNotRunning');
          break;
        case ArcHttpProblem.kHighLatency:
          problemStringIds.push('ArcHttpProblem_HighLatency');
          break;
        case ArcHttpProblem.kVeryHighLatency:
          problemStringIds.push('ArcHttpProblem_VeryHighLatency');
          break;
        case ArcHttpProblem.kFailedHttpRequests:
          problemStringIds.push('ArcHttpProblem_FailedHttpRequests');
          break;
      }
    }

    return problemStringIds;
  }

  private getRoutineVerdictRawString_(verdict: RoutineVerdict): string {
    switch (verdict) {
      case RoutineVerdict.kNoProblem:
        return 'Passed';
      case RoutineVerdict.kNotRun:
        return 'Not Run';
      case RoutineVerdict.kProblem:
        return 'Failed';
    }
    return 'Unknown';
  }
}

declare global {
  interface HTMLElementTagNameMap {
    [NetworkDiagnosticsElement.is]: NetworkDiagnosticsElement;
  }
}

customElements.define(NetworkDiagnosticsElement.is, NetworkDiagnosticsElement);