chromium/chrome/browser/ui/webui/nearby_internals/nearby_internals_contact_handler.cc

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

#include "chrome/browser/ui/webui/nearby_internals/nearby_internals_contact_handler.h"

#include <string>
#include <utility>

#include "base/functional/bind.h"
#include "base/json/json_writer.h"
#include "base/time/time.h"
#include "chrome/browser/nearby_sharing/logging/proto_to_dictionary_conversion.h"
#include "chrome/browser/nearby_sharing/nearby_sharing_service.h"
#include "chrome/browser/nearby_sharing/nearby_sharing_service_factory.h"
#include "components/cross_device/logging/logging.h"

namespace {

std::string FormatListAsJSON(const base::Value::List& list) {
  std::string json;
  base::JSONWriter::WriteWithOptions(
      list, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json);
  return json;
}

base::Value GetJavascriptTimestamp() {
  return base::Value(
      base::Time::Now().InMillisecondsFSinceUnixEpochIgnoringNull());
}

// Keys in the JSON representation of a contact message
const char kContactMessageTimeKey[] = "time";
const char kContactMessageContactsChangedKey[] = "contactsChanged";
const char kContactMessageAllowedIdsKey[] = "allowedIds";
const char kContactMessageContactRecordKey[] = "contactRecords";
const char kContactMessageNumUnreachableContactsKey[] =
    "numUnreachableContacts";

// Converts Contact to a raw dictionary value used as a JSON argument to
// JavaScript functions.
// TODO(nohle): We should probably break up this dictionary into smaller
// dictionaries corresponding to each contact-manager observer functions. This
// will require changes at the javascript layer as well.
base::Value::Dict ContactMessageToDictionary(
    std::optional<bool> did_contacts_change_since_last_upload,
    const std::optional<std::set<std::string>>& allowed_contact_ids,
    const std::optional<std::vector<nearby::sharing::proto::ContactRecord>>&
        contacts,
    std::optional<uint32_t> num_unreachable_contacts_filtered_out) {
  base::Value::Dict dictionary;

  dictionary.Set(kContactMessageTimeKey, GetJavascriptTimestamp());
  if (did_contacts_change_since_last_upload.has_value()) {
    dictionary.Set(kContactMessageContactsChangedKey,
                   *did_contacts_change_since_last_upload);
  }
  if (allowed_contact_ids) {
    base::Value::List allowed_ids_list;
    allowed_ids_list.reserve(allowed_contact_ids->size());
    for (const auto& contact_id : *allowed_contact_ids) {
      allowed_ids_list.Append(contact_id);
    }
    dictionary.Set(kContactMessageAllowedIdsKey,
                   FormatListAsJSON(allowed_ids_list));
  }
  if (contacts) {
    base::Value::List contact_list;
    contact_list.reserve(contacts->size());
    for (const auto& contact : *contacts)
      contact_list.Append(ContactRecordToReadableDictionary(contact));

    dictionary.Set(kContactMessageContactRecordKey,
                   FormatListAsJSON(contact_list));
  }
  if (num_unreachable_contacts_filtered_out.has_value()) {
    dictionary.Set(kContactMessageNumUnreachableContactsKey,
                   int(*num_unreachable_contacts_filtered_out));
  }
  return dictionary;
}

}  // namespace

NearbyInternalsContactHandler::NearbyInternalsContactHandler(
    content::BrowserContext* context)
    : context_(context) {}

NearbyInternalsContactHandler::~NearbyInternalsContactHandler() = default;

void NearbyInternalsContactHandler::RegisterMessages() {
  web_ui()->RegisterMessageCallback(
      "initializeContacts",
      base::BindRepeating(&NearbyInternalsContactHandler::InitializeContents,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "downloadContacts",
      base::BindRepeating(
          &NearbyInternalsContactHandler::HandleDownloadContacts,
          base::Unretained(this)));
}

void NearbyInternalsContactHandler::OnJavascriptAllowed() {
  NearbySharingService* service_ =
      NearbySharingServiceFactory::GetForBrowserContext(context_);
  if (service_) {
    observation_.Observe(service_->GetContactManager());
  } else {
    CD_LOG(ERROR, Feature::NS) << "No NearbyShareService instance to call.";
  }
}

void NearbyInternalsContactHandler::OnJavascriptDisallowed() {
  observation_.Reset();
}

void NearbyInternalsContactHandler::InitializeContents(
    const base::Value::List& args) {
  AllowJavascript();
}

void NearbyInternalsContactHandler::HandleDownloadContacts(
    const base::Value::List& args) {
  NearbySharingService* service_ =
      NearbySharingServiceFactory::GetForBrowserContext(context_);
  if (service_) {
    service_->GetContactManager()->DownloadContacts();
  } else {
    CD_LOG(ERROR, Feature::NS) << "No NearbyShareService instance to call.";
  }
}

void NearbyInternalsContactHandler::OnContactsDownloaded(
    const std::set<std::string>& allowed_contact_ids,
    const std::vector<nearby::sharing::proto::ContactRecord>& contacts,
    uint32_t num_unreachable_contacts_filtered_out) {
  FireWebUIListener("contacts-updated",
                    ContactMessageToDictionary(
                        /*did_contacts_change_since_last_upload=*/std::nullopt,
                        allowed_contact_ids, contacts,
                        num_unreachable_contacts_filtered_out));
}

void NearbyInternalsContactHandler::OnContactsUploaded(
    bool did_contacts_change_since_last_upload) {
  FireWebUIListener(
      "contacts-updated",
      ContactMessageToDictionary(
          did_contacts_change_since_last_upload,
          /*allowed_contact_ids=*/std::nullopt,
          /*contacts=*/std::nullopt,
          /*num_unreachable_contacts_filtered_out=*/std::nullopt));
}