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

#include "third_party/blink/renderer/modules/push_messaging/push_messaging_client.h"

#include <string>
#include <utility>

#include "third_party/blink/public/mojom/manifest/manifest.mojom-blink.h"
#include "third_party/blink/public/mojom/push_messaging/push_messaging_status.mojom-blink.h"
#include "third_party/blink/public/platform/browser_interface_broker_proxy.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "third_party/blink/public/web/web_local_frame_client.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame_client.h"
#include "third_party/blink/renderer/modules/manifest/manifest_manager.h"
#include "third_party/blink/renderer/modules/push_messaging/push_error.h"
#include "third_party/blink/renderer/modules/push_messaging/push_messaging_utils.h"
#include "third_party/blink/renderer/modules/push_messaging/push_subscription.h"
#include "third_party/blink/renderer/modules/push_messaging/push_subscription_options.h"
#include "third_party/blink/renderer/modules/push_messaging/push_type_converter.h"
#include "third_party/blink/renderer/modules/service_worker/service_worker_registration.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
#include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"

namespace blink {

// static
const char PushMessagingClient::kSupplementName[] = "PushMessagingClient";

PushMessagingClient::PushMessagingClient(LocalDOMWindow& window)
    : Supplement<LocalDOMWindow>(window), push_messaging_manager_(&window) {
  // This class will be instantiated for every page load (rather than on push
  // messaging use), so there's nothing to be done in this constructor.
}

// static
PushMessagingClient* PushMessagingClient::From(LocalDOMWindow& window) {
  auto* client = Supplement<LocalDOMWindow>::From<PushMessagingClient>(window);
  if (!client) {
    client = MakeGarbageCollected<PushMessagingClient>(window);
    Supplement<LocalDOMWindow>::ProvideTo(window, client);
  }
  return client;
}

mojom::blink::PushMessaging* PushMessagingClient::GetPushMessagingRemote() {
  if (!push_messaging_manager_.is_bound()) {
    GetSupplementable()->GetBrowserInterfaceBroker().GetInterface(
        push_messaging_manager_.BindNewPipeAndPassReceiver(
            GetSupplementable()->GetTaskRunner(TaskType::kMiscPlatformAPI)));
  }

  return push_messaging_manager_.get();
}

void PushMessagingClient::Subscribe(
    ServiceWorkerRegistration* service_worker_registration,
    PushSubscriptionOptions* options,
    bool user_gesture,
    ScriptPromiseResolver<PushSubscription>* resolver) {
  DCHECK(resolver);

  mojom::blink::PushSubscriptionOptionsPtr options_ptr =
      mojo::ConvertTo<mojom::blink::PushSubscriptionOptionsPtr>(options);

  // If a developer provided an application server key in |options|, skip
  // fetching the manifest.
  if (!options->applicationServerKey()->ByteLength()) {
    ManifestManager* manifest_manager =
        ManifestManager::From(*GetSupplementable());
    manifest_manager->RequestManifest(BindOnce(
        &PushMessagingClient::DidGetManifest, WrapPersistent(this),
        WrapPersistent(service_worker_registration), std::move(options_ptr),
        user_gesture, WrapPersistent(resolver)));
  } else {
    DoSubscribe(service_worker_registration, std::move(options_ptr),
                user_gesture, WrapPersistent(resolver));
  }
}

void PushMessagingClient::Trace(Visitor* visitor) const {
  Supplement<LocalDOMWindow>::Trace(visitor);
  visitor->Trace(push_messaging_manager_);
}

void PushMessagingClient::DidGetManifest(
    ServiceWorkerRegistration* service_worker_registration,
    mojom::blink::PushSubscriptionOptionsPtr options,
    bool user_gesture,
    ScriptPromiseResolver<PushSubscription>* resolver,
    mojom::blink::ManifestRequestResult result,
    const KURL& manifest_url,
    mojom::blink::ManifestPtr manifest) {
  // Get the application_server_key from the manifest since it wasn't provided
  // by the caller.
  if (manifest_url.IsEmpty() || manifest == mojom::blink::Manifest::New() ||
      result != mojom::blink::ManifestRequestResult::kSuccess) {
    DidSubscribe(
        service_worker_registration, resolver,
        mojom::blink::PushRegistrationStatus::MANIFEST_EMPTY_OR_MISSING,
        nullptr /* subscription */);
    return;
  }

  if (!manifest->gcm_sender_id.IsNull()) {
    StringUtf8Adaptor gcm_sender_id_as_utf8_string(manifest->gcm_sender_id);
    Vector<uint8_t> application_server_key;
    application_server_key.append_range(gcm_sender_id_as_utf8_string);
    options->application_server_key = std::move(application_server_key);
  }

  DoSubscribe(service_worker_registration, std::move(options), user_gesture,
              resolver);
}

void PushMessagingClient::DoSubscribe(
    ServiceWorkerRegistration* service_worker_registration,
    mojom::blink::PushSubscriptionOptionsPtr options,
    bool user_gesture,
    ScriptPromiseResolver<PushSubscription>* resolver) {
  DCHECK(resolver);

  if (options->application_server_key.empty()) {
    DidSubscribe(service_worker_registration, resolver,
                 mojom::blink::PushRegistrationStatus::NO_SENDER_ID,
                 nullptr /* subscription */);
    return;
  }

  GetPushMessagingRemote()->Subscribe(
      service_worker_registration->RegistrationId(), std::move(options),
      user_gesture,
      BindOnce(&PushMessagingClient::DidSubscribe, WrapPersistent(this),
               WrapPersistent(service_worker_registration),
               WrapPersistent(resolver)));
}

void PushMessagingClient::DidSubscribe(
    ServiceWorkerRegistration* service_worker_registration,
    ScriptPromiseResolver<PushSubscription>* resolver,
    mojom::blink::PushRegistrationStatus status,
    mojom::blink::PushSubscriptionPtr subscription) {
  DCHECK(resolver);

  if (status ==
          mojom::blink::PushRegistrationStatus::SUCCESS_FROM_PUSH_SERVICE ||
      status == mojom::blink::PushRegistrationStatus::
                    SUCCESS_NEW_SUBSCRIPTION_FROM_PUSH_SERVICE ||
      status == mojom::blink::PushRegistrationStatus::SUCCESS_FROM_CACHE) {
    DCHECK(subscription);

    resolver->Resolve(PushSubscription::Create(std::move(subscription),
                                               service_worker_registration));
  } else {
    resolver->Reject(PushError::CreateException(
        PushRegistrationStatusToPushErrorType(status),
        PushRegistrationStatusToString(status)));
  }
}

}  // namespace blink
