diff --git a/chrome/browser/chrome_browser_interface_binders.cc b/chrome/browser/chrome_browser_interface_binders.cc index 8740790d0e2ca..fcd998105a582 100644 --- a/chrome/browser/chrome_browser_interface_binders.cc +++ b/chrome/browser/chrome_browser_interface_binders.cc @@ -793,6 +793,10 @@ void PopulateChromeWebUIFrameBinders( chromeos::eche_app::mojom::SystemInfoProvider, chromeos::eche_app::EcheAppUI>(map); + RegisterWebUIControllerInterfaceBinder< + chromeos::eche_app::mojom::UidGenerator, chromeos::eche_app::EcheAppUI>( + map); + RegisterWebUIControllerInterfaceBinder< media_app_ui::mojom::PageHandlerFactory, chromeos::MediaAppUI>(map); diff --git a/chrome/browser/chromeos/eche_app/eche_app_manager_factory.cc b/chrome/browser/chromeos/eche_app/eche_app_manager_factory.cc index 67eaa76dd41f9..b53d82f523ec2 100644 --- a/chrome/browser/chromeos/eche_app/eche_app_manager_factory.cc +++ b/chrome/browser/chromeos/eche_app/eche_app_manager_factory.cc @@ -20,10 +20,12 @@ #include "chrome/browser/ui/browser_window.h" #include "chrome/browser/ui/web_applications/system_web_app_ui_utils.h" #include "chromeos/components/eche_app_ui/eche_app_manager.h" +#include "chromeos/components/eche_app_ui/eche_uid_provider.h" #include "chromeos/components/eche_app_ui/system_info.h" #include "chromeos/components/phonehub/phone_hub_manager.h" #include "chromeos/strings/grit/chromeos_strings.h" #include "components/keyed_service/content/browser_context_dependency_manager.h" +#include "components/pref_registry/pref_registry_syncable.h" #include "components/user_manager/user_manager.h" #include "ui/base/l10n/l10n_util.h" #include "ui/chromeos/devicetype_utils.h" @@ -83,6 +85,11 @@ EcheAppManagerFactory::EcheAppManagerFactory() EcheAppManagerFactory::~EcheAppManagerFactory() = default; +void EcheAppManagerFactory::RegisterProfilePrefs( + user_prefs::PrefRegistrySyncable* registry) { + registry->RegisterStringPref(kEcheAppSeedPref, ""); +} + KeyedService* EcheAppManagerFactory::BuildServiceInstanceFor( content::BrowserContext* context) const { if (!features::IsPhoneHubEnabled() || !features::IsEcheSWAEnabled()) @@ -108,9 +115,10 @@ KeyedService* EcheAppManagerFactory::BuildServiceInstanceFor( secure_channel::SecureChannelClientProvider::GetInstance()->GetClient(); if (!secure_channel_client) return nullptr; - return new EcheAppManager(GetSystemInfo(profile), phone_hub_manager, - device_sync_client, multidevice_setup_client, - secure_channel_client, + + return new EcheAppManager(profile->GetPrefs(), GetSystemInfo(profile), + phone_hub_manager, device_sync_client, + multidevice_setup_client, secure_channel_client, base::BindRepeating(&LaunchEcheApp, profile), base::BindRepeating(&CloseEcheApp, profile)); } diff --git a/chrome/browser/chromeos/eche_app/eche_app_manager_factory.h b/chrome/browser/chromeos/eche_app/eche_app_manager_factory.h index 450ca68073bd8..2fc862f312044 100644 --- a/chrome/browser/chromeos/eche_app/eche_app_manager_factory.h +++ b/chrome/browser/chromeos/eche_app/eche_app_manager_factory.h @@ -33,6 +33,8 @@ class EcheAppManagerFactory : public BrowserContextKeyedServiceFactory { // BrowserContextKeyedServiceFactory: KeyedService* BuildServiceInstanceFor( content::BrowserContext* context) const override; + void RegisterProfilePrefs( + user_prefs::PrefRegistrySyncable* registry) override; std::unique_ptr GetSystemInfo(Profile* profile) const; }; diff --git a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc index 7601cd410d3eb..ae98e93877b81 100644 --- a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc +++ b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc @@ -401,6 +401,14 @@ void BindSystemInfoProvider( } } +void BindEcheUidGenerator( + chromeos::eche_app::EcheAppManager* manager, + mojo::PendingReceiver receiver) { + if (manager) { + manager->BindUidGeneratorInterface(std::move(receiver)); + } +} + template <> WebUIController* NewWebUI(WebUI* web_ui, const GURL& url) { @@ -409,7 +417,8 @@ WebUIController* NewWebUI(WebUI* web_ui, chromeos::eche_app::EcheAppManagerFactory::GetForProfile(profile); return new chromeos::eche_app::EcheAppUI( web_ui, base::BindRepeating(&BindEcheSignalingMessageExchanger, manager), - base::BindRepeating(&BindSystemInfoProvider, manager)); + base::BindRepeating(&BindSystemInfoProvider, manager), + base::BindRepeating(&BindEcheUidGenerator, manager)); } void BindScanService( diff --git a/chromeos/components/eche_app_ui/BUILD.gn b/chromeos/components/eche_app_ui/BUILD.gn index 7f34ee5137980..22b513d2db424 100644 --- a/chromeos/components/eche_app_ui/BUILD.gn +++ b/chromeos/components/eche_app_ui/BUILD.gn @@ -27,6 +27,8 @@ static_library("eche_app_ui") { "eche_notification_click_handler.h", "eche_signaler.cc", "eche_signaler.h", + "eche_uid_provider.cc", + "eche_uid_provider.h", "feature_status.cc", "feature_status.h", "feature_status_provider.cc", @@ -51,6 +53,7 @@ static_library("eche_app_ui") { "//chromeos/services/device_sync/public/cpp:cpp", "//chromeos/services/multidevice_setup/public/cpp:cpp", "//chromeos/services/secure_channel/public/cpp/client:client", + "//components/prefs", "//content/public/browser", "//ui/webui", ] @@ -74,6 +77,7 @@ source_set("unit_tests") { sources = [ "eche_feature_status_provider_unittest.cc", "eche_notification_click_handler_unittest.cc", + "eche_uid_provider_unittest.cc", "system_info_provider_unittest.cc", ] deps = [ diff --git a/chromeos/components/eche_app_ui/DEPS b/chromeos/components/eche_app_ui/DEPS index 68f00d5f1d77d..cc74faf023361 100644 --- a/chromeos/components/eche_app_ui/DEPS +++ b/chromeos/components/eche_app_ui/DEPS @@ -2,7 +2,9 @@ include_rules = [ # Do not add chrome here (use a delegate instead). "+chromeos/grit/chromeos_sample_system_web_app_resources.h", "+components/keyed_service/core/keyed_service.h", + "+components/prefs", "+content/public/browser", "+content/public/common", + "+third_party/boringssl/src/include", "+ui/webui", ] diff --git a/chromeos/components/eche_app_ui/eche_app_manager.cc b/chromeos/components/eche_app_ui/eche_app_manager.cc index dd6a3d5007805..44918232802e1 100644 --- a/chromeos/components/eche_app_ui/eche_app_manager.cc +++ b/chromeos/components/eche_app_ui/eche_app_manager.cc @@ -7,6 +7,7 @@ #include "base/system/sys_info.h" #include "chromeos/components/eche_app_ui/eche_notification_click_handler.h" #include "chromeos/components/eche_app_ui/eche_signaler.h" +#include "chromeos/components/eche_app_ui/eche_uid_provider.h" #include "chromeos/components/eche_app_ui/system_info.h" #include "chromeos/components/eche_app_ui/system_info_provider.h" #include "chromeos/components/phonehub/notification_interaction_handler.h" @@ -23,6 +24,7 @@ const char kMetricNameLatency[] = "Eche.Connectivity.Latency"; namespace eche_app { EcheAppManager::EcheAppManager( + PrefService* pref_service, std::unique_ptr system_info, phonehub::PhoneHubManager* phone_hub_manager, device_sync::DeviceSyncClient* device_sync_client, @@ -57,7 +59,8 @@ EcheAppManager::EcheAppManager( signaler_(std::make_unique(eche_connector_.get(), connection_manager_.get())), system_info_provider_( - std::make_unique(std::move(system_info))) {} + std::make_unique(std::move(system_info))), + uid_(std::make_unique(pref_service)) {} EcheAppManager::~EcheAppManager() = default; @@ -71,7 +74,13 @@ void EcheAppManager::BindSystemInfoProviderInterface( system_info_provider_->Bind(std::move(receiver)); } +void EcheAppManager::BindUidGeneratorInterface( + mojo::PendingReceiver receiver) { + uid_->Bind(std::move(receiver)); +} + void EcheAppManager::Shutdown() { + uid_.reset(); system_info_provider_.reset(); signaler_.reset(); eche_connector_.reset(); diff --git a/chromeos/components/eche_app_ui/eche_app_manager.h b/chromeos/components/eche_app_ui/eche_app_manager.h index 8272d89582efa..bb07ad83e793f 100644 --- a/chromeos/components/eche_app_ui/eche_app_manager.h +++ b/chromeos/components/eche_app_ui/eche_app_manager.h @@ -14,6 +14,8 @@ #include "chromeos/components/phonehub/phone_hub_manager.h" #include "components/keyed_service/core/keyed_service.h" +class PrefService; + namespace chromeos { namespace device_sync { @@ -34,13 +36,15 @@ namespace eche_app { class SystemInfo; class EcheSignaler; class SystemInfoProvider; +class EcheUidProvider; // Implements the core logic of the EcheApp and exposes interfaces via its // public API. Implemented as a KeyedService since it depends on other // KeyedService instances. class EcheAppManager : public KeyedService { public: - EcheAppManager(std::unique_ptr system_info, + EcheAppManager(PrefService* pref_service, + std::unique_ptr system_info, phonehub::PhoneHubManager*, device_sync::DeviceSyncClient*, multidevice_setup::MultiDeviceSetupClient*, @@ -54,6 +58,8 @@ class EcheAppManager : public KeyedService { void BindSignalingMessageExchangerInterface( mojo::PendingReceiver receiver); + void BindUidGeneratorInterface( + mojo::PendingReceiver receiver); void BindSystemInfoProviderInterface( mojo::PendingReceiver receiver); @@ -69,6 +75,7 @@ class EcheAppManager : public KeyedService { std::unique_ptr eche_connector_; std::unique_ptr signaler_; std::unique_ptr system_info_provider_; + std::unique_ptr uid_; }; } // namespace eche_app diff --git a/chromeos/components/eche_app_ui/eche_app_ui.cc b/chromeos/components/eche_app_ui/eche_app_ui.cc index 57be5858d8850..05777d8387853 100644 --- a/chromeos/components/eche_app_ui/eche_app_ui.cc +++ b/chromeos/components/eche_app_ui/eche_app_ui.cc @@ -19,10 +19,12 @@ namespace eche_app { EcheAppUI::EcheAppUI(content::WebUI* web_ui, BindSignalingMessageExchangerCallback exchanger_callback, - BindSystemInfoProviderCallback system_info_callback) + BindSystemInfoProviderCallback system_info_callback, + BindUidGeneratorCallback generator_callback) : ui::MojoWebUIController(web_ui), bind_exchanger_callback_(std::move(exchanger_callback)), - bind_system_info_callback_(std::move(system_info_callback)) { + bind_system_info_callback_(std::move(system_info_callback)), + bind_generator_callback_(std::move(generator_callback)) { auto html_source = base::WrapUnique(content::WebUIDataSource::Create(kChromeUIEcheAppHost)); @@ -61,6 +63,11 @@ void EcheAppUI::BindInterface( bind_system_info_callback_.Run(std::move(receiver)); } +void EcheAppUI::BindInterface( + mojo::PendingReceiver receiver) { + bind_generator_callback_.Run(std::move(receiver)); +} + WEB_UI_CONTROLLER_TYPE_IMPL(EcheAppUI) } // namespace eche_app diff --git a/chromeos/components/eche_app_ui/eche_app_ui.h b/chromeos/components/eche_app_ui/eche_app_ui.h index 490208aacb251..0e1f8c98e37b4 100644 --- a/chromeos/components/eche_app_ui/eche_app_ui.h +++ b/chromeos/components/eche_app_ui/eche_app_ui.h @@ -19,11 +19,13 @@ class EcheAppUI : public ui::MojoWebUIController { mojo::PendingReceiver)>; using BindSystemInfoProviderCallback = base::RepeatingCallback)>; + using BindUidGeneratorCallback = + base::RepeatingCallback)>; EcheAppUI(content::WebUI* web_ui, BindSignalingMessageExchangerCallback exchanger_callback, - BindSystemInfoProviderCallback system_info_callback); - + BindSystemInfoProviderCallback system_info_callback, + BindUidGeneratorCallback generator_callback); EcheAppUI(const EcheAppUI&) = delete; EcheAppUI& operator=(const EcheAppUI&) = delete; ~EcheAppUI() override; @@ -33,9 +35,12 @@ class EcheAppUI : public ui::MojoWebUIController { void BindInterface(mojo::PendingReceiver receiver); + void BindInterface(mojo::PendingReceiver receiver); + private: const BindSignalingMessageExchangerCallback bind_exchanger_callback_; const BindSystemInfoProviderCallback bind_system_info_callback_; + const BindUidGeneratorCallback bind_generator_callback_; WEB_UI_CONTROLLER_TYPE_DECL(); }; diff --git a/chromeos/components/eche_app_ui/eche_uid_provider.cc b/chromeos/components/eche_app_ui/eche_uid_provider.cc new file mode 100644 index 0000000000000..48cc39c429577 --- /dev/null +++ b/chromeos/components/eche_app_ui/eche_uid_provider.cc @@ -0,0 +1,104 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromeos/components/eche_app_ui/eche_uid_provider.h" +#include +#include +#include +#include "base/check.h" +#include "chromeos/components/multidevice/logging/logging.h" +#include "components/prefs/pref_service.h" +#include "crypto/random.h" + +namespace chromeos { +namespace eche_app { + +const char kEcheAppSeedPref[] = "cros.echeapp.seed"; +const size_t kSeedSizeInByte = 32; + +EcheUidProvider::EcheUidProvider(PrefService* pref_service) + : pref_service_(pref_service) {} + +EcheUidProvider::~EcheUidProvider() = default; + +void EcheUidProvider::GetUid( + base::OnceCallback callback) { + if (!uid_.empty()) { + std::move(callback).Run(uid_); + return; + } + uint8_t public_key[ED25519_PUBLIC_KEY_LEN]; + uint8_t private_key[ED25519_PRIVATE_KEY_LEN]; + std::string pref_seed = pref_service_->GetString(kEcheAppSeedPref); + if (pref_seed.empty()) { + GenerateKeyPair(public_key, private_key); + } else { + base::Optional> result = + ConvertStringToBinary(pref_seed, kSeedSizeInByte); + if (!result) { + PA_LOG(WARNING) << "Invalid encoded string, regenerate the keypair."; + GenerateKeyPair(public_key, private_key); + } else { + DCHECK_EQ(kSeedSizeInByte, result->size()); + ED25519_keypair_from_seed(public_key, private_key, result->data()); + } + } + uid_ = ConvertBinaryToString(public_key); + std::move(callback).Run(uid_); +} + +void EcheUidProvider::GenerateKeyPair( + uint8_t public_key[ED25519_PUBLIC_KEY_LEN], + uint8_t private_key[ED25519_PRIVATE_KEY_LEN]) { + ED25519_keypair(public_key, private_key); + // Store the seed (what RFC8032 calls a private key), which is the + // first 32 bytes of what BoringSSL calls the private key. + pref_service_->SetString( + kEcheAppSeedPref, + ConvertBinaryToString(base::make_span(private_key, kSeedSizeInByte))); +} + +base::Optional> EcheUidProvider::ConvertStringToBinary( + base::StringPiece str, + size_t expected_len) { + std::vector decoded_data(str.size()); + size_t decoded_data_len = 0; + if (!EVP_DecodeBase64( + decoded_data.data(), &decoded_data_len, decoded_data.size(), + reinterpret_cast(str.data()), str.size())) { + PA_LOG(ERROR) << "Attempting to decode string failed."; + return base::nullopt; + } + if (decoded_data_len != expected_len) { + PA_LOG(ERROR) << "Expected length is not match."; + return base::nullopt; + } + decoded_data.resize(decoded_data_len); + return decoded_data; +} + +std::string EcheUidProvider::ConvertBinaryToString( + base::span src) { + // Use a constant-time implementation of base64 in BoringSSL instead of + // base::Base64Encode. + size_t encoded_data_len; + CHECK(EVP_EncodedLength(&encoded_data_len, src.size()) == 1); + std::vector encoded_data(encoded_data_len); + size_t encoded_block_len = + EVP_EncodeBlock(encoded_data.data(), src.data(), src.size()); + // The return value of EVP_EncodeBlock is not the same with the result of + // EVP_EncodedLength, we save the real size of EVP_EncodeBlock to string, not + // the size of buffer. + return std::string(reinterpret_cast(encoded_data.data()), + encoded_block_len); +} + +void EcheUidProvider::Bind( + mojo::PendingReceiver receiver) { + uid_receiver_.reset(); + uid_receiver_.Bind(std::move(receiver)); +} + +} // namespace eche_app +} // namespace chromeos diff --git a/chromeos/components/eche_app_ui/eche_uid_provider.h b/chromeos/components/eche_app_ui/eche_uid_provider.h new file mode 100644 index 0000000000000..21a782c56093f --- /dev/null +++ b/chromeos/components/eche_app_ui/eche_uid_provider.h @@ -0,0 +1,54 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMEOS_COMPONENTS_ECHE_APP_UI_ECHE_UID_PROVIDER_H_ +#define CHROMEOS_COMPONENTS_ECHE_APP_UI_ECHE_UID_PROVIDER_H_ + +#include +#include "base/containers/span.h" +#include "base/optional.h" +#include "chromeos/components/eche_app_ui/mojom/eche_app.mojom.h" +#include "mojo/public/cpp/bindings/remote.h" +#include "third_party/boringssl/src/include/openssl/curve25519.h" + +class PrefService; + +namespace chromeos { +namespace eche_app { + +extern const char kEcheAppSeedPref[]; +extern const size_t kSeedSizeInByte; + +// Implements the core logic of generating an UID for EcheApp and exposes the +// interface via mojo. Also store the UID in PrefService to persist UID. +class EcheUidProvider : public mojom::UidGenerator { + public: + explicit EcheUidProvider(PrefService* pref_service); + ~EcheUidProvider() override; + + EcheUidProvider(const EcheUidProvider&) = delete; + EcheUidProvider& operator=(const EcheUidProvider&) = delete; + + // mojom::UidGenerator: + void GetUid(base::OnceCallback callback) override; + + void Bind(mojo::PendingReceiver receiver); + + private: + std::string ConvertBinaryToString(base::span src); + base::Optional> ConvertStringToBinary( + base::StringPiece str, + size_t expected_len); + void GenerateKeyPair(uint8_t public_key[ED25519_PUBLIC_KEY_LEN], + uint8_t private_key[ED25519_PRIVATE_KEY_LEN]); + + mojo::Receiver uid_receiver_{this}; + std::string uid_{}; + PrefService* pref_service_; +}; + +} // namespace eche_app +} // namespace chromeos + +#endif // CHROMEOS_COMPONENTS_ECHE_APP_UI_ECHE_UID_PROVIDER_H_ diff --git a/chromeos/components/eche_app_ui/eche_uid_provider_unittest.cc b/chromeos/components/eche_app_ui/eche_uid_provider_unittest.cc new file mode 100644 index 0000000000000..6d6a6315f2f62 --- /dev/null +++ b/chromeos/components/eche_app_ui/eche_uid_provider_unittest.cc @@ -0,0 +1,89 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromeos/components/eche_app_ui/eche_uid_provider.h" +#include +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/testing_pref_service.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chromeos { +namespace eche_app { + +class Callback { + public: + static void GetUidCallback(const std::string& uid) { uid_ = uid; } + static std::string GetUid() { return uid_; } + static void ResetUid() { uid_ = ""; } + + private: + static std::string uid_; +}; + +std::string chromeos::eche_app::Callback::uid_ = ""; + +class EcheUidProviderTest : public testing::Test { + protected: + EcheUidProviderTest() = default; + EcheUidProviderTest(const EcheUidProviderTest&) = delete; + EcheUidProviderTest& operator=(const EcheUidProviderTest&) = delete; + ~EcheUidProviderTest() override = default; + + // testing::Test: + void SetUp() override { + pref_service_.registry()->RegisterStringPref(kEcheAppSeedPref, ""); + uid_provider_ = std::make_unique(&pref_service_); + } + void TearDown() override { + uid_provider_.reset(); + Callback::ResetUid(); + } + void ResetPrefString(const std::string& path, const std::string& value) { + pref_service_.SetString(path, value); + uid_provider_.reset(); + uid_provider_ = std::make_unique(&pref_service_); + } + void ResetUidProvider() { + uid_provider_.reset(); + uid_provider_ = std::make_unique(&pref_service_); + } + void GetUid() { + uid_provider_->GetUid(base::BindOnce(&Callback::GetUidCallback)); + } + + private: + TestingPrefServiceSimple pref_service_; + std::unique_ptr uid_provider_; +}; + +TEST_F(EcheUidProviderTest, GetUidHasValue) { + GetUid(); + EXPECT_NE(Callback::GetUid(), ""); +} + +TEST_F(EcheUidProviderTest, GetUidFromCacheShouldBeTheSameOne) { + GetUid(); + std::string uid = Callback::GetUid(); + GetUid(); + EXPECT_EQ(Callback::GetUid(), uid); +} + +TEST_F(EcheUidProviderTest, GetUidFromPrefShouldBeTheSameOne) { + GetUid(); + std::string uid = Callback::GetUid(); + ResetUidProvider(); + GetUid(); + EXPECT_EQ(Callback::GetUid(), uid); +} + +TEST_F(EcheUidProviderTest, GetUidWithWrongKeyShouldNotBeTheSame) { + GetUid(); + std::string uid = Callback::GetUid(); + ResetPrefString(kEcheAppSeedPref, "wrong seed"); + GetUid(); + EXPECT_NE(Callback::GetUid(), uid); +} + +} // namespace eche_app +} // namespace chromeos diff --git a/chromeos/components/eche_app_ui/mojom/eche_app.mojom b/chromeos/components/eche_app_ui/mojom/eche_app.mojom index 6633019dbc5d6..a369ba7e58e83 100644 --- a/chromeos/components/eche_app_ui/mojom/eche_app.mojom +++ b/chromeos/components/eche_app_ui/mojom/eche_app.mojom @@ -31,3 +31,9 @@ interface SystemInfoProvider { // output parameter is JSON encoded set of data. GetSystemInfo() => (string system_info); }; + +// Interface for generating uid. The uid is unique and persistent. +interface UidGenerator { + // Get the uid. + GetUid() => (string local_uid); +}; diff --git a/chromeos/components/eche_app_ui/resources/browser_proxy.js b/chromeos/components/eche_app_ui/resources/browser_proxy.js index 19b8a764659b4..6197bcbf97a86 100644 --- a/chromeos/components/eche_app_ui/resources/browser_proxy.js +++ b/chromeos/components/eche_app_ui/resources/browser_proxy.js @@ -16,6 +16,9 @@ signalMessageExchanger.setSignalingMessageObserver( // Returns a remote for SystemInfoProvider interface which gets system info // from the browser. const systemInfo = chromeos.echeApp.mojom.SystemInfoProvider.getRemote(); +// Returns a remote for UidGenerator interface which gets an uid from the +// browser. +const uidGenerator = chromeos.echeApp.mojom.UidGenerator.getRemote(); // The implementation of echeapi.d.ts const EcheApiBindingImpl = new class { @@ -35,6 +38,10 @@ const EcheApiBindingImpl = new class { getSystemInfo() { return systemInfo.getSystemInfo(); } + + getLocalUid() { + return uidGenerator.getUid(); + } }; // Declare module echeapi and bind the implementation to echeapi.d.ts @@ -47,6 +54,8 @@ echeapi.webrtc.tearDownSignal = echeapi.webrtc.registerSignalReceiver = EcheApiBindingImpl.onWebRtcSignalReceived.bind(EcheApiBindingImpl); echeapi.system = {}; +echeapi.system.getLocalUid = + EcheApiBindingImpl.getLocalUid.bind(EcheApiBindingImpl); echeapi.system.getSystemInfo = EcheApiBindingImpl.getSystemInfo.bind(EcheApiBindingImpl); window['echeapi'] = echeapi;