diff --git a/app/src/main/java/com/duckduckgo/app/browser/RealWebViewCapabilityChecker.kt b/app/src/main/java/com/duckduckgo/app/browser/RealWebViewCapabilityChecker.kt index 0e4e74e89521..4d2314a377fd 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/RealWebViewCapabilityChecker.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/RealWebViewCapabilityChecker.kt @@ -24,12 +24,12 @@ import com.duckduckgo.app.browser.api.WebViewCapabilityChecker.WebViewCapability import com.duckduckgo.browser.api.WebViewVersionProvider import com.duckduckgo.common.utils.DispatcherProvider import com.duckduckgo.common.utils.extensions.compareSemanticVersion -import com.duckduckgo.di.scopes.FragmentScope +import com.duckduckgo.di.scopes.AppScope import com.squareup.anvil.annotations.ContributesBinding import javax.inject.Inject import kotlinx.coroutines.withContext -@ContributesBinding(FragmentScope::class) +@ContributesBinding(AppScope::class) class RealWebViewCapabilityChecker @Inject constructor( private val dispatchers: DispatcherProvider, private val webViewVersionProvider: WebViewVersionProvider, diff --git a/app/src/main/java/com/duckduckgo/app/browser/WebViewSafeMessageListening.kt b/app/src/main/java/com/duckduckgo/app/browser/WebViewSafeMessageListening.kt deleted file mode 100644 index d614b3167088..000000000000 --- a/app/src/main/java/com/duckduckgo/app/browser/WebViewSafeMessageListening.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2024 DuckDuckGo - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.duckduckgo.app.browser - -import androidx.webkit.WebViewFeature -import com.duckduckgo.browser.api.WebViewMessageListening -import com.duckduckgo.browser.api.WebViewVersionProvider -import com.duckduckgo.common.utils.DispatcherProvider -import com.duckduckgo.common.utils.extensions.compareSemanticVersion -import com.duckduckgo.di.scopes.AppScope -import com.squareup.anvil.annotations.ContributesBinding -import javax.inject.Inject -import kotlinx.coroutines.withContext - -@ContributesBinding(AppScope::class) -class WebViewSafeMessageListening @Inject constructor( - private val dispatchers: DispatcherProvider, - private val webViewVersionProvider: WebViewVersionProvider, -) : WebViewMessageListening { - - override suspend fun isWebMessageListenerSupported(): Boolean { - return withContext(dispatchers.io()) { - webViewVersionProvider.getFullVersion().compareSemanticVersion(WEB_MESSAGE_LISTENER_WEBVIEW_VERSION)?.let { - it >= 0 - } ?: false - } && WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER) - } - - companion object { - private const val WEB_MESSAGE_LISTENER_WEBVIEW_VERSION = "126.0.6478.40" - } -} diff --git a/autofill/autofill-api/src/main/java/com/duckduckgo/autofill/api/AutofillScreens.kt b/autofill/autofill-api/src/main/java/com/duckduckgo/autofill/api/AutofillScreens.kt index ff119db2c586..6535501bb21d 100644 --- a/autofill/autofill-api/src/main/java/com/duckduckgo/autofill/api/AutofillScreens.kt +++ b/autofill/autofill-api/src/main/java/com/duckduckgo/autofill/api/AutofillScreens.kt @@ -16,10 +16,8 @@ package com.duckduckgo.autofill.api -import android.os.Parcelable import com.duckduckgo.autofill.api.domain.app.LoginCredentials import com.duckduckgo.navigation.api.GlobalActivityStarter.ActivityParams -import kotlinx.parcelize.Parcelize sealed interface AutofillScreens { @@ -55,23 +53,6 @@ sealed interface AutofillScreens { data object AutofillImportViaGooglePasswordManagerScreen : ActivityParams { private fun readResolve(): Any = AutofillImportViaGooglePasswordManagerScreen } - - sealed interface Result : Parcelable { - - companion object { - const val RESULT_KEY = "importResult" - const val RESULT_KEY_DETAILS = "importResultDetails" - } - - @Parcelize - data class Success(val importedCount: Int) : Result - - @Parcelize - data class UserCancelled(val stage: String) : Result - - @Parcelize - data object Error : Result - } } } diff --git a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/importing/blob/WebViewBlobDownloader.kt b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/importing/blob/WebViewBlobDownloader.kt new file mode 100644 index 000000000000..a0a9a025c833 --- /dev/null +++ b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/importing/blob/WebViewBlobDownloader.kt @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2024 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.autofill.impl.importing.blob + +import android.annotation.SuppressLint +import android.net.Uri +import android.webkit.WebView +import androidx.webkit.JavaScriptReplyProxy +import androidx.webkit.WebViewCompat +import com.duckduckgo.app.browser.api.WebViewCapabilityChecker +import com.duckduckgo.app.browser.api.WebViewCapabilityChecker.WebViewCapability +import com.duckduckgo.di.scopes.AppScope +import com.squareup.anvil.annotations.ContributesBinding +import javax.inject.Inject + +/** + * This interface provides the ability to add modern blob download support to a WebView. + */ +interface WebViewBlobDownloader { + + /** + * Configures a web view to support blob downloads, including in iframes. + */ + suspend fun addBlobDownloadSupport(webView: WebView) + + /** + * Requests the WebView to convert a blob URL to a data URI. + */ + suspend fun convertBlobToDataUri(blobUrl: String) + + /** + * Stores a reply proxy for a given location. + */ + suspend fun storeReplyProxy( + originUrl: String, + replyProxy: JavaScriptReplyProxy, + locationHref: String?, + ) + + /** + * Clears any stored JavaScript reply proxies. + */ + fun clearReplyProxies() +} + +@ContributesBinding(AppScope::class) +class WebViewBlobDownloaderModernImpl @Inject constructor( + private val webViewCapabilityChecker: WebViewCapabilityChecker, +) : WebViewBlobDownloader { + + // Map>() = Map>() + private val fixedReplyProxyMap = mutableMapOf>() + + @SuppressLint("RequiresFeature") + override suspend fun addBlobDownloadSupport(webView: WebView) { + if (isBlobDownloadWebViewFeatureEnabled()) { + WebViewCompat.addDocumentStartJavaScript(webView, script, setOf("*")) + } + } + + @SuppressLint("RequiresFeature") + override suspend fun convertBlobToDataUri(blobUrl: String) { + for ((key, proxies) in fixedReplyProxyMap) { + if (sameOrigin(blobUrl.removePrefix("blob:"), key)) { + for (replyProxy in proxies.values) { + replyProxy.postMessage(blobUrl) + } + return + } + } + } + + override suspend fun storeReplyProxy( + originUrl: String, + replyProxy: JavaScriptReplyProxy, + locationHref: String?, + ) { + val frameProxies = fixedReplyProxyMap[originUrl]?.toMutableMap() ?: mutableMapOf() + // if location.href is not passed, we fall back to origin + val safeLocationHref = locationHref ?: originUrl + frameProxies[safeLocationHref] = replyProxy + fixedReplyProxyMap[originUrl] = frameProxies + } + + private fun sameOrigin( + firstUrl: String, + secondUrl: String, + ): Boolean { + return kotlin.runCatching { + val firstUri = Uri.parse(firstUrl) + val secondUri = Uri.parse(secondUrl) + + firstUri.host == secondUri.host && firstUri.scheme == secondUri.scheme && firstUri.port == secondUri.port + }.getOrNull() ?: return false + } + + override fun clearReplyProxies() { + fixedReplyProxyMap.clear() + } + + private suspend fun isBlobDownloadWebViewFeatureEnabled(): Boolean { + return webViewCapabilityChecker.isSupported(WebViewCapability.WebMessageListener) && + webViewCapabilityChecker.isSupported(WebViewCapability.DocumentStartJavaScript) + } + + companion object { + private val script = """ + window.__url_to_blob_collection = {}; + + const original_createObjectURL = URL.createObjectURL; + + URL.createObjectURL = function () { + const blob = arguments[0]; + const url = original_createObjectURL.call(this, ...arguments); + if (blob instanceof Blob) { + __url_to_blob_collection[url] = blob; + } + return url; + } + + function blobToBase64DataUrl(blob) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onloadend = function() { + resolve(reader.result); + } + reader.onerror = function() { + reject(new Error('Failed to read Blob object')); + } + reader.readAsDataURL(blob); + }); + } + + const pingMessage = 'Ping:' + window.location.href + ddgBlobDownloadObj.postMessage(pingMessage) + + ddgBlobDownloadObj.onmessage = function(event) { + if (event.data.startsWith('blob:')) { + const blob = window.__url_to_blob_collection[event.data]; + if (blob) { + blobToBase64DataUrl(blob).then((dataUrl) => { + ddgBlobDownloadObj.postMessage(dataUrl); + }); + } + } + } + """.trimIndent() + } +} diff --git a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/importing/gpm/webflow/ImportGooglePasswordBlobConsumer.kt b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/importing/gpm/webflow/ImportGooglePasswordBlobConsumer.kt index 0dd3a250ae6d..089dfbdc13c8 100644 --- a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/importing/gpm/webflow/ImportGooglePasswordBlobConsumer.kt +++ b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/importing/gpm/webflow/ImportGooglePasswordBlobConsumer.kt @@ -23,8 +23,8 @@ import androidx.webkit.JavaScriptReplyProxy import androidx.webkit.WebMessageCompat import androidx.webkit.WebViewCompat import com.duckduckgo.app.di.AppCoroutineScope +import com.duckduckgo.autofill.impl.importing.blob.WebViewBlobDownloader import com.duckduckgo.autofill.impl.importing.gpm.webflow.GooglePasswordBlobConsumer.Callback -import com.duckduckgo.browser.api.WebViewMessageListening import com.duckduckgo.common.utils.DispatcherProvider import com.duckduckgo.di.scopes.FragmentScope import com.squareup.anvil.annotations.ContributesBinding @@ -50,23 +50,19 @@ interface GooglePasswordBlobConsumer { @ContributesBinding(FragmentScope::class) class ImportGooglePasswordBlobConsumer @Inject constructor( + private val webViewBlobDownloader: WebViewBlobDownloader, private val dispatchers: DispatcherProvider, - private val webViewMessageListening: WebViewMessageListening, @AppCoroutineScope private val appCoroutineScope: CoroutineScope, ) : GooglePasswordBlobConsumer { - private val replyProxyMap = mutableMapOf() - - // Map>() = Map>() - private val fixedReplyProxyMap = mutableMapOf>() - @SuppressLint("RequiresFeature") override suspend fun configureWebViewForBlobDownload( webView: WebView, callback: Callback, ) { withContext(dispatchers.main()) { - WebViewCompat.addDocumentStartJavaScript(webView, blobDownloadScript(), setOf("*")) + webViewBlobDownloader.addBlobDownloadSupport(webView) + WebViewCompat.addWebMessageListener( webView, "ddgBlobDownloadObj", @@ -93,108 +89,11 @@ class ImportGooglePasswordBlobConsumer @Inject constructor( }.onFailure { callback.onCsvError() } } else if (message.data?.startsWith("Ping:") == true) { val locationRef = message.data.toString().encode().md5().toString() - saveReplyProxyForBlobDownload(sourceOrigin.toString(), replyProxy, locationRef) - } - } - - private suspend fun saveReplyProxyForBlobDownload( - originUrl: String, - replyProxy: JavaScriptReplyProxy, - locationHref: String? = null, - ) { - withContext(dispatchers.io()) { // FF check has disk IO - if (true) { - // if (webViewBlobDownloadFeature.fixBlobDownloadWithIframes().isEnabled()) { - val frameProxies = fixedReplyProxyMap[originUrl]?.toMutableMap() ?: mutableMapOf() - // if location.href is not passed, we fall back to origin - val safeLocationHref = locationHref ?: originUrl - frameProxies[safeLocationHref] = replyProxy - fixedReplyProxyMap[originUrl] = frameProxies - } else { - replyProxyMap[originUrl] = replyProxy - } + webViewBlobDownloader.storeReplyProxy(sourceOrigin.toString(), replyProxy, locationRef) } } - @SuppressLint("RequiresFeature") // it's already checked in isBlobDownloadWebViewFeatureEnabled override suspend fun postMessageToConvertBlobToDataUri(url: String) { - withContext(dispatchers.main()) { // main because postMessage is not always safe in another thread - if (true) { - // if (withContext(dispatchers.io()) { webViewBlobDownloadFeature.fixBlobDownloadWithIframes().isEnabled() }) { - for ((key, proxies) in fixedReplyProxyMap) { - if (sameOrigin(url.removePrefix("blob:"), key)) { - for (replyProxy in proxies.values) { - replyProxy.postMessage(url) - } - return@withContext - } - } - } else { - for ((key, value) in replyProxyMap) { - if (sameOrigin(url.removePrefix("blob:"), key)) { - value.postMessage(url) - return@withContext - } - } - } - } - } - - private fun sameOrigin( - firstUrl: String, - secondUrl: String, - ): Boolean { - return kotlin.runCatching { - val firstUri = Uri.parse(firstUrl) - val secondUri = Uri.parse(secondUrl) - - firstUri.host == secondUri.host && firstUri.scheme == secondUri.scheme && firstUri.port == secondUri.port - }.getOrNull() ?: return false - } - - private fun blobDownloadScript(): String { - val script = """ - window.__url_to_blob_collection = {}; - - const original_createObjectURL = URL.createObjectURL; - - URL.createObjectURL = function () { - const blob = arguments[0]; - const url = original_createObjectURL.call(this, ...arguments); - if (blob instanceof Blob) { - __url_to_blob_collection[url] = blob; - } - return url; - } - - function blobToBase64DataUrl(blob) { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onloadend = function() { - resolve(reader.result); - } - reader.onerror = function() { - reject(new Error('Failed to read Blob object')); - } - reader.readAsDataURL(blob); - }); - } - - const pingMessage = 'Ping:' + window.location.href - ddgBlobDownloadObj.postMessage(pingMessage) - - ddgBlobDownloadObj.onmessage = function(event) { - if (event.data.startsWith('blob:')) { - const blob = window.__url_to_blob_collection[event.data]; - if (blob) { - blobToBase64DataUrl(blob).then((dataUrl) => { - ddgBlobDownloadObj.postMessage(dataUrl); - }); - } - } - } - """.trimIndent() - - return script + webViewBlobDownloader.convertBlobToDataUri(url) } } diff --git a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/importing/gpm/webflow/ImportGooglePasswordsWebFlowActivity.kt b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/importing/gpm/webflow/ImportGooglePasswordsWebFlowActivity.kt index a5ca8ee8a6cb..8acf4763fe1a 100644 --- a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/importing/gpm/webflow/ImportGooglePasswordsWebFlowActivity.kt +++ b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/importing/gpm/webflow/ImportGooglePasswordsWebFlowActivity.kt @@ -22,11 +22,11 @@ import androidx.fragment.app.commit import com.duckduckgo.anvil.annotations.ContributeToActivityStarter import com.duckduckgo.anvil.annotations.InjectWith import com.duckduckgo.autofill.api.AutofillScreens.ImportGooglePassword.AutofillImportViaGooglePasswordManagerScreen -import com.duckduckgo.autofill.api.AutofillScreens.ImportGooglePassword.Result.Companion.RESULT_KEY -import com.duckduckgo.autofill.api.AutofillScreens.ImportGooglePassword.Result.Companion.RESULT_KEY_DETAILS -import com.duckduckgo.autofill.api.AutofillScreens.ImportGooglePassword.Result.UserCancelled import com.duckduckgo.autofill.impl.R import com.duckduckgo.autofill.impl.databinding.ActivityImportGooglePasswordsWebflowBinding +import com.duckduckgo.autofill.impl.importing.gpm.webflow.ImportGooglePasswordsWebFlowFragment.Companion.Result.Companion.RESULT_KEY +import com.duckduckgo.autofill.impl.importing.gpm.webflow.ImportGooglePasswordsWebFlowFragment.Companion.Result.Companion.RESULT_KEY_DETAILS +import com.duckduckgo.autofill.impl.importing.gpm.webflow.ImportGooglePasswordsWebFlowFragment.Companion.Result.UserCancelled import com.duckduckgo.common.ui.DuckDuckGoActivity import com.duckduckgo.common.ui.viewbinding.viewBinding import com.duckduckgo.di.scopes.ActivityScope diff --git a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/importing/gpm/webflow/ImportGooglePasswordsWebFlowFragment.kt b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/importing/gpm/webflow/ImportGooglePasswordsWebFlowFragment.kt index e6714f1316be..57169877e243 100644 --- a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/importing/gpm/webflow/ImportGooglePasswordsWebFlowFragment.kt +++ b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/importing/gpm/webflow/ImportGooglePasswordsWebFlowFragment.kt @@ -19,6 +19,7 @@ package com.duckduckgo.autofill.impl.importing.gpm.webflow import android.annotation.SuppressLint import android.content.Intent import android.os.Bundle +import android.os.Parcelable import android.view.View import android.webkit.WebSettings import android.webkit.WebView @@ -35,10 +36,6 @@ import com.duckduckgo.anvil.annotations.InjectWith import com.duckduckgo.app.statistics.pixels.Pixel import com.duckduckgo.autofill.api.AutofillCapabilityChecker import com.duckduckgo.autofill.api.AutofillFragmentResultsPlugin -import com.duckduckgo.autofill.api.AutofillScreens.ImportGooglePassword.Result -import com.duckduckgo.autofill.api.AutofillScreens.ImportGooglePassword.Result.Companion.RESULT_KEY -import com.duckduckgo.autofill.api.AutofillScreens.ImportGooglePassword.Result.Companion.RESULT_KEY_DETAILS -import com.duckduckgo.autofill.api.AutofillWebMessageRequest import com.duckduckgo.autofill.api.BrowserAutofill import com.duckduckgo.autofill.api.CredentialAutofillDialogFactory import com.duckduckgo.autofill.api.domain.app.LoginCredentials @@ -46,14 +43,20 @@ import com.duckduckgo.autofill.api.domain.app.LoginTriggerType import com.duckduckgo.autofill.impl.R import com.duckduckgo.autofill.impl.databinding.FragmentImportGooglePasswordsWebflowBinding import com.duckduckgo.autofill.impl.importing.CsvPasswordImporter +import com.duckduckgo.autofill.impl.importing.CsvPasswordImporter.ParseResult.Error +import com.duckduckgo.autofill.impl.importing.CsvPasswordImporter.ParseResult.Success import com.duckduckgo.autofill.impl.importing.PasswordImporter +import com.duckduckgo.autofill.impl.importing.gpm.webflow.ImportGooglePasswordsWebFlowFragment.Companion.Result.Companion.RESULT_KEY +import com.duckduckgo.autofill.impl.importing.gpm.webflow.ImportGooglePasswordsWebFlowFragment.Companion.Result.Companion.RESULT_KEY_DETAILS import com.duckduckgo.autofill.impl.importing.gpm.webflow.ImportGooglePasswordsWebFlowViewModel.ViewState.NavigatingBack import com.duckduckgo.autofill.impl.importing.gpm.webflow.ImportGooglePasswordsWebFlowViewModel.ViewState.ShowingWebContent import com.duckduckgo.autofill.impl.importing.gpm.webflow.ImportGooglePasswordsWebFlowViewModel.ViewState.UserCancelledImportFlow import com.duckduckgo.autofill.impl.importing.gpm.webflow.ImportGooglePasswordsWebFlowWebChromeClient.ProgressListener import com.duckduckgo.autofill.impl.importing.gpm.webflow.ImportGooglePasswordsWebFlowWebViewClient.NewPageCallback -import com.duckduckgo.autofill.impl.importing.gpm.webflow.autofill.ImportGooglePasswordAutofillCallback -import com.duckduckgo.autofill.impl.importing.gpm.webflow.autofill.ImportGooglePasswordAutofillEventListener +import com.duckduckgo.autofill.impl.importing.gpm.webflow.autofill.NoOpAutofillCallback +import com.duckduckgo.autofill.impl.importing.gpm.webflow.autofill.NoOpAutofillEventListener +import com.duckduckgo.autofill.impl.importing.gpm.webflow.autofill.NoOpEmailProtectionInContextSignupFlowListener +import com.duckduckgo.autofill.impl.importing.gpm.webflow.autofill.NoOpEmailProtectionUserPromptListener import com.duckduckgo.common.ui.DuckDuckGoFragment import com.duckduckgo.common.ui.viewbinding.viewBinding import com.duckduckgo.common.utils.ConflatedJob @@ -65,6 +68,7 @@ import com.duckduckgo.user.agent.api.UserAgentProvider import javax.inject.Inject import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import kotlinx.parcelize.Parcelize import timber.log.Timber @InjectWith(FragmentScope::class) @@ -72,8 +76,10 @@ class ImportGooglePasswordsWebFlowFragment : DuckDuckGoFragment(R.layout.fragment_import_google_passwords_webflow), ProgressListener, NewPageCallback, - ImportGooglePasswordAutofillEventListener, - ImportGooglePasswordAutofillCallback, + NoOpAutofillCallback, + NoOpEmailProtectionInContextSignupFlowListener, + NoOpEmailProtectionUserPromptListener, + NoOpAutofillEventListener, GooglePasswordBlobConsumer.Callback { @Inject @@ -109,6 +115,9 @@ class ImportGooglePasswordsWebFlowFragment : @Inject lateinit var csvPasswordImporter: CsvPasswordImporter + @Inject + lateinit var browserAutofillConfigurator: BrowserAutofill.Configurator + @Inject lateinit var passwordImporter: PasswordImporter @@ -223,7 +232,13 @@ class ImportGooglePasswordsWebFlowFragment : private fun configureAutofill(it: WebView) { lifecycleScope.launch { - browserAutofill.addJsInterface(it, this@ImportGooglePasswordsWebFlowFragment, CUSTOM_FLOW_TAB_ID) + browserAutofill.addJsInterface( + it, + this@ImportGooglePasswordsWebFlowFragment, + this@ImportGooglePasswordsWebFlowFragment, + this@ImportGooglePasswordsWebFlowFragment, + CUSTOM_FLOW_TAB_ID, + ) } autofillFragmentResultListeners.getPlugins().forEach { plugin -> @@ -260,6 +275,7 @@ class ImportGooglePasswordsWebFlowFragment : private fun getToolbar() = (activity as ImportGooglePasswordsWebFlowActivity).binding.includeToolbar.toolbar override fun onPageStarted(url: String?) { + browserAutofillConfigurator.configureAutofillForCurrentPage(binding.webView, url) viewModel.onPageStarted(url) } @@ -268,20 +284,20 @@ class ImportGooglePasswordsWebFlowFragment : } override suspend fun onCredentialsAvailableToInject( - autofillWebMessageRequest: AutofillWebMessageRequest, + originalUrl: String, credentials: List, triggerType: LoginTriggerType, ) { Timber.i("cdr Credentials available to autofill (%d creds available)", credentials.size) withContext(dispatchers.main()) { val url = binding.webView.url ?: return@withContext - if (url != autofillWebMessageRequest.originalPageUrl) { + if (url != originalUrl) { Timber.w("WebView url has changed since autofill request; bailing") return@withContext } val dialog = credentialAutofillDialogFactory.autofillSelectCredentialsDialog( - autofillWebMessageRequest, + url, credentials, triggerType, CUSTOM_FLOW_TAB_ID, @@ -292,13 +308,33 @@ class ImportGooglePasswordsWebFlowFragment : override suspend fun onCsvAvailable(csv: String) { Timber.i("cdr CSV available %s", csv) - val passwords = csvPasswordImporter.readCsv(csv) - val result = passwordImporter.importPasswords(passwords) - Timber.i("cdr Imported %d passwords (# duplicates = %d", result.savedCredentialIds.size, result.duplicatedPasswords.size) - val resultBundle = Bundle().also { - it.putParcelable(RESULT_KEY_DETAILS, Result.Success(result.savedCredentialIds.size)) + val parseResult = csvPasswordImporter.readCsv(csv) + when (parseResult) { + is Success -> { + passwordImporter.importPasswords(parseResult.loginCredentialsToImport) + } + + Error -> { + Timber.e("cdr Error parsing CSV") + } } + + val resultBundle = Bundle().also { it.putParcelable(RESULT_KEY_DETAILS, parseResult) } setFragmentResult(RESULT_KEY, resultBundle) + + /** + * val result = csvPasswordImporter.importCsv(csv) + * val resultDetails = when (result) { + * is Success -> { + * Timber.i("cdr Found %d passwords; Imported %d passwords", result.numberPasswordsInSource, result.passwordIdsImported.size) + * Result.Success(foundInImport = result.numberPasswordsInSource, importedCount = result.passwordIdsImported.size) + * } + * + * ImportResult.Error -> Result.Error + * } + * val resultBundle = Bundle().also { it.putParcelable(RESULT_KEY_DETAILS, resultDetails) } + * + */ } override suspend fun onCsvError() { @@ -309,9 +345,45 @@ class ImportGooglePasswordsWebFlowFragment : setFragmentResult(RESULT_KEY, resultBundle) } + override fun onShareCredentialsForAutofill( + originalUrl: String, + selectedCredentials: LoginCredentials, + ) { + if (binding.webView.url != originalUrl) { + Timber.w("WebView url has changed since autofill request; bailing") + return + } + browserAutofill.injectCredentials(selectedCredentials) + } + + override fun onNoCredentialsChosenForAutofill(originalUrl: String) { + if (binding.webView.url != originalUrl) { + Timber.w("WebView url has changed since autofill request; bailing") + return + } + browserAutofill.injectCredentials(null) + } + companion object { private const val STARTING_URL = "https://passwords.google.com/options?ep=1" private const val CUSTOM_FLOW_TAB_ID = "import-passwords-webflow" private const val SELECT_CREDENTIALS_FRAGMENT_TAG = "autofillSelectCredentialsDialog" + + sealed interface Result : Parcelable { + + companion object { + const val RESULT_KEY = "importResult" + const val RESULT_KEY_DETAILS = "importResultDetails" + } + + @Parcelize + data class Success(val importedCount: Int, val foundInImport: Int) : Result + + @Parcelize + data class UserCancelled(val stage: String) : Result + + @Parcelize + data object Error : Result + } } } diff --git a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/importing/gpm/webflow/PasswordImporterCssScriptLoader.kt b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/importing/gpm/webflow/PasswordImporterCssScriptLoader.kt index 7abf2df16f66..593ea668a2c2 100644 --- a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/importing/gpm/webflow/PasswordImporterCssScriptLoader.kt +++ b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/importing/gpm/webflow/PasswordImporterCssScriptLoader.kt @@ -16,6 +16,8 @@ package com.duckduckgo.autofill.impl.importing.gpm.webflow +import com.duckduckgo.autofill.impl.importing.gpm.feature.AutofillImportPasswordsFeature +import com.duckduckgo.autofill.impl.importing.gpm.feature.AutofillImportPasswordsFeatureStore import com.duckduckgo.common.utils.DispatcherProvider import com.duckduckgo.di.scopes.FragmentScope import com.squareup.anvil.annotations.ContributesBinding @@ -30,6 +32,8 @@ interface PasswordImporterScriptLoader { @ContributesBinding(FragmentScope::class) class PasswordImporterCssScriptLoader @Inject constructor( private val dispatchers: DispatcherProvider, + private val importPasswordsFeature: AutofillImportPasswordsFeature, + private val featureSettingsStore: AutofillImportPasswordsFeatureStore, ) : PasswordImporterScriptLoader { private lateinit var contentScopeJS: String @@ -37,54 +41,19 @@ class PasswordImporterCssScriptLoader @Inject constructor( override suspend fun getScript(): String { return withContext(dispatchers.io()) { getContentScopeJS() - .replace(CONTENT_SCOPE_PLACEHOLDER, getContentScopeJson()) + .replace(CONTENT_SCOPE_PLACEHOLDER, getContentScopeJson(loadSettingsJson())) .replace(USER_UNPROTECTED_DOMAINS_PLACEHOLDER, getUnprotectedDomainsJson()) .replace(USER_PREFERENCES_PLACEHOLDER, getUserPreferencesJson()) } } - private fun getContentScopeJson( - showHintSignInButton: Boolean = true, - showHintSettingsButton: Boolean = true, - showHintExportButton: Boolean = true, - ): String = ( + private fun getContentScopeJson(settingsJson: String): String = ( """{ "features":{ "passwordImport" : { "state": "enabled", "exceptions": [], - "settings": { - "settingsButton": { - "highlight": { - "enabled": $showHintSettingsButton, - "selector": "bla bla" - }, - "autotap": { - "enabled": false, - "selector": "bla bla" - } - }, - "exportButton": { - "highlight": { - "enabled": $showHintExportButton, - "selector": "bla bla" - }, - "autotap": { - "enabled": false, - "selector": "bla bla" - } - }, - "signInButton": { - "highlight":{ - "enabled": $showHintSignInButton, - "selector": "bla bla" - }, - "autotap": { - "enabled": false, - "selector": "bla bla" - } - } - } + "settings": $settingsJson } }, "unprotectedTemporary":[] @@ -93,6 +62,10 @@ class PasswordImporterCssScriptLoader @Inject constructor( """.trimMargin() ) + private suspend fun loadSettingsJson(): String { + return featureSettingsStore.getAutofillImportPasswordsSettings() + } + private fun getUserPreferencesJson(): String { return """ { diff --git a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/importing/gpm/webflow/autofill/AutofillNoOpCallbacks.kt b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/importing/gpm/webflow/autofill/AutofillNoOpCallbacks.kt index 7339119699f2..8c9a35f141f7 100644 --- a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/importing/gpm/webflow/autofill/AutofillNoOpCallbacks.kt +++ b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/importing/gpm/webflow/autofill/AutofillNoOpCallbacks.kt @@ -17,28 +17,93 @@ package com.duckduckgo.autofill.impl.importing.gpm.webflow.autofill import com.duckduckgo.autofill.api.AutofillEventListener -import com.duckduckgo.autofill.api.AutofillWebMessageRequest import com.duckduckgo.autofill.api.Callback +import com.duckduckgo.autofill.api.EmailProtectionInContextSignupFlowListener +import com.duckduckgo.autofill.api.EmailProtectionUserPromptListener import com.duckduckgo.autofill.api.domain.app.LoginCredentials +import com.duckduckgo.autofill.api.domain.app.LoginTriggerType -interface ImportGooglePasswordAutofillCallback : Callback { +interface NoOpAutofillCallback : Callback { + override suspend fun onCredentialsAvailableToInject( + originalUrl: String, + credentials: List, + triggerType: LoginTriggerType, + ) { + } + + override suspend fun onCredentialsAvailableToSave( + currentUrl: String, + credentials: LoginCredentials, + ) { + } override suspend fun onGeneratedPasswordAvailableToUse( - autofillWebMessageRequest: AutofillWebMessageRequest, + originalUrl: String, username: String?, generatedPassword: String, - ) {} + ) { + } + + override fun noCredentialsAvailable(originalUrl: String) { + } + + override fun onCredentialsSaved(savedCredentials: LoginCredentials) { + } +} + +interface NoOpAutofillEventListener : AutofillEventListener { + override fun onAcceptGeneratedPassword(originalUrl: String) { + } + + override fun onRejectGeneratedPassword(originalUrl: String) { + } + + override fun onUseEmailProtectionPersonalAddress( + originalUrl: String, + duckAddress: String, + ) { + } + + override fun onUseEmailProtectionPrivateAlias( + originalUrl: String, + duckAddress: String, + ) { + } - override fun onCredentialsSaved(savedCredentials: LoginCredentials) {} + override fun onSelectedToSignUpForInContextEmailProtection() { + } - override fun showNativeChooseEmailAddressPrompt(autofillWebMessageRequest: AutofillWebMessageRequest) {} - override suspend fun onCredentialsAvailableToSave(autofillWebMessageRequest: AutofillWebMessageRequest, credentials: LoginCredentials) {} - override fun showNativeInContextEmailProtectionSignupPrompt(autofillWebMessageRequest: AutofillWebMessageRequest) {} + override fun onEndOfEmailProtectionInContextSignupFlow() { + } + + override fun onShareCredentialsForAutofill( + originalUrl: String, + selectedCredentials: LoginCredentials, + ) { + } + + override fun onNoCredentialsChosenForAutofill(originalUrl: String) { + } + + override fun onSavedCredentials(credentials: LoginCredentials) { + } + + override fun onUpdatedCredentials(credentials: LoginCredentials) { + } + + override fun onAutofillStateChange() { + } +} + +interface NoOpEmailProtectionInContextSignupFlowListener : EmailProtectionInContextSignupFlowListener { + override fun closeInContextSignup() { + } } -interface ImportGooglePasswordAutofillEventListener : AutofillEventListener { - override fun onSelectedToSignUpForInContextEmailProtection(autofillWebMessageRequest: AutofillWebMessageRequest) {} - override fun onSavedCredentials(credentials: LoginCredentials) {} - override fun onUpdatedCredentials(credentials: LoginCredentials) {} - override fun onAutofillStateChange() {} +interface NoOpEmailProtectionUserPromptListener : EmailProtectionUserPromptListener { + override fun showNativeInContextEmailProtectionSignupPrompt() { + } + + override fun showNativeChooseEmailAddressPrompt() { + } } diff --git a/autofill/autofill-internal/src/main/java/com/duckduckgo/autofill/internal/AutofillInternalSettingsActivity.kt b/autofill/autofill-internal/src/main/java/com/duckduckgo/autofill/internal/AutofillInternalSettingsActivity.kt index 174a2d286979..d9c9aa3e90a0 100644 --- a/autofill/autofill-internal/src/main/java/com/duckduckgo/autofill/internal/AutofillInternalSettingsActivity.kt +++ b/autofill/autofill-internal/src/main/java/com/duckduckgo/autofill/internal/AutofillInternalSettingsActivity.kt @@ -23,6 +23,7 @@ import android.content.Intent import android.os.Bundle import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.content.IntentCompat import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle.State.STARTED import androidx.lifecycle.lifecycleScope @@ -32,8 +33,7 @@ import com.duckduckgo.app.tabs.BrowserNav import com.duckduckgo.autofill.api.AutofillFeature import com.duckduckgo.autofill.api.AutofillScreens.AutofillSettingsScreen import com.duckduckgo.autofill.api.AutofillScreens.ImportGooglePassword -import com.duckduckgo.autofill.api.AutofillScreens.ImportGooglePassword.Result.Error -import com.duckduckgo.autofill.api.AutofillScreens.ImportGooglePassword.Result.Success +import com.duckduckgo.autofill.api.AutofillScreens.ImportGooglePassword.AutofillImportViaGooglePasswordManagerScreen import com.duckduckgo.autofill.api.AutofillSettingsLaunchSource.InternalDevSettings import com.duckduckgo.autofill.api.domain.app.LoginCredentials import com.duckduckgo.autofill.api.email.EmailManager @@ -45,6 +45,11 @@ import com.duckduckgo.autofill.impl.importing.CsvPasswordImporter.ParseResult import com.duckduckgo.autofill.impl.importing.PasswordImporter import com.duckduckgo.autofill.impl.importing.PasswordImporter.ImportResult.Finished import com.duckduckgo.autofill.impl.importing.PasswordImporter.ImportResult.InProgress +import com.duckduckgo.autofill.impl.importing.gpm.webflow.ImportGooglePasswordsWebFlowFragment +import com.duckduckgo.autofill.impl.importing.gpm.webflow.ImportGooglePasswordsWebFlowFragment.Companion.Result.Companion.RESULT_KEY_DETAILS +import com.duckduckgo.autofill.impl.importing.gpm.webflow.ImportGooglePasswordsWebFlowFragment.Companion.Result.Error +import com.duckduckgo.autofill.impl.importing.gpm.webflow.ImportGooglePasswordsWebFlowFragment.Companion.Result.Success +import com.duckduckgo.autofill.impl.importing.gpm.webflow.ImportGooglePasswordsWebFlowFragment.Companion.Result.UserCancelled import com.duckduckgo.autofill.impl.reporting.AutofillSiteBreakageReportingDataStore import com.duckduckgo.autofill.impl.store.InternalAutofillStore import com.duckduckgo.autofill.impl.store.NeverSavedSiteRepository @@ -130,7 +135,7 @@ class AutofillInternalSettingsActivity : DuckDuckGoActivity() { logcat { "cdr onActivityResult for CSV file request. resultCode=${result.resultCode}. uri=$fileUrl" } if (fileUrl != null) { - lifecycleScope.launch { + lifecycleScope.launch(dispatchers.io()) { when (val parseResult = csvPasswordImporter.readCsv(fileUrl)) { is ParseResult.Success -> { val jobId = passwordImporter.importPasswords(parseResult.loginCredentialsToImport) @@ -149,7 +154,7 @@ class AutofillInternalSettingsActivity : DuckDuckGoActivity() { logcat { "cdr onActivityResult for Google Password Manager import flow. resultCode=${result.resultCode}" } if (result.resultCode == Activity.RESULT_OK) { - observePasswordInputUpdates() + } } @@ -258,7 +263,7 @@ class AutofillInternalSettingsActivity : DuckDuckGoActivity() { startActivity(browserNav.openInNewTab(this, googlePasswordsUrl)) } binding.importPasswordsLaunchGooglePasswordCustomFlow.setClickListener { - val intent = globalActivityStarter.startIntent(this, ImportGooglePassword.AutofillImportViaGooglePasswordManagerScreen) + val intent = globalActivityStarter.startIntent(this, AutofillImportViaGooglePasswordManagerScreen) importGooglePasswordsFlowLauncher.launch(intent) } diff --git a/browser-api/src/main/java/com/duckduckgo/browser/api/WebViewMessageListening.kt b/browser-api/src/main/java/com/duckduckgo/browser/api/WebViewMessageListening.kt deleted file mode 100644 index b612dea72384..000000000000 --- a/browser-api/src/main/java/com/duckduckgo/browser/api/WebViewMessageListening.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2024 DuckDuckGo - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.duckduckgo.browser.api - -interface WebViewMessageListening { - - suspend fun isWebMessageListenerSupported(): Boolean -} diff --git a/node_modules/@duckduckgo/content-scope-scripts/build/android/contentScope.js b/node_modules/@duckduckgo/content-scope-scripts/build/android/contentScope.js index a377b75dcdef..92047e082cde 100644 --- a/node_modules/@duckduckgo/content-scope-scripts/build/android/contentScope.js +++ b/node_modules/@duckduckgo/content-scope-scripts/build/android/contentScope.js @@ -2266,16 +2266,11 @@ */ /** - * @description - * * A wrapper for messaging on Windows. * * This requires 3 methods to be available, see {@link WindowsMessagingConfig} for details * - * @example - * - * ```javascript - * [[include:messaging/lib/examples/windows.example.js]]``` + * @document messaging/lib/examples/windows.example.js * */ // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -2473,8 +2468,7 @@ * Depending on where the script is running, we may want to restrict access to those globals. On the native * side those handlers `window.chrome.webview` handlers might be deleted and replaces with in-scope variables, such as: * - * ```ts - * [[include:messaging/lib/examples/windows.example.js]]``` + * [Example](./examples/windows.example.js) * */ class WindowsMessagingConfig { @@ -2589,9 +2583,6 @@ } /** - * @module Messaging Schema - * - * @description * These are all the shared data types used throughout. Transports receive these types and * can choose how to deliver the message to their respective native platforms. * @@ -2600,6 +2591,9 @@ * - Subscriptions via {@link Subscription} * * Note: For backwards compatibility, some platforms may alter the data shape within the transport. + * + * @module Messaging Schema + * */ /** @@ -2729,8 +2723,6 @@ } /** - * - * @description * * A wrapper for messaging on WebKit platforms. It supports modern WebKit messageHandlers * along with encryption for older versions (like macOS Catalina) @@ -3046,10 +3038,7 @@ * * Please see {@link WebkitMessagingTransport} for details on how messages are sent/received * - * @example Webkit Messaging - * - * ```javascript - * [[include:messaging/lib/examples/webkit.example.js]]``` + * [Example](./examples/webkit.example.js) */ class WebkitMessagingConfig { /** @@ -3158,17 +3147,11 @@ } /** - * @description * * A wrapper for messaging on Android. * * You must share a {@link AndroidMessagingConfig} instance between features * - * @example - * - * ```javascript - * [[include:messaging/lib/examples/windows.example.js]]``` - * */ // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -3323,7 +3306,7 @@ * - `$messageSecret` matches {@link AndroidMessagingConfig.messageSecret} * - `$message` is JSON string that represents one of {@link MessageResponse} or {@link SubscriptionEvent} * - * ```kotlin + * ``` * object ReplyHandler { * fun constructReply(message: String, messageCallback: String, messageSecret: String): String { * return """ @@ -3502,9 +3485,6 @@ } /** - * @module Messaging - * @category Libraries - * @description * * An abstraction for communications between JavaScript and host platforms. * @@ -3524,6 +3504,7 @@ * - Schema: {@link "Messaging Schema"} * - Implementation Guide: {@link "Messaging Implementation Guide"} * + * @module Messaging */ /** @@ -3630,9 +3611,6 @@ * It's useful for debugging, and for enabling scripts to run in * other environments - for example, testing in a browser without the need * for a full integration - * - * ```js - * [[include:messaging/lib/examples/test.example.js]]``` */ class TestTransportConfig { /** @@ -11705,9 +11683,6 @@ } /** - * @module Duck Player Thumbnails - * - * @description * * ## Decision flow for `mouseover` (appending Dax) * @@ -11758,7 +11733,7 @@ * - `eventTarget` matches a CSS selector in `[config] allowedEventTargets` * - otherwise, do nothing * - * [[include:injected/src/features/duckplayer/thumbnails.md]] + * @module Duck Player Thumbnails */ @@ -12287,8 +12262,6 @@ /** * @module Duck Player Video Overlay * - * @description - * * ## Decision flow for appending the Video Overlays * * We'll try to append the full video overlay (or small Dax icon) onto the main video player @@ -13008,9 +12981,6 @@ } /** - * @module Duck Player Overlays - * - * @description * * Duck Player Overlays are either the small Dax icons that appear on top of video thumbnails * when browsing YouTube. These icons allow users to open the video in Duck Player. @@ -13039,9 +13009,9 @@ * * For example, to enable the Duck Player Overlay on YouTube, the following config is used: * - * ```json - * [[include:injected/integration-test/test-pages/duckplayer/config/overlays-live.json]]``` + * [📝 JSON example](../../integration-test/test-pages/duckplayer/config/overlays-live.json) * + * @module Duck Player Overlays */ /** @@ -13251,7 +13221,6 @@ /** * @module Android integration - * @category Content Scope Scripts Integrations */ function initCode () { diff --git a/node_modules/@duckduckgo/content-scope-scripts/build/android/pages/duckplayer/js/index.css b/node_modules/@duckduckgo/content-scope-scripts/build/android/pages/duckplayer/js/index.css index f0299a0ba328..800ff880f59b 100644 --- a/node_modules/@duckduckgo/content-scope-scripts/build/android/pages/duckplayer/js/index.css +++ b/node_modules/@duckduckgo/content-scope-scripts/build/android/pages/duckplayer/js/index.css @@ -205,13 +205,16 @@ body[data-display=app] { .SwitchBarMobile_stateHidden { display: none; } -.SwitchBarMobile_label { +.SwitchBarMobile_labelRow { display: flex; align-items: center; justify-content: space-between; width: 100%; gap: 16px; } +.SwitchBarMobile_label { + pointer-events: none; +} .SwitchBarMobile_checkbox { } .SwitchBarMobile_text { diff --git a/node_modules/@duckduckgo/content-scope-scripts/build/android/pages/duckplayer/js/index.js b/node_modules/@duckduckgo/content-scope-scripts/build/android/pages/duckplayer/js/index.js index ee121a2eb0d3..092ca0a0c623 100644 --- a/node_modules/@duckduckgo/content-scope-scripts/build/android/pages/duckplayer/js/index.js +++ b/node_modules/@duckduckgo/content-scope-scripts/build/android/pages/duckplayer/js/index.js @@ -1260,8 +1260,8 @@ M.__r = 0; } function P(n2, l3, u3, t3, i3, o3, r3, f3, e3, c3, s3) { - var a3, p3, y3, d3, w3, _3 = t3 && t3.__k || v, g2 = l3.length; - for (u3.__d = e3, $(u3, l3, _3), e3 = u3.__d, a3 = 0; a3 < g2; a3++) + var a3, p3, y3, d3, w3, _3 = t3 && t3.__k || v, g3 = l3.length; + for (u3.__d = e3, $(u3, l3, _3), e3 = u3.__d, a3 = 0; a3 < g3; a3++) null != (y3 = u3.__k[a3]) && (p3 = -1 === y3.__i ? h : _3[y3.__i] || h, y3.__i = a3, O(n2, y3, p3, i3, o3, r3, f3, e3, c3, s3), d3 = y3.__e, y3.ref && p3.ref != y3.ref && (p3.ref && N(p3.ref, null, y3), s3.push(y3.ref, y3.__c || d3, y3)), null == w3 && null != d3 && (w3 = d3), 65536 & y3.__u || p3.__k === y3.__k ? e3 = I(y3, e3, n2) : "function" == typeof y3.type && void 0 !== y3.__d ? e3 = y3.__d : d3 && (e3 = d3.nextSibling), y3.__d = void 0, y3.__u &= -196609); u3.__d = e3, u3.__e = w3; } @@ -1349,14 +1349,14 @@ }; } function O(n2, u3, t3, i3, o3, r3, f3, e3, c3, s3) { - var a3, h3, v3, p3, w3, _3, g2, m2, x3, C3, S2, M2, $2, I2, H, L2, T3 = u3.type; + var a3, h3, v3, p3, w3, _3, g3, m2, x3, C3, S2, M2, $2, I2, H, L2, T3 = u3.type; if (void 0 !== u3.constructor) return null; 128 & t3.__u && (c3 = !!(32 & t3.__u), r3 = [e3 = u3.__e = t3.__e]), (a3 = l.__b) && a3(u3); n: if ("function" == typeof T3) try { - if (m2 = u3.props, x3 = "prototype" in T3 && T3.prototype.render, C3 = (a3 = T3.contextType) && i3[a3.__c], S2 = a3 ? C3 ? C3.props.value : a3.__ : i3, t3.__c ? g2 = (h3 = u3.__c = t3.__c).__ = h3.__E : (x3 ? u3.__c = h3 = new T3(m2, S2) : (u3.__c = h3 = new k(m2, S2), h3.constructor = T3, h3.render = q), C3 && C3.sub(h3), h3.props = m2, h3.state || (h3.state = {}), h3.context = S2, h3.__n = i3, v3 = h3.__d = true, h3.__h = [], h3._sb = []), x3 && null == h3.__s && (h3.__s = h3.state), x3 && null != T3.getDerivedStateFromProps && (h3.__s == h3.state && (h3.__s = d({}, h3.__s)), d(h3.__s, T3.getDerivedStateFromProps(m2, h3.__s))), p3 = h3.props, w3 = h3.state, h3.__v = u3, v3) + if (m2 = u3.props, x3 = "prototype" in T3 && T3.prototype.render, C3 = (a3 = T3.contextType) && i3[a3.__c], S2 = a3 ? C3 ? C3.props.value : a3.__ : i3, t3.__c ? g3 = (h3 = u3.__c = t3.__c).__ = h3.__E : (x3 ? u3.__c = h3 = new T3(m2, S2) : (u3.__c = h3 = new k(m2, S2), h3.constructor = T3, h3.render = q), C3 && C3.sub(h3), h3.props = m2, h3.state || (h3.state = {}), h3.context = S2, h3.__n = i3, v3 = h3.__d = true, h3.__h = [], h3._sb = []), x3 && null == h3.__s && (h3.__s = h3.state), x3 && null != T3.getDerivedStateFromProps && (h3.__s == h3.state && (h3.__s = d({}, h3.__s)), d(h3.__s, T3.getDerivedStateFromProps(m2, h3.__s))), p3 = h3.props, w3 = h3.state, h3.__v = u3, v3) x3 && null == T3.getDerivedStateFromProps && null != h3.componentWillMount && h3.componentWillMount(), x3 && null != h3.componentDidMount && h3.__h.push(h3.componentDidMount); else { if (x3 && null == T3.getDerivedStateFromProps && m2 !== p3 && null != h3.componentWillReceiveProps && h3.componentWillReceiveProps(m2, S2), !h3.__e && (null != h3.shouldComponentUpdate && false === h3.shouldComponentUpdate(m2, h3.__s, S2) || u3.__v === t3.__v)) { @@ -1379,7 +1379,7 @@ do { h3.__d = false, $2 && $2(u3), a3 = h3.render(h3.props, h3.state, h3.context), h3.state = h3.__s; } while (h3.__d && ++I2 < 25); - h3.state = h3.__s, null != h3.getChildContext && (i3 = d(d({}, i3), h3.getChildContext())), x3 && !v3 && null != h3.getSnapshotBeforeUpdate && (_3 = h3.getSnapshotBeforeUpdate(p3, w3)), P(n2, y(L2 = null != a3 && a3.type === b && null == a3.key ? a3.props.children : a3) ? L2 : [L2], u3, t3, i3, o3, r3, f3, e3, c3, s3), h3.base = u3.__e, u3.__u &= -161, h3.__h.length && f3.push(h3), g2 && (h3.__E = h3.__ = null); + h3.state = h3.__s, null != h3.getChildContext && (i3 = d(d({}, i3), h3.getChildContext())), x3 && !v3 && null != h3.getSnapshotBeforeUpdate && (_3 = h3.getSnapshotBeforeUpdate(p3, w3)), P(n2, y(L2 = null != a3 && a3.type === b && null == a3.key ? a3.props.children : a3) ? L2 : [L2], u3, t3, i3, o3, r3, f3, e3, c3, s3), h3.base = u3.__e, u3.__u &= -161, h3.__h.length && f3.push(h3), g3 && (h3.__E = h3.__ = null); } catch (n3) { if (u3.__v = null, c3 || null != r3) { for (u3.__u |= c3 ? 160 : 128; e3 && 8 === e3.nodeType && e3.nextSibling; ) @@ -1408,7 +1408,7 @@ }); } function z(u3, t3, i3, o3, r3, f3, e3, c3, s3) { - var a3, v3, p3, d3, _3, g2, m2, b2 = i3.props, k3 = t3.props, C3 = t3.type; + var a3, v3, p3, d3, _3, g3, m2, b2 = i3.props, k3 = t3.props, C3 = t3.type; if ("svg" === C3 ? r3 = "http://www.w3.org/2000/svg" : "math" === C3 ? r3 = "http://www.w3.org/1998/Math/MathML" : r3 || (r3 = "http://www.w3.org/1999/xhtml"), null != f3) { for (a3 = 0; a3 < f3.length; a3++) if ((_3 = f3[a3]) && "setAttribute" in _3 == !!C3 && (C3 ? _3.localName === C3 : 3 === _3.nodeType)) { @@ -1438,13 +1438,13 @@ A(u3, a3, null, _3, r3); } for (a3 in k3) - _3 = k3[a3], "children" == a3 ? d3 = _3 : "dangerouslySetInnerHTML" == a3 ? v3 = _3 : "value" == a3 ? g2 = _3 : "checked" == a3 ? m2 = _3 : c3 && "function" != typeof _3 || b2[a3] === _3 || A(u3, a3, _3, b2[a3], r3); + _3 = k3[a3], "children" == a3 ? d3 = _3 : "dangerouslySetInnerHTML" == a3 ? v3 = _3 : "value" == a3 ? g3 = _3 : "checked" == a3 ? m2 = _3 : c3 && "function" != typeof _3 || b2[a3] === _3 || A(u3, a3, _3, b2[a3], r3); if (v3) c3 || p3 && (v3.__html === p3.__html || v3.__html === u3.innerHTML) || (u3.innerHTML = v3.__html), t3.__k = []; else if (p3 && (u3.innerHTML = ""), P(u3, y(d3) ? d3 : [d3], t3, i3, o3, "foreignObject" === C3 ? "http://www.w3.org/1999/xhtml" : r3, f3, e3, f3 ? f3[0] : i3.__k && x(i3, 0), c3, s3), null != f3) for (a3 = f3.length; a3--; ) w(f3[a3]); - c3 || (a3 = "value", "progress" === C3 && null == g2 ? u3.removeAttribute("value") : void 0 !== g2 && (g2 !== u3[a3] || "progress" === C3 && !g2 || "option" === C3 && g2 !== b2[a3]) && A(u3, a3, g2, b2[a3], r3), a3 = "checked", void 0 !== m2 && m2 !== u3[a3] && A(u3, a3, m2, b2[a3], r3)); + c3 || (a3 = "value", "progress" === C3 && null == g3 ? u3.removeAttribute("value") : void 0 !== g3 && (g3 !== u3[a3] || "progress" === C3 && !g3 || "option" === C3 && g3 !== b2[a3]) && A(u3, a3, g3, b2[a3], r3), a3 = "checked", void 0 !== m2 && m2 !== u3[a3] && A(u3, a3, m2, b2[a3], r3)); } return u3; } @@ -1610,6 +1610,16 @@ var u3 = r2.context[n2.__c], i3 = d2(t2++, 9); return i3.c = n2, u3 ? (null == i3.__ && (i3.__ = true, u3.sub(r2)), u3.props.value) : n2.__; } + function g2() { + var n2 = d2(t2++, 11); + if (!n2.__) { + for (var u3 = r2.__v; null !== u3 && !u3.__m && null !== u3.__; ) + u3 = u3.__; + var i3 = u3.__m || (u3.__m = [0, 0]); + n2.__ = "P" + i3[0] + "-" + i3[1]++; + } + return n2.__; + } function j2() { for (var n2; n2 = f2.shift(); ) if (n2.__P && n2.__H) @@ -2419,6 +2429,7 @@ switchBar: "SwitchBarMobile_switchBar", stateExiting: "SwitchBarMobile_stateExiting", stateHidden: "SwitchBarMobile_stateHidden", + labelRow: "SwitchBarMobile_labelRow", label: "SwitchBarMobile_label", checkbox: "SwitchBarMobile_checkbox", text: "SwitchBarMobile_text", @@ -2494,13 +2505,14 @@ // pages/duckplayer/app/components/Switch.jsx var import_classnames4 = __toESM(require_classnames(), 1); - function Switch({ checked, onChange, platformName = "ios" }) { + function Switch({ checked, onChange, id, platformName = "ios" }) { return /* @__PURE__ */ _( "button", { role: "switch", "aria-checked": checked, onClick: onChange, + id, className: (0, import_classnames4.default)(Switch_default.switch, { [Switch_default.ios]: platformName === "ios", [Switch_default.android]: platformName === "android" @@ -2514,6 +2526,7 @@ function SwitchBarMobile({ platformName }) { const { onChange, onDone, state } = x2(SwitchContext); const { t: t3 } = useTypedTranslation(); + const inputId = g2(); function blockClick(e3) { if (state === "exiting") { return e3.preventDefault(); @@ -2529,12 +2542,13 @@ [SwitchBarMobile_default.stateExiting]: state === "exiting", [SwitchBarMobile_default.stateHidden]: state === "completed" }); - return /* @__PURE__ */ _("div", { class: classes, "data-state": state, onTransitionEnd }, /* @__PURE__ */ _("label", { onClick: blockClick, class: SwitchBarMobile_default.label }, /* @__PURE__ */ _("span", { className: SwitchBarMobile_default.text }, t3("keepEnabled")), /* @__PURE__ */ _( + return /* @__PURE__ */ _("div", { class: classes, "data-state": state, "data-allow-animation": "true", onTransitionEnd }, /* @__PURE__ */ _("div", { class: SwitchBarMobile_default.labelRow, onClick: blockClick }, /* @__PURE__ */ _("label", { for: inputId, class: SwitchBarMobile_default.label }, /* @__PURE__ */ _("span", { className: SwitchBarMobile_default.text }, t3("keepEnabled"))), /* @__PURE__ */ _( Switch, { checked: state !== "showing", onChange, - platformName + platformName, + id: inputId } ))); } diff --git a/package-lock.json b/package-lock.json index a6141f485b03..c76e94af7b6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.7.tgz", "integrity": "sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==", "dev": true, + "license": "MIT", "dependencies": { "@babel/highlight": "^7.25.7", "picocolors": "^1.0.0" @@ -39,6 +40,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz", "integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -48,6 +50,7 @@ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.7.tgz", "integrity": "sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.25.7", "chalk": "^2.4.2", @@ -62,17 +65,19 @@ "version": "10.16.0", "resolved": "https://registry.npmjs.org/@duckduckgo/autoconsent/-/autoconsent-10.16.0.tgz", "integrity": "sha512-731vJAQ/wOu+zpGOgXgnuTyhpF7LCuiVmmOFVLUU9RNHX1357aEm+7D2SdyPAuHj+uDpGkhTbaz/MPX6qFoInQ==", + "license": "MPL-2.0", "dependencies": { "tldts-experimental": "^6.1.37" } }, "node_modules/@duckduckgo/autofill": { "resolved": "git+ssh://git@github.com/duckduckgo/duckduckgo-autofill.git#1fee787458d13f8ed07f9fe81aecd6e59609339e", - "hasInstallScript": true + "hasInstallScript": true, + "license": "Apache-2.0" }, "node_modules/@duckduckgo/content-scope-scripts": { - "resolved": "git+ssh://git@github.com/duckduckgo/content-scope-scripts.git#c55305c796c0e5afcd902702d7d264620bf04993", - "hasInstallScript": true, + "resolved": "git+ssh://git@github.com/duckduckgo/content-scope-scripts.git#6f9fe14d516c681b87be1f059664bea1c6a176b5", + "license": "Apache-2.0", "workspaces": [ "injected", "special-pages", @@ -88,13 +93,15 @@ } }, "node_modules/@duckduckgo/privacy-reference-tests": { - "resolved": "git+ssh://git@github.com/duckduckgo/privacy-reference-tests.git#6133e7d9d9cd5f1b925cab1971b4d785dc639df7" + "resolved": "git+ssh://git@github.com/duckduckgo/privacy-reference-tests.git#6133e7d9d9cd5f1b925cab1971b4d785dc639df7", + "license": "Apache-2.0" }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -109,6 +116,7 @@ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.0.0" } @@ -118,6 +126,7 @@ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.0.0" } @@ -127,6 +136,7 @@ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -136,13 +146,15 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -153,6 +165,7 @@ "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.1.0.tgz", "integrity": "sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw==", "dev": true, + "license": "MIT", "dependencies": { "@rollup/pluginutils": "^3.0.8" }, @@ -165,6 +178,7 @@ "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz", "integrity": "sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw==", "dev": true, + "license": "MIT", "dependencies": { "@rollup/pluginutils": "^3.1.0", "@types/resolve": "1.17.1", @@ -185,6 +199,7 @@ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "0.0.39", "estree-walker": "^1.0.1", @@ -201,13 +216,15 @@ "version": "0.0.39", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { - "version": "22.7.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.6.tgz", - "integrity": "sha512-/d7Rnj0/ExXDMcioS78/kf1lMzYk4BZV8MZGTBKzTGZ6/406ukkbYlIsZmMPhcR5KlkunDHQLrtAVmSq7r+mSw==", + "version": "22.7.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.7.tgz", + "integrity": "sha512-SRxCrrg9CL/y54aiMCG3edPKdprgMVGDXjA3gB8UmmBW5TcXzRUYAh8EWzTnSJFAd1rgImPELza+A3bJ+qxz8Q==", "dev": true, + "license": "MIT", "dependencies": { "undici-types": "~6.19.2" } @@ -217,6 +234,7 @@ "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -226,6 +244,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz", "integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -238,6 +257,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^1.9.0" }, @@ -249,13 +269,15 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/builtin-modules": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" }, @@ -268,6 +290,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -282,6 +305,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "1.1.3" } @@ -290,19 +314,22 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -312,6 +339,7 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.0" } @@ -320,7 +348,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fsevents": { "version": "2.3.3", @@ -328,6 +357,7 @@ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -341,6 +371,7 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -350,6 +381,7 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -359,6 +391,7 @@ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -371,6 +404,7 @@ "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", "dev": true, + "license": "MIT", "dependencies": { "builtin-modules": "^3.3.0" }, @@ -386,6 +420,7 @@ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", "dev": true, + "license": "MIT", "dependencies": { "hasown": "^2.0.2" }, @@ -400,13 +435,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/jest-worker": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -421,6 +458,7 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -430,6 +468,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -441,31 +480,36 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -478,6 +522,7 @@ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, + "license": "MIT", "dependencies": { "safe-buffer": "^5.1.0" } @@ -487,6 +532,7 @@ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, + "license": "MIT", "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -504,6 +550,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", "dev": true, + "license": "MIT", "bin": { "rollup": "dist/bin/rollup" }, @@ -520,6 +567,7 @@ "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==", "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.10.4", "jest-worker": "^26.2.1", @@ -548,13 +596,15 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/serialize-javascript": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "randombytes": "^2.1.0" } @@ -564,6 +614,7 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -573,6 +624,7 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, + "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -583,6 +635,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^3.0.0" }, @@ -595,6 +648,7 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -607,6 +661,7 @@ "resolved": "https://registry.npmjs.org/terser/-/terser-5.36.0.tgz", "integrity": "sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -623,12 +678,14 @@ "node_modules/tldts-core": { "version": "6.1.52", "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.52.tgz", - "integrity": "sha512-j4OxQI5rc1Ve/4m/9o2WhWSC4jGc4uVbCINdOEJRAraCi0YqTqgMcxUx7DbmuP0G3PCixoof/RZB0Q5Kh9tagw==" + "integrity": "sha512-j4OxQI5rc1Ve/4m/9o2WhWSC4jGc4uVbCINdOEJRAraCi0YqTqgMcxUx7DbmuP0G3PCixoof/RZB0Q5Kh9tagw==", + "license": "MIT" }, "node_modules/tldts-experimental": { "version": "6.1.52", "resolved": "https://registry.npmjs.org/tldts-experimental/-/tldts-experimental-6.1.52.tgz", "integrity": "sha512-cBEsjlg5CoO6n3GofBxjUQ+b1uTmeNbhwADrzdVCOMzby7et1Woa3b7MkuqRvgK9K0u7lMvwxmVs8NKizW5HmQ==", + "license": "MIT", "dependencies": { "tldts-core": "^6.1.52" } @@ -637,7 +694,8 @@ "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true + "dev": true, + "license": "MIT" } } }