Merge branch 'main' into tools/PM-6819/reactive-generator
This commit is contained in:
commit
8421a6dcea
|
@ -57,6 +57,7 @@ libs/common/src/admin-console @bitwarden/team-admin-console-dev
|
|||
libs/admin-console @bitwarden/team-admin-console-dev
|
||||
|
||||
## Billing team files ##
|
||||
apps/browser/src/billing @bitwarden/team-billing-dev
|
||||
apps/web/src/app/billing @bitwarden/team-billing-dev
|
||||
libs/angular/src/billing @bitwarden/team-billing-dev
|
||||
libs/common/src/billing @bitwarden/team-billing-dev
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "قفل المخزن"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "دعم الوضع الخاص تجريبي وبعض الميزات محدودة."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "الحقول المخصصة"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Anbarı kilidlə"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Gizli rejim dəstəyi təcrübidir və bəzi özəlliklər limitlidir."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Özəl sahələr"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Заблакіраваць сховішча"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Прыватны рэжым - гэта эксперыментальная функцыя і некаторыя магчымасці ў ім абмежаваны."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Карыстальніцкія палі"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Заключване на трезора"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Поддръжката на частния режим е експериментална и някои функционалности са ограничени."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Допълнителни полета"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "ভল্ট লক করুন"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Private mode support is experimental and some features are limited."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "পছন্দসই ক্ষেত্র"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Lock the vault"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Private mode support is experimental and some features are limited."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Custom fields"
|
||||
},
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
"message": "Bitwarden"
|
||||
},
|
||||
"extName": {
|
||||
"message": "Bitwarden Password Manager",
|
||||
"message": "Bitwarden - Gestor de contrasenyes",
|
||||
"description": "Extension name, MUST be less than 40 characters (Safari restriction)"
|
||||
},
|
||||
"extDesc": {
|
||||
"message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information",
|
||||
"message": "A casa, a la feina o en moviment, Bitwarden protegeix totes les contrasenyes, claus de pas i informació sensible",
|
||||
"description": "Extension description, MUST be less than 112 characters (Safari restriction)"
|
||||
},
|
||||
"loginOrCreateNewAccount": {
|
||||
|
@ -173,10 +173,10 @@
|
|||
"message": "Canvia la contrasenya mestra"
|
||||
},
|
||||
"continueToWebApp": {
|
||||
"message": "Continue to web app?"
|
||||
"message": "Continua cap a l'aplicació web?"
|
||||
},
|
||||
"changeMasterPasswordOnWebConfirmation": {
|
||||
"message": "You can change your master password on the Bitwarden web app."
|
||||
"message": "Podeu canviar la vostra contrasenya mestra a l'aplicació web de Bitwarden."
|
||||
},
|
||||
"fingerprintPhrase": {
|
||||
"message": "Frase d'empremta digital",
|
||||
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Tanca la caixa forta"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "El suport del mode privat és experimental i algunes funcions són limitades."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Camps personalitzats"
|
||||
},
|
||||
|
@ -3001,7 +2998,7 @@
|
|||
"description": "Notification message for when saving credentials has failed."
|
||||
},
|
||||
"success": {
|
||||
"message": "Success"
|
||||
"message": "Èxit"
|
||||
},
|
||||
"removePasskey": {
|
||||
"message": "Suprimeix la clau de pas"
|
||||
|
@ -3010,26 +3007,26 @@
|
|||
"message": "Clau de pas suprimida"
|
||||
},
|
||||
"unassignedItemsBannerNotice": {
|
||||
"message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console."
|
||||
"message": "Avís: els elements de l'organització no assignats ja no són visibles a la visualització de Totes les caixes fortes i només es poden accedir des de la Consola d'administració."
|
||||
},
|
||||
"unassignedItemsBannerSelfHostNotice": {
|
||||
"message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console."
|
||||
"message": "Avís: el 16 de maig de 2024, els elements de l'organització no assignats deixaran de ser visibles a la visualització de Totes les caixes fortes i només es podran accedir des de la Consola d'administració."
|
||||
},
|
||||
"unassignedItemsBannerCTAPartOne": {
|
||||
"message": "Assign these items to a collection from the",
|
||||
"message": "Assigna aquests elements a una col·lecció de",
|
||||
"description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible."
|
||||
},
|
||||
"unassignedItemsBannerCTAPartTwo": {
|
||||
"message": "to make them visible.",
|
||||
"message": "per fer-los visibles.",
|
||||
"description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible."
|
||||
},
|
||||
"adminConsole": {
|
||||
"message": "Admin Console"
|
||||
"message": "Consola d'administració"
|
||||
},
|
||||
"errorAssigningTargetCollection": {
|
||||
"message": "Error assigning target collection."
|
||||
"message": "S'ha produït un error en assignar la col·lecció de destinació."
|
||||
},
|
||||
"errorAssigningTargetFolder": {
|
||||
"message": "Error assigning target folder."
|
||||
"message": "S'ha produït un error en assignar la carpeta de destinació."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
"message": "Bitwarden"
|
||||
},
|
||||
"extName": {
|
||||
"message": "Bitwarden Password Manager",
|
||||
"message": "Bitwarden - Správce hesel",
|
||||
"description": "Extension name, MUST be less than 40 characters (Safari restriction)"
|
||||
},
|
||||
"extDesc": {
|
||||
"message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information",
|
||||
"message": "Bitwarden zabezpečí všechna Vaše hesla, přístupové klíče a citlivé informace doma, v práci nebo na cestách",
|
||||
"description": "Extension description, MUST be less than 112 characters (Safari restriction)"
|
||||
},
|
||||
"loginOrCreateNewAccount": {
|
||||
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Zamkne trezor."
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Podpora soukromého režimu je experimentální a některé funkce jsou omezené."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Vlastní pole"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Cloi'r gell"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Private mode support is experimental and some features are limited."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Meysydd addasedig"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Lås boksen"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Understøttelse af privat tilstand er eksperimentel, og nogle funktioner er begrænsede."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Brugerdefinerede felter"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Den Tresor sperren"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Die Unterstützung des privaten Modus ist experimentell und einige Funktionen sind eingeschränkt."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Benutzerdefinierte Felder"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Κλειδώστε το vault"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Η υποστήριξη ιδιωτικής λειτουργίας είναι πειραματική και ορισμένες δυνατότητες είναι περιορισμένες."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Προσαρμοσμένα Πεδία"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Lock the vault"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Private mode support is experimental and some features are limited."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Custom fields"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Lock the vault"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Private mode support is experimental and some features are limited."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Custom fields"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Bloquear la caja fuerte"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "El soporte en modo privado es experimental y algunas características son limitadas."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Campos personalizados"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Lukusta hoidla"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Privaatrežiimi toetus on katsejärgus, mistõttu mõned funktsioonid on piiratud."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Kohandatud väljad"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Blokeatu kutxa gotorra"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Modu pribatuko euskarria esperimentala da eta ezaugarri batzuk mugatuak dira."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Eremu pertsonalizatuak"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "قفل گاوصندوق"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "پشتیبانی حالت خصوصی آزمایشی است و برخی از ویژگیها محدود هستند."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "فیلدهای سفارشی"
|
||||
},
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
"message": "Bitwarden"
|
||||
},
|
||||
"extName": {
|
||||
"message": "Bitwarden – Salasanahallinta",
|
||||
"message": "Bitwarden Salasanahallinta",
|
||||
"description": "Extension name, MUST be less than 40 characters (Safari restriction)"
|
||||
},
|
||||
"extDesc": {
|
||||
"message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information",
|
||||
"message": "Kotona, töissä tai reissussa, Bitwarden suojaa helposti salasanasi, suojausavaimesi ja arkaluonteiset tietosi.",
|
||||
"description": "Extension description, MUST be less than 112 characters (Safari restriction)"
|
||||
},
|
||||
"loginOrCreateNewAccount": {
|
||||
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Lukitse holvi"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Yksityisen tilan tuki on kokeellinen ja jotkin ominaisuudet toimivat rajoitetusti."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Lisäkentät"
|
||||
},
|
||||
|
@ -2828,7 +2825,7 @@
|
|||
"message": "Korvataanko suojausavain?"
|
||||
},
|
||||
"overwritePasskeyAlert": {
|
||||
"message": "Kohde sisältää jo suojausavaimen. Haluatko varmasti korvata nykyisen salasanan?"
|
||||
"message": "Kohde sisältää jo suojausavaimen. Haluatko varmasti korvata nykyisen suojausavaimen?"
|
||||
},
|
||||
"featureNotSupported": {
|
||||
"message": "Ominaisuutta ei vielä tueta"
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "I-lock ang vault"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Ang suporta sa private mode ay eksperimental at limitado ang ilang mga tampok."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Pasadyang mga patlang"
|
||||
},
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
"message": "Bitwarden"
|
||||
},
|
||||
"extName": {
|
||||
"message": "Bitwarden Password Manager",
|
||||
"message": "Gestionnaire de mots de passe Bitwarden",
|
||||
"description": "Extension name, MUST be less than 40 characters (Safari restriction)"
|
||||
},
|
||||
"extDesc": {
|
||||
"message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information",
|
||||
"message": "Chez vous, au travail, n'importe où, Bitwarden sécurise mots de passe, clés d'accès et informations sensibles",
|
||||
"description": "Extension description, MUST be less than 112 characters (Safari restriction)"
|
||||
},
|
||||
"loginOrCreateNewAccount": {
|
||||
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Verrouiller le coffre"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "La prise en charge de la navigation privée est expérimentale et certaines fonctionnalités sont limitées."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Champs personnalisés"
|
||||
},
|
||||
|
@ -3001,7 +2998,7 @@
|
|||
"description": "Notification message for when saving credentials has failed."
|
||||
},
|
||||
"success": {
|
||||
"message": "Success"
|
||||
"message": "Succès"
|
||||
},
|
||||
"removePasskey": {
|
||||
"message": "Retirer la clé d'identification (passkey)"
|
||||
|
@ -3016,15 +3013,15 @@
|
|||
"message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console."
|
||||
},
|
||||
"unassignedItemsBannerCTAPartOne": {
|
||||
"message": "Assign these items to a collection from the",
|
||||
"message": "Ajouter ces éléments à une collection depuis la",
|
||||
"description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible."
|
||||
},
|
||||
"unassignedItemsBannerCTAPartTwo": {
|
||||
"message": "to make them visible.",
|
||||
"message": "pour les rendre visibles.",
|
||||
"description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible."
|
||||
},
|
||||
"adminConsole": {
|
||||
"message": "Admin Console"
|
||||
"message": "Console Admin"
|
||||
},
|
||||
"errorAssigningTargetCollection": {
|
||||
"message": "Error assigning target collection."
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Lock the vault"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Private mode support is experimental and some features are limited."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Custom fields"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "נעל את הכספת"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "המצב הפרטי הוא במסגרת ניסוי וחלק מהיכולות מוגבלות."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "שדות מותאמים אישית"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "वॉल्ट लॉक करें"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "निजी मोड समर्थन प्रायोगिक है और कुछ सुविधाएँ सीमित हैं।"
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Custom Fields"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Zaključaj trezor"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Podrška za privatni način rada je eksperimentalna, a neke su značajke ograničene."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Prilagođena polja"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "A széf zárolása"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "A privát mód támogatása kísérleti és néhány funkció korlátozott."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Egyedi mezők"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Kunci brankas"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Dukungan mode pribadi bersifat eksperimental dan beberapa fitur terbatas."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Ruas Khusus"
|
||||
},
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
"description": "Extension name, MUST be less than 40 characters (Safari restriction)"
|
||||
},
|
||||
"extDesc": {
|
||||
"message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information",
|
||||
"message": "A casa, al lavoro, o in viaggio, Bitwarden protegge tutte le tue password, passkey, e informazioni sensibili",
|
||||
"description": "Extension description, MUST be less than 112 characters (Safari restriction)"
|
||||
},
|
||||
"loginOrCreateNewAccount": {
|
||||
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Blocca la cassaforte"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Il supporto della modalità privata è sperimentale e alcune funzionalità sono limitate."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Campi personalizzati"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "保管庫をロック"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "プライベートモードのサポートは実験的であり、一部機能は制限されています。"
|
||||
},
|
||||
"customFields": {
|
||||
"message": "カスタムフィールド"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Lock the vault"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Private mode support is experimental and some features are limited."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Custom fields"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Lock the vault"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Private mode support is experimental and some features are limited."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Custom fields"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "ವಾಲ್ಟ್ ಅನ್ನು ಲಾಕ್ ಮಾಡಿ"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Private mode support is experimental and some features are limited."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "ಕಸ್ಟಮ್ ಕ್ಷೇತ್ರಗಳು"
|
||||
},
|
||||
|
|
|
@ -176,7 +176,7 @@
|
|||
"message": "웹 앱에서 계속하시겠용?"
|
||||
},
|
||||
"changeMasterPasswordOnWebConfirmation": {
|
||||
"message": "You can change your master password on the Bitwarden web app."
|
||||
"message": "Bitwarden 웹 앱에서 마스터 비밀번호를 변경할 수 있습니다."
|
||||
},
|
||||
"fingerprintPhrase": {
|
||||
"message": "지문 구절",
|
||||
|
@ -229,10 +229,10 @@
|
|||
"message": "Bitwarden 도움말 센터"
|
||||
},
|
||||
"communityForums": {
|
||||
"message": "Explore Bitwarden community forums"
|
||||
"message": "Bitwarden 커뮤니티 포럼 탐색하기"
|
||||
},
|
||||
"contactSupport": {
|
||||
"message": "Contact Bitwarden support"
|
||||
"message": "Bitwarden 지원에 문의하기"
|
||||
},
|
||||
"sync": {
|
||||
"message": "동기화"
|
||||
|
@ -275,7 +275,7 @@
|
|||
"message": "길이"
|
||||
},
|
||||
"passwordMinLength": {
|
||||
"message": "Minimum password length"
|
||||
"message": "최소 비밀번호 길이"
|
||||
},
|
||||
"uppercase": {
|
||||
"message": "대문자 (A-Z)"
|
||||
|
@ -333,7 +333,7 @@
|
|||
"message": "비밀번호"
|
||||
},
|
||||
"totp": {
|
||||
"message": "Authenticator secret"
|
||||
"message": "인증기 비밀 키"
|
||||
},
|
||||
"passphrase": {
|
||||
"message": "패스프레이즈"
|
||||
|
@ -375,10 +375,10 @@
|
|||
"message": "기타"
|
||||
},
|
||||
"unlockMethodNeededToChangeTimeoutActionDesc": {
|
||||
"message": "Set up an unlock method to change your vault timeout action."
|
||||
"message": "잠금 해제 방법을 설정하여 보관함의 시간 초과 동작을 변경하세요."
|
||||
},
|
||||
"unlockMethodNeeded": {
|
||||
"message": "Set up an unlock method in Settings"
|
||||
"message": "설정에서 잠금 해제 수단 설정하기"
|
||||
},
|
||||
"rateExtension": {
|
||||
"message": "확장 프로그램 평가"
|
||||
|
@ -421,7 +421,7 @@
|
|||
"message": "지금 잠그기"
|
||||
},
|
||||
"lockAll": {
|
||||
"message": "Lock all"
|
||||
"message": "모두 잠그기"
|
||||
},
|
||||
"immediately": {
|
||||
"message": "즉시"
|
||||
|
@ -484,7 +484,7 @@
|
|||
"message": "마스터 비밀번호를 재입력해야 합니다."
|
||||
},
|
||||
"masterPasswordMinlength": {
|
||||
"message": "Master password must be at least $VALUE$ characters long.",
|
||||
"message": "마스터 비밀번호는 최소 $VALUE$자 이상이어야 합니다.",
|
||||
"description": "The Master Password must be at least a specific number of characters long.",
|
||||
"placeholders": {
|
||||
"value": {
|
||||
|
@ -500,10 +500,10 @@
|
|||
"message": "계정 생성이 완료되었습니다! 이제 로그인하실 수 있습니다."
|
||||
},
|
||||
"youSuccessfullyLoggedIn": {
|
||||
"message": "You successfully logged in"
|
||||
"message": "로그인에 성공했습니다."
|
||||
},
|
||||
"youMayCloseThisWindow": {
|
||||
"message": "You may close this window"
|
||||
"message": "이제 창을 닫으실 수 있습니다."
|
||||
},
|
||||
"masterPassSent": {
|
||||
"message": "마스터 비밀번호 힌트가 담긴 이메일을 보냈습니다."
|
||||
|
@ -528,16 +528,16 @@
|
|||
"message": "선택한 항목을 이 페이지에서 자동 완성할 수 없습니다. 대신 정보를 직접 복사 / 붙여넣기하여 사용하십시오."
|
||||
},
|
||||
"totpCaptureError": {
|
||||
"message": "Unable to scan QR code from the current webpage"
|
||||
"message": "현재 웹페이지에서 QR 코드를 스캔할 수 없습니다"
|
||||
},
|
||||
"totpCaptureSuccess": {
|
||||
"message": "Authenticator key added"
|
||||
"message": "인증 키를 추가했습니다"
|
||||
},
|
||||
"totpCapture": {
|
||||
"message": "Scan authenticator QR code from current webpage"
|
||||
"message": "현재 웹페이지에서 QR 코드 스캔하기"
|
||||
},
|
||||
"copyTOTP": {
|
||||
"message": "Copy Authenticator key (TOTP)"
|
||||
"message": "인증서 키 (TOTP) 복사"
|
||||
},
|
||||
"loggedOut": {
|
||||
"message": "로그아웃됨"
|
||||
|
@ -644,7 +644,7 @@
|
|||
"description": "This is the folder for uncategorized items"
|
||||
},
|
||||
"enableAddLoginNotification": {
|
||||
"message": "Ask to add login"
|
||||
"message": "로그인을 추가할 건지 물어보기"
|
||||
},
|
||||
"addLoginNotificationDesc": {
|
||||
"message": "\"로그인 추가 알림\"을 사용하면 새 로그인을 사용할 때마다 보관함에 그 로그인을 추가할 것인지 물어봅니다."
|
||||
|
@ -653,7 +653,7 @@
|
|||
"message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts."
|
||||
},
|
||||
"showCardsCurrentTab": {
|
||||
"message": "Show cards on Tab page"
|
||||
"message": "탭 페이지에 카드 표시"
|
||||
},
|
||||
"showCardsCurrentTabDesc": {
|
||||
"message": "List card items on the Tab page for easy auto-fill."
|
||||
|
@ -679,7 +679,7 @@
|
|||
"message": "예, 지금 저장하겠습니다."
|
||||
},
|
||||
"enableChangedPasswordNotification": {
|
||||
"message": "Ask to update existing login"
|
||||
"message": "현재 로그인으로 업데이트할 건지 묻기"
|
||||
},
|
||||
"changedPasswordNotificationDesc": {
|
||||
"message": "Ask to update a login's password when a change is detected on a website."
|
||||
|
@ -703,7 +703,7 @@
|
|||
"message": "Unlock your Bitwarden vault to complete the auto-fill request."
|
||||
},
|
||||
"notificationUnlock": {
|
||||
"message": "Unlock"
|
||||
"message": "잠금 해제"
|
||||
},
|
||||
"enableContextMenuItem": {
|
||||
"message": "Show context menu options"
|
||||
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "보관함 잠그기"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "시크릿 모드 지원은 실험적이며 일부 기능이 제한됩니다."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "사용자 지정 필드"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Užrakinti saugyklą"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Privataus režimo palaikymas yra eksperimentinis, o kai kurios funkcijos yra ribotos."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Pasirinktiniai laukai"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Aizslēgt glabātavu"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Personiskā stāvokļa atbalsts ir izmēģinājuma, un dažas iespējas ir ierobežotas."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Pielāgoti lauki"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "നിലവറ പൂട്ടുക"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Private mode support is experimental and some features are limited."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "ഇഷ്ടാനുസൃത ഫീൽഡുകൾ"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Lock the vault"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Private mode support is experimental and some features are limited."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Custom fields"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Lock the vault"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Private mode support is experimental and some features are limited."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Custom fields"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Lås hvelvet"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Støtte for privatmodus er eksperimentelt, og noen funksjoner er begrenset."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Tilpassede felter"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Lock the vault"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Private mode support is experimental and some features are limited."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Custom fields"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Kluis vergrendelen"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Private mode ondersteuning is experimenteel en sommige functies zijn beperkt."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Aangepaste velden"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Lock the vault"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Private mode support is experimental and some features are limited."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Custom fields"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Lock the vault"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Private mode support is experimental and some features are limited."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Custom fields"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Zablokuj sejf"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Obsługa trybu prywatnego jest eksperymentalna, a niektóre funkcje są ograniczone."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Pola niestandardowe"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Bloquear o cofre"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "O suporte para modo privado é experimental e alguns recursos são limitados."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Campos Personalizados"
|
||||
},
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
"message": "Bitwarden"
|
||||
},
|
||||
"extName": {
|
||||
"message": "Bitwarden Password Manager",
|
||||
"message": "Bitwarden - Gestor de Palavras-passe",
|
||||
"description": "Extension name, MUST be less than 40 characters (Safari restriction)"
|
||||
},
|
||||
"extDesc": {
|
||||
"message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information",
|
||||
"message": "Em casa, no trabalho, em todo o lado, o Bitwarden protege todas as suas palavras-passe e informações sensíveis",
|
||||
"description": "Extension description, MUST be less than 112 characters (Safari restriction)"
|
||||
},
|
||||
"loginOrCreateNewAccount": {
|
||||
|
@ -303,10 +303,10 @@
|
|||
"message": "Incluir número"
|
||||
},
|
||||
"minNumbers": {
|
||||
"message": "Números mínimos"
|
||||
"message": "Mínimo de números"
|
||||
},
|
||||
"minSpecial": {
|
||||
"message": "Caracteres especiais minímos"
|
||||
"message": "Mínimo de caracteres especiais"
|
||||
},
|
||||
"avoidAmbChar": {
|
||||
"message": "Evitar caracteres ambíguos"
|
||||
|
@ -1064,7 +1064,7 @@
|
|||
"message": "Editar as definições do navegador."
|
||||
},
|
||||
"autofillOverlayVisibilityOff": {
|
||||
"message": "Desligado",
|
||||
"message": "Desativado",
|
||||
"description": "Overlay setting select option for disabling autofill overlay"
|
||||
},
|
||||
"autofillOverlayVisibilityOnFieldFocus": {
|
||||
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Bloquear o cofre"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "O suporte do modo privado é experimental e algumas funcionalidades são limitadas."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Campos personalizados"
|
||||
},
|
||||
|
@ -1279,7 +1276,7 @@
|
|||
"message": "Número do passaporte"
|
||||
},
|
||||
"licenseNumber": {
|
||||
"message": "Número da licença"
|
||||
"message": "Número da carta de condução"
|
||||
},
|
||||
"email": {
|
||||
"message": "E-mail"
|
||||
|
@ -1303,7 +1300,7 @@
|
|||
"message": "Cidade / Localidade"
|
||||
},
|
||||
"stateProvince": {
|
||||
"message": "Estado / Província"
|
||||
"message": "Estado / Região"
|
||||
},
|
||||
"zipPostalCode": {
|
||||
"message": "Código postal"
|
||||
|
@ -1443,7 +1440,7 @@
|
|||
"description": "ex. Date this item was updated"
|
||||
},
|
||||
"dateCreated": {
|
||||
"message": "Criado a",
|
||||
"message": "Criado",
|
||||
"description": "ex. Date this item was created"
|
||||
},
|
||||
"datePasswordUpdated": {
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Blocare seif"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Suportul pentru modul privat este experimental, iar unele caracteristici sunt limitate."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Câmpuri particularizate"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Заблокировать хранилище"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Частный режим - экспериментальный, некоторые функции ограничены."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Пользовательские поля"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "සුරක්ෂිතාගාරය ලොක් කරන්න"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Private mode support is experimental and some features are limited."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "අභිරුචි ක්ෂේත්ර"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Zamknúť trezor"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Podpora privátneho režimu je experimentálna a niektoré funkcie sú obmedzené."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Vlastné polia"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Zakleni trezor"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Private mode support is experimental and some features are limited."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Polja po meri"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Закључај сеф"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Подршка за приватни режим је експериментална и неке функције су ограничене."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Прилагођена Поља"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Lås valvet"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Stöd för privat läge är experimentellt och vissa funktioner är begränsade."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Anpassade fält"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Lock the vault"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Private mode support is experimental and some features are limited."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Custom fields"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "ล็อกตู้เซฟ"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Private mode support is experimental and some features are limited."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Custom Fields"
|
||||
},
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
"message": "Bitwarden"
|
||||
},
|
||||
"extName": {
|
||||
"message": "Bitwarden Password Manager",
|
||||
"message": "Bitwarden Parola Yöneticisi",
|
||||
"description": "Extension name, MUST be less than 40 characters (Safari restriction)"
|
||||
},
|
||||
"extDesc": {
|
||||
"message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information",
|
||||
"message": "Bitwarden tüm parolalarınızı, geçiş anahtarlarınızı ve hassas bilgilerinizi güvenle saklar",
|
||||
"description": "Extension description, MUST be less than 112 characters (Safari restriction)"
|
||||
},
|
||||
"loginOrCreateNewAccount": {
|
||||
|
@ -173,10 +173,10 @@
|
|||
"message": "Ana parolayı değiştir"
|
||||
},
|
||||
"continueToWebApp": {
|
||||
"message": "Continue to web app?"
|
||||
"message": "Web uygulamasına devam edilsin mi?"
|
||||
},
|
||||
"changeMasterPasswordOnWebConfirmation": {
|
||||
"message": "You can change your master password on the Bitwarden web app."
|
||||
"message": "Ana parolanızı Bitwarden web uygulamasında değiştirebilirsiniz."
|
||||
},
|
||||
"fingerprintPhrase": {
|
||||
"message": "Parmak izi ifadesi",
|
||||
|
@ -333,7 +333,7 @@
|
|||
"message": "Parola"
|
||||
},
|
||||
"totp": {
|
||||
"message": "Authenticator secret"
|
||||
"message": "Kimlik doğrulama sırrı"
|
||||
},
|
||||
"passphrase": {
|
||||
"message": "Uzun söz"
|
||||
|
@ -528,16 +528,16 @@
|
|||
"message": "Seçilen hesap bu sayfada otomatik olarak doldurulamadı. Lütfen bilgileri elle kopyalayıp yapıştırın."
|
||||
},
|
||||
"totpCaptureError": {
|
||||
"message": "Mevcut web sayfasından QR kodu taranamıyor"
|
||||
"message": "Mevcut web sayfasındaki QR kodu taranamıyor"
|
||||
},
|
||||
"totpCaptureSuccess": {
|
||||
"message": "Kimlik doğrulama anahtarı eklendi"
|
||||
},
|
||||
"totpCapture": {
|
||||
"message": "Mevcut web sayfasından kimlik doğrulayıcı QR kodunu tarayın"
|
||||
"message": "Mevcut web sayfasındaki kimlik doğrulayıcı QR kodunu tarayın"
|
||||
},
|
||||
"copyTOTP": {
|
||||
"message": "Kimlik Doğrulayıcı anahtarını kopyala (TOTP)"
|
||||
"message": "Kimlik doğrulama anahtarını kopyala (TOTP)"
|
||||
},
|
||||
"loggedOut": {
|
||||
"message": "Çıkış yapıldı"
|
||||
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Kasayı kilitle"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Gizli mod desteği deneyseldir ve bazı özellikler kısıtlıdır."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Özel alanlar"
|
||||
},
|
||||
|
@ -2005,7 +2002,7 @@
|
|||
"message": "Klasör seç..."
|
||||
},
|
||||
"noFoldersFound": {
|
||||
"message": "Herhangi bir klasör bulunamadı",
|
||||
"message": "Hiçbir klasör bulunamadı",
|
||||
"description": "Used as a message within the notification bar when no folders are found"
|
||||
},
|
||||
"orgPermissionsUpdatedMustSetPassword": {
|
||||
|
@ -2652,13 +2649,13 @@
|
|||
}
|
||||
},
|
||||
"tryAgain": {
|
||||
"message": "Tekrar deneyin"
|
||||
"message": "Yeniden dene"
|
||||
},
|
||||
"verificationRequiredForActionSetPinToContinue": {
|
||||
"message": "Bu işlem için doğrulama gerekiyor. Devam etmek için bir PIN ayarlayın."
|
||||
"message": "Bu işlem için doğrulama gerekiyor. Devam etmek için bir PIN belirleyin."
|
||||
},
|
||||
"setPin": {
|
||||
"message": "PIN Belirle"
|
||||
"message": "PIN belirle"
|
||||
},
|
||||
"verifyWithBiometrics": {
|
||||
"message": "Biyometri ile doğrula"
|
||||
|
@ -2673,7 +2670,7 @@
|
|||
"message": "Farklı bir yönteme mi ihtiyacınız var?"
|
||||
},
|
||||
"useMasterPassword": {
|
||||
"message": "Ana parolayı kullanın"
|
||||
"message": "Ana parolayı kullan"
|
||||
},
|
||||
"usePin": {
|
||||
"message": "PIN kullan"
|
||||
|
@ -2685,7 +2682,7 @@
|
|||
"message": "E-posta adresinize gönderilen doğrulama kodunu girin."
|
||||
},
|
||||
"resendCode": {
|
||||
"message": "Kodu tekrar gönder"
|
||||
"message": "Kodu yeniden gönder"
|
||||
},
|
||||
"total": {
|
||||
"message": "Toplam"
|
||||
|
@ -2700,19 +2697,19 @@
|
|||
}
|
||||
},
|
||||
"launchDuoAndFollowStepsToFinishLoggingIn": {
|
||||
"message": "DUO'yu başlatın ve oturum açmayı tamamlamak için adımları izleyin."
|
||||
"message": "Duo'yu başlatın ve oturum açmayı tamamlamak için adımları izleyin."
|
||||
},
|
||||
"duoRequiredForAccount": {
|
||||
"message": "Hesabınız için Duo'ya iki adımlı giriş yapmanız gerekiyor."
|
||||
"message": "Hesabınız için Duo iki adımlı giriş gereklidir."
|
||||
},
|
||||
"popoutTheExtensionToCompleteLogin": {
|
||||
"message": "Oturum açma işlemini tamamlamak için uzantıyı açın."
|
||||
"message": "Giriş işlemini tamamlamak için uzantıyı dışarı alın."
|
||||
},
|
||||
"popoutExtension": {
|
||||
"message": "Popout uzantısı"
|
||||
"message": "Uzantıyı dışarı al"
|
||||
},
|
||||
"launchDuo": {
|
||||
"message": "DUO'yu başlat"
|
||||
"message": "Duo'yu başlat"
|
||||
},
|
||||
"importFormatError": {
|
||||
"message": "Veriler doğru biçimlendirilmemiş. Lütfen içe aktarma dosyanızı kontrol edin ve tekrar deneyin."
|
||||
|
@ -2795,7 +2792,7 @@
|
|||
"message": "Geçiş anahtarı klonlanan öğeye kopyalanmayacaktır. Bu öğeyi klonlamaya devam etmek istiyor musunuz?"
|
||||
},
|
||||
"passkeyFeatureIsNotImplementedForAccountsWithoutMasterPassword": {
|
||||
"message": "Açılan sitenin gerektirdiği doğrulama. Bu özellik henüz ana şifresi olmayan hesaplara uygulanmamaktadır."
|
||||
"message": "Site kimlik doğrulaması gerektiriyor. Bu özellik henüz ana parolası olmayan hesaplarda kullanılamaz."
|
||||
},
|
||||
"logInWithPasskey": {
|
||||
"message": "Geçiş anahtarı ile giriş yapılsın mı?"
|
||||
|
@ -2828,13 +2825,13 @@
|
|||
"message": "Geçiş anahtarının üzerine yazılsın mı?"
|
||||
},
|
||||
"overwritePasskeyAlert": {
|
||||
"message": "Bu öğe zaten bir şifre anahtarı içeriyor. Geçerli şifrenin üzerine yazmak istediğinizden emin misiniz?"
|
||||
"message": "Bu kayıt zaten bir geçiş anahtarı içeriyor. Mevcut geçiş anahtarının üzerine yazmak istediğinizden emin misiniz?"
|
||||
},
|
||||
"featureNotSupported": {
|
||||
"message": "Bu özellik henüz desteklenmiyor"
|
||||
},
|
||||
"yourPasskeyIsLocked": {
|
||||
"message": "Şifreyi kullanmak için kimlik doğrulama gerekiyor. Devam etmek için kimliğinizi doğrulayın."
|
||||
"message": "Geçiş anahtarını kullanmak için kimlik doğrulama gerekiyor. Devam etmek için kimliğinizi doğrulayın."
|
||||
},
|
||||
"multifactorAuthenticationCancelled": {
|
||||
"message": "Çok faktörlü kimlik doğrulama iptal edildi"
|
||||
|
@ -2943,10 +2940,10 @@
|
|||
"message": "konum"
|
||||
},
|
||||
"useDeviceOrHardwareKey": {
|
||||
"message": "Cihazınızı veya donanım anahtarınızı kullanın"
|
||||
"message": "Cihazınızı veya donanımsal anahtarınızı kullanın"
|
||||
},
|
||||
"justOnce": {
|
||||
"message": "Yalnızca bir kez"
|
||||
"message": "Yalnızca bir defa"
|
||||
},
|
||||
"alwaysForThisSite": {
|
||||
"message": "Bu site için her zaman"
|
||||
|
@ -2961,23 +2958,23 @@
|
|||
}
|
||||
},
|
||||
"commonImportFormats": {
|
||||
"message": "Ortak formatlar",
|
||||
"message": "Sık kullanılan biçimler",
|
||||
"description": "Label indicating the most common import formats"
|
||||
},
|
||||
"overrideDefaultBrowserAutofillTitle": {
|
||||
"message": "Bitwarden varsayılan şifre yöneticiniz yapılsın mı?",
|
||||
"message": "Bitwarden varsayılan parola yöneticiniz yapılsın mı?",
|
||||
"description": "Dialog title facilitating the ability to override a chrome browser's default autofill behavior"
|
||||
},
|
||||
"overrideDefaultBrowserAutofillDescription": {
|
||||
"message": "Bu seçeneğin göz ardı edilmesi, Bitwarden otomatik doldurma menüsü ile tarayıcınızınki arasında çakışmalara neden olabilir.",
|
||||
"message": "Bu seçeneği göz ardı ederseniz Bitwarden otomatik doldurma menüsüyle tarayıcınızınki arasında çakışma yaşanabilir.",
|
||||
"description": "Dialog message facilitating the ability to override a chrome browser's default autofill behavior"
|
||||
},
|
||||
"overrideDefaultBrowserAutoFillSettings": {
|
||||
"message": "Bitwarden'ı varsayılan şifre yöneticiniz yapın",
|
||||
"message": "Bitwarden'ı varsayılan parola yöneticiniz yapın",
|
||||
"description": "Label for the setting that allows overriding the default browser autofill settings"
|
||||
},
|
||||
"privacyPermissionAdditionNotGrantedTitle": {
|
||||
"message": "Bitwarden varsayılan parola yöneticisi olarak ayarlanamıyor",
|
||||
"message": "Bitwarden varsayılan parola yöneticisi olarak ayarlanamadı",
|
||||
"description": "Title for the dialog that appears when the user has not granted the extension permission to set privacy settings"
|
||||
},
|
||||
"privacyPermissionAdditionNotGrantedDescription": {
|
||||
|
@ -3001,13 +2998,13 @@
|
|||
"description": "Notification message for when saving credentials has failed."
|
||||
},
|
||||
"success": {
|
||||
"message": "Success"
|
||||
"message": "Başarılı"
|
||||
},
|
||||
"removePasskey": {
|
||||
"message": "Remove passkey"
|
||||
"message": "Geçiş anahtarını kaldır"
|
||||
},
|
||||
"passkeyRemoved": {
|
||||
"message": "Passkey removed"
|
||||
"message": "Geçiş anahtarı kaldırıldı"
|
||||
},
|
||||
"unassignedItemsBannerNotice": {
|
||||
"message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console."
|
||||
|
@ -3024,7 +3021,7 @@
|
|||
"description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible."
|
||||
},
|
||||
"adminConsole": {
|
||||
"message": "Admin Console"
|
||||
"message": "Yönetici Konsolu"
|
||||
},
|
||||
"errorAssigningTargetCollection": {
|
||||
"message": "Error assigning target collection."
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Заблокувати сховище"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Приватний режим - це експериментальна функція і деякі можливості обмежені."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Власні поля"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "Khoá kho lưu trữ"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Hỗ trợ cho chế độ riêng tư đang được thử nghiệm và hạn chế một số tính năng."
|
||||
},
|
||||
"customFields": {
|
||||
"message": "Trường tùy chỉnh"
|
||||
},
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "锁定密码库"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "私密模式的支持是实验性的,某些功能会受到限制。"
|
||||
},
|
||||
"customFields": {
|
||||
"message": "自定义字段"
|
||||
},
|
||||
|
@ -3016,11 +3013,11 @@
|
|||
"message": "注意:从 2024 年 5 月 16 日起,未分配的组织项目在「所有密码库」视图中将不再可见,只能通过管理控制台访问。"
|
||||
},
|
||||
"unassignedItemsBannerCTAPartOne": {
|
||||
"message": "Assign these items to a collection from the",
|
||||
"message": "将这些项目分配到集合,通过",
|
||||
"description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible."
|
||||
},
|
||||
"unassignedItemsBannerCTAPartTwo": {
|
||||
"message": "以使其可见。",
|
||||
"message": ",以使其可见。",
|
||||
"description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible."
|
||||
},
|
||||
"adminConsole": {
|
||||
|
|
|
@ -1120,9 +1120,6 @@
|
|||
"commandLockVaultDesc": {
|
||||
"message": "鎖定密碼庫"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "私密模式的支援是實驗性功能,部分功能無法完全發揮作用。"
|
||||
},
|
||||
"customFields": {
|
||||
"message": "自訂欄位"
|
||||
},
|
||||
|
|
|
@ -12,6 +12,7 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs
|
|||
import { EventType } from "@bitwarden/common/enums";
|
||||
import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { EventCollectionService } from "@bitwarden/common/services/event/event-collection.service";
|
||||
import {
|
||||
|
@ -74,9 +75,10 @@ describe("AutofillService", () => {
|
|||
const logService = mock<LogService>();
|
||||
const userVerificationService = mock<UserVerificationService>();
|
||||
const billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
|
||||
const platformUtilsService = mock<PlatformUtilsService>();
|
||||
|
||||
beforeEach(() => {
|
||||
scriptInjectorService = new BrowserScriptInjectorService();
|
||||
scriptInjectorService = new BrowserScriptInjectorService(platformUtilsService, logService);
|
||||
autofillService = new AutofillService(
|
||||
cipherService,
|
||||
autofillSettingsService,
|
||||
|
|
|
@ -3,7 +3,6 @@ import { firstValueFrom } from "rxjs";
|
|||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants";
|
||||
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
|
||||
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||
import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types";
|
||||
|
@ -107,17 +106,13 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
frameId = 0,
|
||||
triggeringOnPageLoad = true,
|
||||
): Promise<void> {
|
||||
// Autofill settings loaded from state can await the active account state indefinitely if
|
||||
// not guarded by an active account check (e.g. the user is logged in)
|
||||
// Autofill user settings loaded from state can await the active account state indefinitely
|
||||
// if not guarded by an active account check (e.g. the user is logged in)
|
||||
const activeAccount = await firstValueFrom(this.accountService.activeAccount$);
|
||||
|
||||
// These settings are not available until the user logs in
|
||||
let overlayVisibility: InlineMenuVisibilitySetting = AutofillOverlayVisibility.Off;
|
||||
let autoFillOnPageLoadIsEnabled = false;
|
||||
const overlayVisibility = await this.getOverlayVisibility();
|
||||
|
||||
if (activeAccount) {
|
||||
overlayVisibility = await this.getOverlayVisibility();
|
||||
}
|
||||
const mainAutofillScript = overlayVisibility
|
||||
? "bootstrap-autofill-overlay.js"
|
||||
: "bootstrap-autofill.js";
|
||||
|
@ -2087,9 +2082,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||
for (let index = 0; index < tabs.length; index++) {
|
||||
const tab = tabs[index];
|
||||
if (tab.url?.startsWith("http")) {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.injectAutofillScripts(tab, 0, false);
|
||||
void this.injectAutofillScripts(tab, 0, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -106,7 +106,6 @@ import { DefaultConfigService } from "@bitwarden/common/platform/services/config
|
|||
import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
|
||||
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
|
||||
import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation";
|
||||
import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/multithread-encrypt.service.implementation";
|
||||
import { FileUploadService } from "@bitwarden/common/platform/services/file-upload/file-upload.service";
|
||||
import { KeyGenerationService } from "@bitwarden/common/platform/services/key-generation.service";
|
||||
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
|
||||
|
@ -210,11 +209,14 @@ import { UpdateBadge } from "../platform/listeners/update-badge";
|
|||
/* eslint-disable no-restricted-imports */
|
||||
import { ChromeMessageSender } from "../platform/messaging/chrome-message.sender";
|
||||
/* eslint-enable no-restricted-imports */
|
||||
import { OffscreenDocumentService } from "../platform/offscreen-document/abstractions/offscreen-document";
|
||||
import { DefaultOffscreenDocumentService } from "../platform/offscreen-document/offscreen-document.service";
|
||||
import { BrowserStateService as StateServiceAbstraction } from "../platform/services/abstractions/browser-state.service";
|
||||
import { BrowserCryptoService } from "../platform/services/browser-crypto.service";
|
||||
import { BrowserEnvironmentService } from "../platform/services/browser-environment.service";
|
||||
import BrowserLocalStorageService from "../platform/services/browser-local-storage.service";
|
||||
import BrowserMemoryStorageService from "../platform/services/browser-memory-storage.service";
|
||||
import { BrowserMultithreadEncryptServiceImplementation } from "../platform/services/browser-multithread-encrypt.service.implementation";
|
||||
import { BrowserScriptInjectorService } from "../platform/services/browser-script-injector.service";
|
||||
import { DefaultBrowserStateService } from "../platform/services/default-browser-state.service";
|
||||
import I18nService from "../platform/services/i18n.service";
|
||||
|
@ -334,6 +336,7 @@ export default class MainBackground {
|
|||
userAutoUnlockKeyService: UserAutoUnlockKeyService;
|
||||
scriptInjectorService: BrowserScriptInjectorService;
|
||||
kdfConfigService: kdfConfigServiceAbstraction;
|
||||
offscreenDocumentService: OffscreenDocumentService;
|
||||
|
||||
onUpdatedRan: boolean;
|
||||
onReplacedRan: boolean;
|
||||
|
@ -391,11 +394,14 @@ export default class MainBackground {
|
|||
),
|
||||
);
|
||||
|
||||
this.offscreenDocumentService = new DefaultOffscreenDocumentService();
|
||||
|
||||
this.platformUtilsService = new BackgroundPlatformUtilsService(
|
||||
this.messagingService,
|
||||
(clipboardValue, clearMs) => this.clearClipboard(clipboardValue, clearMs),
|
||||
async () => this.biometricUnlock(),
|
||||
self,
|
||||
this.offscreenDocumentService,
|
||||
);
|
||||
|
||||
// Creates a session key for mv3 storage of large memory items
|
||||
|
@ -467,14 +473,14 @@ export default class MainBackground {
|
|||
storageServiceProvider,
|
||||
);
|
||||
|
||||
this.encryptService =
|
||||
flagEnabled("multithreadDecryption") && BrowserApi.isManifestVersion(2)
|
||||
? new MultithreadEncryptServiceImplementation(
|
||||
this.cryptoFunctionService,
|
||||
this.logService,
|
||||
true,
|
||||
)
|
||||
: new EncryptServiceImplementation(this.cryptoFunctionService, this.logService, true);
|
||||
this.encryptService = flagEnabled("multithreadDecryption")
|
||||
? new BrowserMultithreadEncryptServiceImplementation(
|
||||
this.cryptoFunctionService,
|
||||
this.logService,
|
||||
true,
|
||||
this.offscreenDocumentService,
|
||||
)
|
||||
: new EncryptServiceImplementation(this.cryptoFunctionService, this.logService, true);
|
||||
|
||||
this.singleUserStateProvider = new DefaultSingleUserStateProvider(
|
||||
storageServiceProvider,
|
||||
|
@ -807,7 +813,10 @@ export default class MainBackground {
|
|||
);
|
||||
this.totpService = new TotpService(this.cryptoFunctionService, this.logService);
|
||||
|
||||
this.scriptInjectorService = new BrowserScriptInjectorService();
|
||||
this.scriptInjectorService = new BrowserScriptInjectorService(
|
||||
this.platformUtilsService,
|
||||
this.logService,
|
||||
);
|
||||
this.autofillService = new AutofillService(
|
||||
this.cipherService,
|
||||
this.autofillSettingsService,
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
"default_popup": "popup/index.html"
|
||||
},
|
||||
"permissions": [
|
||||
"<all_urls>",
|
||||
"activeTab",
|
||||
"tabs",
|
||||
"contextMenus",
|
||||
"storage",
|
||||
|
@ -65,7 +65,7 @@
|
|||
"webRequestAuthProvider"
|
||||
],
|
||||
"optional_permissions": ["nativeMessaging", "privacy"],
|
||||
"host_permissions": ["<all_urls>"],
|
||||
"host_permissions": ["https://*/*", "http://*/*"],
|
||||
"content_security_policy": {
|
||||
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'",
|
||||
"sandbox": "sandbox allow-scripts; script-src 'self'"
|
||||
|
|
|
@ -1,10 +1,20 @@
|
|||
import {
|
||||
LogServiceInitOptions,
|
||||
logServiceFactory,
|
||||
} from "../../background/service-factories/log-service.factory";
|
||||
import { BrowserScriptInjectorService } from "../../services/browser-script-injector.service";
|
||||
|
||||
import { CachedServices, FactoryOptions, factory } from "./factory-options";
|
||||
import {
|
||||
PlatformUtilsServiceInitOptions,
|
||||
platformUtilsServiceFactory,
|
||||
} from "./platform-utils-service.factory";
|
||||
|
||||
type BrowserScriptInjectorServiceOptions = FactoryOptions;
|
||||
|
||||
export type BrowserScriptInjectorServiceInitOptions = BrowserScriptInjectorServiceOptions;
|
||||
export type BrowserScriptInjectorServiceInitOptions = BrowserScriptInjectorServiceOptions &
|
||||
PlatformUtilsServiceInitOptions &
|
||||
LogServiceInitOptions;
|
||||
|
||||
export function browserScriptInjectorServiceFactory(
|
||||
cache: { browserScriptInjectorService?: BrowserScriptInjectorService } & CachedServices,
|
||||
|
@ -14,6 +24,10 @@ export function browserScriptInjectorServiceFactory(
|
|||
cache,
|
||||
"browserScriptInjectorService",
|
||||
opts,
|
||||
async () => new BrowserScriptInjectorService(),
|
||||
async () =>
|
||||
new BrowserScriptInjectorService(
|
||||
await platformUtilsServiceFactory(cache, opts),
|
||||
await logServiceFactory(cache, opts),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ export function platformUtilsServiceFactory(
|
|||
opts.platformUtilsServiceOptions.clipboardWriteCallback,
|
||||
opts.platformUtilsServiceOptions.biometricCallback,
|
||||
opts.platformUtilsServiceOptions.win,
|
||||
null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -525,32 +525,6 @@ describe("BrowserApi", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("createOffscreenDocument", () => {
|
||||
it("creates the offscreen document with the supplied reasons and justification", async () => {
|
||||
const reasons = [chrome.offscreen.Reason.CLIPBOARD];
|
||||
const justification = "justification";
|
||||
|
||||
await BrowserApi.createOffscreenDocument(reasons, justification);
|
||||
|
||||
expect(chrome.offscreen.createDocument).toHaveBeenCalledWith({
|
||||
url: "offscreen-document/index.html",
|
||||
reasons,
|
||||
justification,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("closeOffscreenDocument", () => {
|
||||
it("closes the offscreen document", () => {
|
||||
const callbackMock = jest.fn();
|
||||
|
||||
BrowserApi.closeOffscreenDocument(callbackMock);
|
||||
|
||||
expect(chrome.offscreen.closeDocument).toHaveBeenCalled();
|
||||
expect(callbackMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("registerContentScriptsMv2", () => {
|
||||
const details: browser.contentScripts.RegisteredContentScriptOptions = {
|
||||
matches: ["<all_urls>"],
|
||||
|
|
|
@ -558,34 +558,6 @@ export class BrowserApi {
|
|||
chrome.privacy.services.passwordSavingEnabled.set({ value });
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the offscreen document with the given reasons and justification.
|
||||
*
|
||||
* @param reasons - List of reasons for opening the offscreen document.
|
||||
* @see https://developer.chrome.com/docs/extensions/reference/api/offscreen#type-Reason
|
||||
* @param justification - Custom written justification for opening the offscreen document.
|
||||
*/
|
||||
static async createOffscreenDocument(reasons: chrome.offscreen.Reason[], justification: string) {
|
||||
await chrome.offscreen.createDocument({
|
||||
url: "offscreen-document/index.html",
|
||||
reasons,
|
||||
justification,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the offscreen document.
|
||||
*
|
||||
* @param callback - Optional callback to execute after the offscreen document is closed.
|
||||
*/
|
||||
static closeOffscreenDocument(callback?: () => void) {
|
||||
chrome.offscreen.closeDocument(() => {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles registration of static content scripts within manifest v2.
|
||||
*
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
type OffscreenDocumentExtensionMessage = {
|
||||
export type OffscreenDocumentExtensionMessage = {
|
||||
[key: string]: any;
|
||||
command: string;
|
||||
text?: string;
|
||||
decryptRequest?: string;
|
||||
};
|
||||
|
||||
type OffscreenExtensionMessageEventParams = {
|
||||
|
@ -9,18 +10,21 @@ type OffscreenExtensionMessageEventParams = {
|
|||
sender: chrome.runtime.MessageSender;
|
||||
};
|
||||
|
||||
type OffscreenDocumentExtensionMessageHandlers = {
|
||||
export type OffscreenDocumentExtensionMessageHandlers = {
|
||||
[key: string]: ({ message, sender }: OffscreenExtensionMessageEventParams) => any;
|
||||
offscreenCopyToClipboard: ({ message }: OffscreenExtensionMessageEventParams) => any;
|
||||
offscreenReadFromClipboard: () => any;
|
||||
offscreenDecryptItems: ({ message }: OffscreenExtensionMessageEventParams) => Promise<string>;
|
||||
};
|
||||
|
||||
interface OffscreenDocument {
|
||||
export interface OffscreenDocument {
|
||||
init(): void;
|
||||
}
|
||||
|
||||
export {
|
||||
OffscreenDocumentExtensionMessage,
|
||||
OffscreenDocumentExtensionMessageHandlers,
|
||||
OffscreenDocument,
|
||||
};
|
||||
export abstract class OffscreenDocumentService {
|
||||
abstract withDocument<T>(
|
||||
reasons: chrome.offscreen.Reason[],
|
||||
justification: string,
|
||||
callback: () => Promise<T> | T,
|
||||
): Promise<T>;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
import { DefaultOffscreenDocumentService } from "./offscreen-document.service";
|
||||
|
||||
class TestCase {
|
||||
synchronicity: string;
|
||||
private _callback: () => Promise<any> | any;
|
||||
get callback() {
|
||||
return jest.fn(this._callback);
|
||||
}
|
||||
|
||||
constructor(synchronicity: string, callback: () => Promise<any> | any) {
|
||||
this.synchronicity = synchronicity;
|
||||
this._callback = callback;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.synchronicity;
|
||||
}
|
||||
}
|
||||
|
||||
describe.each([
|
||||
new TestCase("synchronous callback", () => 42),
|
||||
new TestCase("asynchronous callback", () => Promise.resolve(42)),
|
||||
])("DefaultOffscreenDocumentService %s", (testCase) => {
|
||||
let sut: DefaultOffscreenDocumentService;
|
||||
const reasons = [chrome.offscreen.Reason.TESTING];
|
||||
const justification = "justification is testing";
|
||||
const url = "offscreen-document/index.html";
|
||||
const api = {
|
||||
createDocument: jest.fn(),
|
||||
closeDocument: jest.fn(),
|
||||
hasDocument: jest.fn().mockResolvedValue(false),
|
||||
Reason: chrome.offscreen.Reason,
|
||||
};
|
||||
let callback: jest.Mock<() => Promise<number> | number>;
|
||||
|
||||
beforeEach(() => {
|
||||
callback = testCase.callback;
|
||||
chrome.offscreen = api;
|
||||
|
||||
sut = new DefaultOffscreenDocumentService();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe("withDocument", () => {
|
||||
it("creates a document when none exists", async () => {
|
||||
await sut.withDocument(reasons, justification, () => {});
|
||||
|
||||
expect(chrome.offscreen.createDocument).toHaveBeenCalledWith({
|
||||
url,
|
||||
reasons,
|
||||
justification,
|
||||
});
|
||||
});
|
||||
|
||||
it("does not create a document when one exists", async () => {
|
||||
api.hasDocument.mockResolvedValue(true);
|
||||
|
||||
await sut.withDocument(reasons, justification, callback);
|
||||
|
||||
expect(chrome.offscreen.createDocument).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe.each([true, false])("hasDocument returns %s", (hasDocument) => {
|
||||
beforeEach(() => {
|
||||
api.hasDocument.mockResolvedValue(hasDocument);
|
||||
});
|
||||
|
||||
it("calls the callback", async () => {
|
||||
await sut.withDocument(reasons, justification, callback);
|
||||
|
||||
expect(callback).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns the callback result", async () => {
|
||||
const result = await sut.withDocument(reasons, justification, callback);
|
||||
|
||||
expect(result).toBe(42);
|
||||
});
|
||||
|
||||
it("closes the document when the callback completes and no other callbacks are running", async () => {
|
||||
await sut.withDocument(reasons, justification, callback);
|
||||
|
||||
expect(chrome.offscreen.closeDocument).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not close the document when the callback completes and other callbacks are running", async () => {
|
||||
await Promise.all([
|
||||
sut.withDocument(reasons, justification, callback),
|
||||
sut.withDocument(reasons, justification, callback),
|
||||
sut.withDocument(reasons, justification, callback),
|
||||
sut.withDocument(reasons, justification, callback),
|
||||
]);
|
||||
|
||||
expect(chrome.offscreen.closeDocument).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,41 @@
|
|||
export class DefaultOffscreenDocumentService implements DefaultOffscreenDocumentService {
|
||||
private workerCount = 0;
|
||||
|
||||
constructor() {}
|
||||
|
||||
async withDocument<T>(
|
||||
reasons: chrome.offscreen.Reason[],
|
||||
justification: string,
|
||||
callback: () => Promise<T> | T,
|
||||
): Promise<T> {
|
||||
this.workerCount++;
|
||||
try {
|
||||
if (!(await this.documentExists())) {
|
||||
await this.create(reasons, justification);
|
||||
}
|
||||
|
||||
return await callback();
|
||||
} finally {
|
||||
this.workerCount--;
|
||||
if (this.workerCount === 0) {
|
||||
await this.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async create(reasons: chrome.offscreen.Reason[], justification: string): Promise<void> {
|
||||
await chrome.offscreen.createDocument({
|
||||
url: "offscreen-document/index.html",
|
||||
reasons,
|
||||
justification,
|
||||
});
|
||||
}
|
||||
|
||||
private async close(): Promise<void> {
|
||||
await chrome.offscreen.closeDocument();
|
||||
}
|
||||
|
||||
private async documentExists(): Promise<boolean> {
|
||||
return await chrome.offscreen.hasDocument();
|
||||
}
|
||||
}
|
|
@ -1,7 +1,25 @@
|
|||
import { mock } from "jest-mock-extended";
|
||||
|
||||
import { Decryptable } from "@bitwarden/common/platform/interfaces/decryptable.interface";
|
||||
import { InitializerMetadata } from "@bitwarden/common/platform/interfaces/initializer-metadata.interface";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
|
||||
import { flushPromises, sendExtensionRuntimeMessage } from "../../autofill/spec/testing-utils";
|
||||
import { BrowserApi } from "../browser/browser-api";
|
||||
import BrowserClipboardService from "../services/browser-clipboard.service";
|
||||
|
||||
jest.mock(
|
||||
"@bitwarden/common/platform/services/cryptography/multithread-encrypt.service.implementation",
|
||||
() => ({
|
||||
MultithreadEncryptServiceImplementation: class MultithreadEncryptServiceImplementation {
|
||||
getDecryptedItemsFromWorker = async <T extends InitializerMetadata>(
|
||||
items: Decryptable<T>[],
|
||||
_key: SymmetricCryptoKey,
|
||||
): Promise<string> => JSON.stringify(items);
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
describe("OffscreenDocument", () => {
|
||||
const browserApiMessageListenerSpy = jest.spyOn(BrowserApi, "messageListener");
|
||||
const browserClipboardServiceCopySpy = jest.spyOn(BrowserClipboardService, "copy");
|
||||
|
@ -60,5 +78,37 @@ describe("OffscreenDocument", () => {
|
|||
expect(browserClipboardServiceReadSpy).toHaveBeenCalledWith(window);
|
||||
});
|
||||
});
|
||||
|
||||
describe("handleOffscreenDecryptItems", () => {
|
||||
it("returns an empty array as a string if the decrypt request is not present in the message", async () => {
|
||||
let response: string | undefined;
|
||||
sendExtensionRuntimeMessage(
|
||||
{ command: "offscreenDecryptItems" },
|
||||
mock<chrome.runtime.MessageSender>(),
|
||||
(res: string) => (response = res),
|
||||
);
|
||||
await flushPromises();
|
||||
|
||||
expect(response).toBe("[]");
|
||||
});
|
||||
|
||||
it("decrypts the items and sends back the response as a string", async () => {
|
||||
const items = [{ id: "test" }];
|
||||
const key = { id: "test" };
|
||||
const decryptRequest = JSON.stringify({ items, key });
|
||||
let response: string | undefined;
|
||||
|
||||
sendExtensionRuntimeMessage(
|
||||
{ command: "offscreenDecryptItems", decryptRequest },
|
||||
mock<chrome.runtime.MessageSender>(),
|
||||
(res: string) => {
|
||||
response = res;
|
||||
},
|
||||
);
|
||||
await flushPromises();
|
||||
|
||||
expect(response).toBe(JSON.stringify(items));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,21 +1,35 @@
|
|||
import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
|
||||
import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/multithread-encrypt.service.implementation";
|
||||
import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service";
|
||||
|
||||
import { BrowserApi } from "../browser/browser-api";
|
||||
import BrowserClipboardService from "../services/browser-clipboard.service";
|
||||
|
||||
import {
|
||||
OffscreenDocument as OffscreenDocumentInterface,
|
||||
OffscreenDocumentExtensionMessage,
|
||||
OffscreenDocumentExtensionMessageHandlers,
|
||||
OffscreenDocument as OffscreenDocumentInterface,
|
||||
} from "./abstractions/offscreen-document";
|
||||
|
||||
class OffscreenDocument implements OffscreenDocumentInterface {
|
||||
private consoleLogService: ConsoleLogService = new ConsoleLogService(false);
|
||||
private readonly consoleLogService: ConsoleLogService;
|
||||
private encryptService: MultithreadEncryptServiceImplementation;
|
||||
private readonly extensionMessageHandlers: OffscreenDocumentExtensionMessageHandlers = {
|
||||
offscreenCopyToClipboard: ({ message }) => this.handleOffscreenCopyToClipboard(message),
|
||||
offscreenReadFromClipboard: () => this.handleOffscreenReadFromClipboard(),
|
||||
offscreenDecryptItems: ({ message }) => this.handleOffscreenDecryptItems(message),
|
||||
};
|
||||
|
||||
constructor() {
|
||||
const cryptoFunctionService = new WebCryptoFunctionService(self);
|
||||
this.consoleLogService = new ConsoleLogService(false);
|
||||
this.encryptService = new MultithreadEncryptServiceImplementation(
|
||||
cryptoFunctionService,
|
||||
this.consoleLogService,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the offscreen document extension.
|
||||
*/
|
||||
|
@ -39,6 +53,23 @@ class OffscreenDocument implements OffscreenDocumentInterface {
|
|||
return await BrowserClipboardService.read(self);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts the items in the message using the encrypt service.
|
||||
*
|
||||
* @param message - The extension message containing the items to decrypt
|
||||
*/
|
||||
private async handleOffscreenDecryptItems(
|
||||
message: OffscreenDocumentExtensionMessage,
|
||||
): Promise<string> {
|
||||
const { decryptRequest } = message;
|
||||
if (!decryptRequest) {
|
||||
return "[]";
|
||||
}
|
||||
|
||||
const request = JSON.parse(decryptRequest);
|
||||
return await this.encryptService.getDecryptedItemsFromWorker(request.items, request.key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the listener for extension messages.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { EncryptionType } from "@bitwarden/common/platform/enums";
|
||||
import { Decryptable } from "@bitwarden/common/platform/interfaces/decryptable.interface";
|
||||
import { InitializerMetadata } from "@bitwarden/common/platform/interfaces/initializer-metadata.interface";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { InitializerKey } from "@bitwarden/common/platform/services/cryptography/initializer-key";
|
||||
import { makeStaticByteArray } from "@bitwarden/common/spec";
|
||||
|
||||
import { BrowserApi } from "../browser/browser-api";
|
||||
import { OffscreenDocumentService } from "../offscreen-document/abstractions/offscreen-document";
|
||||
|
||||
import { BrowserMultithreadEncryptServiceImplementation } from "./browser-multithread-encrypt.service.implementation";
|
||||
|
||||
describe("BrowserMultithreadEncryptServiceImplementation", () => {
|
||||
let cryptoFunctionServiceMock: MockProxy<CryptoFunctionService>;
|
||||
let logServiceMock: MockProxy<LogService>;
|
||||
let offscreenDocumentServiceMock: MockProxy<OffscreenDocumentService>;
|
||||
let encryptService: BrowserMultithreadEncryptServiceImplementation;
|
||||
const manifestVersionSpy = jest.spyOn(BrowserApi, "manifestVersion", "get");
|
||||
const sendMessageWithResponseSpy = jest.spyOn(BrowserApi, "sendMessageWithResponse");
|
||||
const encType = EncryptionType.AesCbc256_HmacSha256_B64;
|
||||
const key = new SymmetricCryptoKey(makeStaticByteArray(64, 100), encType);
|
||||
const items: Decryptable<InitializerMetadata>[] = [
|
||||
{
|
||||
decrypt: jest.fn(),
|
||||
initializerKey: InitializerKey.Cipher,
|
||||
},
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
cryptoFunctionServiceMock = mock<CryptoFunctionService>();
|
||||
logServiceMock = mock<LogService>();
|
||||
offscreenDocumentServiceMock = mock<OffscreenDocumentService>({
|
||||
withDocument: jest.fn((_, __, callback) => callback() as any),
|
||||
});
|
||||
encryptService = new BrowserMultithreadEncryptServiceImplementation(
|
||||
cryptoFunctionServiceMock,
|
||||
logServiceMock,
|
||||
false,
|
||||
offscreenDocumentServiceMock,
|
||||
);
|
||||
manifestVersionSpy.mockReturnValue(3);
|
||||
sendMessageWithResponseSpy.mockResolvedValue(JSON.stringify([]));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it("decrypts items using web workers if the chrome.offscreen API is not supported", async () => {
|
||||
manifestVersionSpy.mockReturnValue(2);
|
||||
|
||||
await encryptService.decryptItems([], key);
|
||||
|
||||
expect(offscreenDocumentServiceMock.withDocument).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("decrypts items using the chrome.offscreen API if it is supported", async () => {
|
||||
sendMessageWithResponseSpy.mockResolvedValue(JSON.stringify(items));
|
||||
|
||||
await encryptService.decryptItems(items, key);
|
||||
|
||||
expect(offscreenDocumentServiceMock.withDocument).toHaveBeenCalledWith(
|
||||
[chrome.offscreen.Reason.WORKERS],
|
||||
"Use web worker to decrypt items.",
|
||||
expect.any(Function),
|
||||
);
|
||||
expect(BrowserApi.sendMessageWithResponse).toHaveBeenCalledWith("offscreenDecryptItems", {
|
||||
decryptRequest: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it("returns an empty array if the passed items are not defined", async () => {
|
||||
const result = await encryptService.decryptItems(null, key);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it("returns an empty array if the offscreen document message returns an empty value", async () => {
|
||||
sendMessageWithResponseSpy.mockResolvedValue("");
|
||||
|
||||
const result = await encryptService.decryptItems(items, key);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it("returns an empty array if the offscreen document message returns an empty array", async () => {
|
||||
sendMessageWithResponseSpy.mockResolvedValue("[]");
|
||||
|
||||
const result = await encryptService.decryptItems(items, key);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,91 @@
|
|||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { Decryptable } from "@bitwarden/common/platform/interfaces/decryptable.interface";
|
||||
import { InitializerMetadata } from "@bitwarden/common/platform/interfaces/initializer-metadata.interface";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/multithread-encrypt.service.implementation";
|
||||
|
||||
import { BrowserApi } from "../browser/browser-api";
|
||||
import { OffscreenDocumentService } from "../offscreen-document/abstractions/offscreen-document";
|
||||
|
||||
export class BrowserMultithreadEncryptServiceImplementation extends MultithreadEncryptServiceImplementation {
|
||||
constructor(
|
||||
cryptoFunctionService: CryptoFunctionService,
|
||||
logService: LogService,
|
||||
logMacFailures: boolean,
|
||||
private offscreenDocumentService: OffscreenDocumentService,
|
||||
) {
|
||||
super(cryptoFunctionService, logService, logMacFailures);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles decryption of items, will use the offscreen document if supported.
|
||||
*
|
||||
* @param items - The items to decrypt.
|
||||
* @param key - The key to use for decryption.
|
||||
*/
|
||||
async decryptItems<T extends InitializerMetadata>(
|
||||
items: Decryptable<T>[],
|
||||
key: SymmetricCryptoKey,
|
||||
): Promise<T[]> {
|
||||
if (!this.isOffscreenDocumentSupported()) {
|
||||
return await super.decryptItems(items, key);
|
||||
}
|
||||
|
||||
return await this.decryptItemsInOffscreenDocument(items, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts items using the offscreen document api.
|
||||
*
|
||||
* @param items - The items to decrypt.
|
||||
* @param key - The key to use for decryption.
|
||||
*/
|
||||
private async decryptItemsInOffscreenDocument<T extends InitializerMetadata>(
|
||||
items: Decryptable<T>[],
|
||||
key: SymmetricCryptoKey,
|
||||
): Promise<T[]> {
|
||||
if (items == null || items.length < 1) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const request = {
|
||||
id: Utils.newGuid(),
|
||||
items: items,
|
||||
key: key,
|
||||
};
|
||||
|
||||
const response = await this.offscreenDocumentService.withDocument(
|
||||
[chrome.offscreen.Reason.WORKERS],
|
||||
"Use web worker to decrypt items.",
|
||||
async () => {
|
||||
return (await BrowserApi.sendMessageWithResponse("offscreenDecryptItems", {
|
||||
decryptRequest: JSON.stringify(request),
|
||||
})) as string;
|
||||
},
|
||||
);
|
||||
|
||||
if (!response) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const responseItems = JSON.parse(response);
|
||||
if (responseItems?.length < 1) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.initializeItems(responseItems);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the offscreen document api is supported.
|
||||
*/
|
||||
private isOffscreenDocumentSupported() {
|
||||
return (
|
||||
BrowserApi.isManifestVersion(3) &&
|
||||
typeof chrome !== "undefined" &&
|
||||
typeof chrome.offscreen !== "undefined"
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,3 +1,8 @@
|
|||
import { mock } from "jest-mock-extended";
|
||||
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
import { BrowserApi } from "../browser/browser-api";
|
||||
|
||||
import {
|
||||
|
@ -20,9 +25,11 @@ describe("ScriptInjectorService", () => {
|
|||
let scriptInjectorService: BrowserScriptInjectorService;
|
||||
jest.spyOn(BrowserApi, "executeScriptInTab").mockImplementation();
|
||||
jest.spyOn(BrowserApi, "isManifestVersion");
|
||||
const platformUtilsService = mock<PlatformUtilsService>();
|
||||
const logService = mock<LogService>();
|
||||
|
||||
beforeEach(() => {
|
||||
scriptInjectorService = new BrowserScriptInjectorService();
|
||||
scriptInjectorService = new BrowserScriptInjectorService(platformUtilsService, logService);
|
||||
});
|
||||
|
||||
describe("inject", () => {
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
import { BrowserApi } from "../browser/browser-api";
|
||||
|
||||
import {
|
||||
|
@ -7,6 +10,13 @@ import {
|
|||
} from "./abstractions/script-injector.service";
|
||||
|
||||
export class BrowserScriptInjectorService extends ScriptInjectorService {
|
||||
constructor(
|
||||
private readonly platformUtilsService: PlatformUtilsService,
|
||||
private readonly logService: LogService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Facilitates the injection of a script into a tab context. Will adjust
|
||||
* behavior between manifest v2 and v3 based on the passed configuration.
|
||||
|
@ -23,9 +33,26 @@ export class BrowserScriptInjectorService extends ScriptInjectorService {
|
|||
const injectionDetails = this.buildInjectionDetails(injectDetails, file);
|
||||
|
||||
if (BrowserApi.isManifestVersion(3)) {
|
||||
await BrowserApi.executeScriptInTab(tabId, injectionDetails, {
|
||||
world: mv3Details?.world ?? "ISOLATED",
|
||||
});
|
||||
try {
|
||||
await BrowserApi.executeScriptInTab(tabId, injectionDetails, {
|
||||
world: mv3Details?.world ?? "ISOLATED",
|
||||
});
|
||||
} catch (error) {
|
||||
// Swallow errors for host permissions, since this is believed to be a Manifest V3 Chrome bug
|
||||
// @TODO remove when the bugged behaviour is resolved
|
||||
if (
|
||||
error.message !==
|
||||
"Cannot access contents of the page. Extension manifest must request permission to access the respective host."
|
||||
) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (this.platformUtilsService.isDev()) {
|
||||
this.logService.warning(
|
||||
`BrowserApi.executeScriptInTab exception for ${injectDetails.file} in tab ${tabId}: ${error.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
|
@ -15,21 +13,13 @@ import { MigrationRunner } from "@bitwarden/common/platform/services/migration-r
|
|||
import { StateService as BaseStateService } from "@bitwarden/common/platform/services/state.service";
|
||||
|
||||
import { Account } from "../../models/account";
|
||||
import { browserSession, sessionSync } from "../decorators/session-sync-observable";
|
||||
|
||||
import { BrowserStateService } from "./abstractions/browser-state.service";
|
||||
|
||||
@browserSession
|
||||
export class DefaultBrowserStateService
|
||||
extends BaseStateService<GlobalState, Account>
|
||||
implements BrowserStateService
|
||||
{
|
||||
@sessionSync({
|
||||
initializer: Account.fromJSON as any, // TODO: Remove this any when all any types are removed from Account
|
||||
initializeAs: "record",
|
||||
})
|
||||
protected accountsSubject: BehaviorSubject<{ [userId: string]: Account }>;
|
||||
|
||||
protected accountDeserializer = Account.fromJSON;
|
||||
|
||||
constructor(
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
|
||||
import { OffscreenDocumentService } from "../../offscreen-document/abstractions/offscreen-document";
|
||||
|
||||
import { BrowserPlatformUtilsService } from "./browser-platform-utils.service";
|
||||
|
||||
export class BackgroundPlatformUtilsService extends BrowserPlatformUtilsService {
|
||||
|
@ -8,8 +10,9 @@ export class BackgroundPlatformUtilsService extends BrowserPlatformUtilsService
|
|||
clipboardWriteCallback: (clipboardValue: string, clearMs: number) => void,
|
||||
biometricCallback: () => Promise<boolean>,
|
||||
win: Window & typeof globalThis,
|
||||
offscreenDocumentService: OffscreenDocumentService,
|
||||
) {
|
||||
super(clipboardWriteCallback, biometricCallback, win);
|
||||
super(clipboardWriteCallback, biometricCallback, win, offscreenDocumentService);
|
||||
}
|
||||
|
||||
override showToast(
|
||||
|
|
|
@ -1,15 +1,22 @@
|
|||
import { MockProxy, mock } from "jest-mock-extended";
|
||||
|
||||
import { DeviceType } from "@bitwarden/common/enums";
|
||||
|
||||
import { flushPromises } from "../../../autofill/spec/testing-utils";
|
||||
import { SafariApp } from "../../../browser/safariApp";
|
||||
import { BrowserApi } from "../../browser/browser-api";
|
||||
import { OffscreenDocumentService } from "../../offscreen-document/abstractions/offscreen-document";
|
||||
import BrowserClipboardService from "../browser-clipboard.service";
|
||||
|
||||
import { BrowserPlatformUtilsService } from "./browser-platform-utils.service";
|
||||
|
||||
class TestBrowserPlatformUtilsService extends BrowserPlatformUtilsService {
|
||||
constructor(clipboardSpy: jest.Mock, win: Window & typeof globalThis) {
|
||||
super(clipboardSpy, null, win);
|
||||
constructor(
|
||||
clipboardSpy: jest.Mock,
|
||||
win: Window & typeof globalThis,
|
||||
offscreenDocumentService: OffscreenDocumentService,
|
||||
) {
|
||||
super(clipboardSpy, null, win, offscreenDocumentService);
|
||||
}
|
||||
|
||||
showToast(
|
||||
|
@ -24,13 +31,16 @@ class TestBrowserPlatformUtilsService extends BrowserPlatformUtilsService {
|
|||
|
||||
describe("Browser Utils Service", () => {
|
||||
let browserPlatformUtilsService: BrowserPlatformUtilsService;
|
||||
let offscreenDocumentService: MockProxy<OffscreenDocumentService>;
|
||||
const clipboardWriteCallbackSpy = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
offscreenDocumentService = mock();
|
||||
(window as any).matchMedia = jest.fn().mockReturnValueOnce({});
|
||||
browserPlatformUtilsService = new TestBrowserPlatformUtilsService(
|
||||
clipboardWriteCallbackSpy,
|
||||
window,
|
||||
offscreenDocumentService,
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -223,23 +233,23 @@ describe("Browser Utils Service", () => {
|
|||
.spyOn(browserPlatformUtilsService, "getDevice")
|
||||
.mockReturnValue(DeviceType.ChromeExtension);
|
||||
getManifestVersionSpy.mockReturnValue(3);
|
||||
jest.spyOn(BrowserApi, "createOffscreenDocument");
|
||||
jest.spyOn(BrowserApi, "sendMessageWithResponse").mockResolvedValue(undefined);
|
||||
jest.spyOn(BrowserApi, "closeOffscreenDocument");
|
||||
|
||||
browserPlatformUtilsService.copyToClipboard(text);
|
||||
await flushPromises();
|
||||
|
||||
expect(triggerOffscreenCopyToClipboardSpy).toHaveBeenCalledWith(text);
|
||||
expect(clipboardServiceCopySpy).not.toHaveBeenCalled();
|
||||
expect(BrowserApi.createOffscreenDocument).toHaveBeenCalledWith(
|
||||
expect(offscreenDocumentService.withDocument).toHaveBeenCalledWith(
|
||||
[chrome.offscreen.Reason.CLIPBOARD],
|
||||
"Write text to the clipboard.",
|
||||
expect.any(Function),
|
||||
);
|
||||
|
||||
const callback = offscreenDocumentService.withDocument.mock.calls[0][2];
|
||||
await callback();
|
||||
expect(BrowserApi.sendMessageWithResponse).toHaveBeenCalledWith("offscreenCopyToClipboard", {
|
||||
text,
|
||||
});
|
||||
expect(BrowserApi.closeOffscreenDocument).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("skips the clipboardWriteCallback if the clipboard is clearing", async () => {
|
||||
|
@ -298,18 +308,21 @@ describe("Browser Utils Service", () => {
|
|||
.spyOn(browserPlatformUtilsService, "getDevice")
|
||||
.mockReturnValue(DeviceType.ChromeExtension);
|
||||
getManifestVersionSpy.mockReturnValue(3);
|
||||
jest.spyOn(BrowserApi, "createOffscreenDocument");
|
||||
jest.spyOn(BrowserApi, "sendMessageWithResponse").mockResolvedValue("test");
|
||||
jest.spyOn(BrowserApi, "closeOffscreenDocument");
|
||||
offscreenDocumentService.withDocument.mockImplementationOnce((_, __, callback) =>
|
||||
Promise.resolve("test"),
|
||||
);
|
||||
|
||||
await browserPlatformUtilsService.readFromClipboard();
|
||||
|
||||
expect(BrowserApi.createOffscreenDocument).toHaveBeenCalledWith(
|
||||
expect(offscreenDocumentService.withDocument).toHaveBeenCalledWith(
|
||||
[chrome.offscreen.Reason.CLIPBOARD],
|
||||
"Read text from the clipboard.",
|
||||
expect.any(Function),
|
||||
);
|
||||
|
||||
const callback = offscreenDocumentService.withDocument.mock.calls[0][2];
|
||||
await callback();
|
||||
expect(BrowserApi.sendMessageWithResponse).toHaveBeenCalledWith("offscreenReadFromClipboard");
|
||||
expect(BrowserApi.closeOffscreenDocument).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns an empty string from the offscreen document if the response is not of type string", async () => {
|
||||
|
@ -317,9 +330,10 @@ describe("Browser Utils Service", () => {
|
|||
.spyOn(browserPlatformUtilsService, "getDevice")
|
||||
.mockReturnValue(DeviceType.ChromeExtension);
|
||||
getManifestVersionSpy.mockReturnValue(3);
|
||||
jest.spyOn(BrowserApi, "createOffscreenDocument");
|
||||
jest.spyOn(BrowserApi, "sendMessageWithResponse").mockResolvedValue(1);
|
||||
jest.spyOn(BrowserApi, "closeOffscreenDocument");
|
||||
offscreenDocumentService.withDocument.mockImplementationOnce((_, __, callback) =>
|
||||
Promise.resolve(1),
|
||||
);
|
||||
|
||||
const result = await browserPlatformUtilsService.readFromClipboard();
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
|
||||
import { SafariApp } from "../../../browser/safariApp";
|
||||
import { BrowserApi } from "../../browser/browser-api";
|
||||
import { OffscreenDocumentService } from "../../offscreen-document/abstractions/offscreen-document";
|
||||
import BrowserClipboardService from "../browser-clipboard.service";
|
||||
|
||||
export abstract class BrowserPlatformUtilsService implements PlatformUtilsService {
|
||||
|
@ -15,6 +16,7 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic
|
|||
private clipboardWriteCallback: (clipboardValue: string, clearMs: number) => void,
|
||||
private biometricCallback: () => Promise<boolean>,
|
||||
private globalContext: Window | ServiceWorkerGlobalScope,
|
||||
private offscreenDocumentService: OffscreenDocumentService,
|
||||
) {}
|
||||
|
||||
static getDevice(globalContext: Window | ServiceWorkerGlobalScope): DeviceType {
|
||||
|
@ -316,24 +318,26 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic
|
|||
* Triggers the offscreen document API to copy the text to the clipboard.
|
||||
*/
|
||||
private async triggerOffscreenCopyToClipboard(text: string) {
|
||||
await BrowserApi.createOffscreenDocument(
|
||||
await this.offscreenDocumentService.withDocument(
|
||||
[chrome.offscreen.Reason.CLIPBOARD],
|
||||
"Write text to the clipboard.",
|
||||
async () => {
|
||||
await BrowserApi.sendMessageWithResponse("offscreenCopyToClipboard", { text });
|
||||
},
|
||||
);
|
||||
await BrowserApi.sendMessageWithResponse("offscreenCopyToClipboard", { text });
|
||||
BrowserApi.closeOffscreenDocument();
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers the offscreen document API to read the text from the clipboard.
|
||||
*/
|
||||
private async triggerOffscreenReadFromClipboard() {
|
||||
await BrowserApi.createOffscreenDocument(
|
||||
const response = await this.offscreenDocumentService.withDocument(
|
||||
[chrome.offscreen.Reason.CLIPBOARD],
|
||||
"Read text from the clipboard.",
|
||||
async () => {
|
||||
return await BrowserApi.sendMessageWithResponse("offscreenReadFromClipboard");
|
||||
},
|
||||
);
|
||||
const response = await BrowserApi.sendMessageWithResponse("offscreenReadFromClipboard");
|
||||
BrowserApi.closeOffscreenDocument();
|
||||
if (typeof response === "string") {
|
||||
return response;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { ToastService } from "@bitwarden/components";
|
||||
|
||||
import { OffscreenDocumentService } from "../../offscreen-document/abstractions/offscreen-document";
|
||||
|
||||
import { BrowserPlatformUtilsService } from "./browser-platform-utils.service";
|
||||
|
||||
export class ForegroundPlatformUtilsService extends BrowserPlatformUtilsService {
|
||||
|
@ -8,8 +10,9 @@ export class ForegroundPlatformUtilsService extends BrowserPlatformUtilsService
|
|||
clipboardWriteCallback: (clipboardValue: string, clearMs: number) => void,
|
||||
biometricCallback: () => Promise<boolean>,
|
||||
win: Window & typeof globalThis,
|
||||
offscreenDocumentService: OffscreenDocumentService,
|
||||
) {
|
||||
super(clipboardWriteCallback, biometricCallback, win);
|
||||
super(clipboardWriteCallback, biometricCallback, win, offscreenDocumentService);
|
||||
}
|
||||
|
||||
override showToast(
|
||||
|
|
|
@ -26,6 +26,7 @@ import { TwoFactorOptionsComponent } from "../auth/popup/two-factor-options.comp
|
|||
import { TwoFactorComponent } from "../auth/popup/two-factor.component";
|
||||
import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component";
|
||||
import { AutofillComponent } from "../autofill/popup/settings/autofill.component";
|
||||
import { PremiumComponent } from "../billing/popup/settings/premium.component";
|
||||
import BrowserPopupUtils from "../platform/popup/browser-popup-utils";
|
||||
import { GeneratorComponent } from "../tools/popup/generator/generator.component";
|
||||
import { PasswordGeneratorHistoryComponent } from "../tools/popup/generator/password-generator-history.component";
|
||||
|
@ -51,7 +52,6 @@ import { ExcludedDomainsComponent } from "./settings/excluded-domains.component"
|
|||
import { FoldersComponent } from "./settings/folders.component";
|
||||
import { HelpAndFeedbackComponent } from "./settings/help-and-feedback.component";
|
||||
import { OptionsComponent } from "./settings/options.component";
|
||||
import { PremiumComponent } from "./settings/premium.component";
|
||||
import { SettingsComponent } from "./settings/settings.component";
|
||||
import { SyncComponent } from "./settings/sync.component";
|
||||
import { TabsComponent } from "./tabs.component";
|
||||
|
|
|
@ -35,6 +35,7 @@ import { TwoFactorOptionsComponent } from "../auth/popup/two-factor-options.comp
|
|||
import { TwoFactorComponent } from "../auth/popup/two-factor.component";
|
||||
import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component";
|
||||
import { AutofillComponent } from "../autofill/popup/settings/autofill.component";
|
||||
import { PremiumComponent } from "../billing/popup/settings/premium.component";
|
||||
import { HeaderComponent } from "../platform/popup/header.component";
|
||||
import { PopupFooterComponent } from "../platform/popup/layout/popup-footer.component";
|
||||
import { PopupHeaderComponent } from "../platform/popup/layout/popup-header.component";
|
||||
|
@ -76,7 +77,6 @@ import { ExcludedDomainsComponent } from "./settings/excluded-domains.component"
|
|||
import { FoldersComponent } from "./settings/folders.component";
|
||||
import { HelpAndFeedbackComponent } from "./settings/help-and-feedback.component";
|
||||
import { OptionsComponent } from "./settings/options.component";
|
||||
import { PremiumComponent } from "./settings/premium.component";
|
||||
import { SettingsComponent } from "./settings/settings.component";
|
||||
import { SyncComponent } from "./settings/sync.component";
|
||||
import { VaultTimeoutInputComponent } from "./settings/vault-timeout-input.component";
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { StateProvider } from "@bitwarden/common/platform/state";
|
||||
import { SearchService } from "@bitwarden/common/services/search.service";
|
||||
|
||||
export class PopupSearchService extends SearchService {
|
||||
constructor(logService: LogService, i18nService: I18nService, stateProvider: StateProvider) {
|
||||
super(logService, i18nService, stateProvider);
|
||||
}
|
||||
|
||||
clearIndex(): Promise<void> {
|
||||
throw new Error("Not available.");
|
||||
}
|
||||
|
||||
indexCiphers(): Promise<void> {
|
||||
throw new Error("Not available.");
|
||||
}
|
||||
|
||||
async getIndexForSearch() {
|
||||
return await super.getIndexForSearch();
|
||||
}
|
||||
}
|
|
@ -19,7 +19,6 @@ import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.
|
|||
import { AuthRequestServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
|
||||
import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abstractions/search.service";
|
||||
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
|
||||
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
|
@ -100,6 +99,8 @@ import { runInsideAngular } from "../../platform/browser/run-inside-angular.oper
|
|||
/* eslint-disable no-restricted-imports */
|
||||
import { ChromeMessageSender } from "../../platform/messaging/chrome-message.sender";
|
||||
/* eslint-enable no-restricted-imports */
|
||||
import { OffscreenDocumentService } from "../../platform/offscreen-document/abstractions/offscreen-document";
|
||||
import { DefaultOffscreenDocumentService } from "../../platform/offscreen-document/offscreen-document.service";
|
||||
import BrowserPopupUtils from "../../platform/popup/browser-popup-utils";
|
||||
import { BrowserFileDownloadService } from "../../platform/popup/services/browser-file-download.service";
|
||||
import { BrowserStateService as StateServiceAbstraction } from "../../platform/services/abstractions/browser-state.service";
|
||||
|
@ -123,7 +124,6 @@ import { VaultFilterService } from "../../vault/services/vault-filter.service";
|
|||
import { DebounceNavigationService } from "./debounce-navigation.service";
|
||||
import { InitService } from "./init.service";
|
||||
import { PopupCloseWarningService } from "./popup-close-warning.service";
|
||||
import { PopupSearchService } from "./popup-search.service";
|
||||
|
||||
const OBSERVABLE_LARGE_OBJECT_MEMORY_STORAGE = new SafeInjectionToken<
|
||||
AbstractStorageService & ObservableStorageService
|
||||
|
@ -180,26 +180,11 @@ const safeProviders: SafeProvider[] = [
|
|||
useFactory: getBgService<SsoLoginServiceAbstraction>("ssoLoginService"),
|
||||
deps: [],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: SearchServiceAbstraction,
|
||||
useClass: PopupSearchService,
|
||||
deps: [LogService, I18nServiceAbstraction, StateProvider],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: CipherService,
|
||||
useFactory: getBgService<CipherService>("cipherService"),
|
||||
deps: [],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: CryptoFunctionService,
|
||||
useFactory: () => new WebCryptoFunctionService(window),
|
||||
deps: [],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: CollectionService,
|
||||
useFactory: getBgService<CollectionService>("collectionService"),
|
||||
deps: [],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: LogService,
|
||||
useFactory: (platformUtilsService: PlatformUtilsService) =>
|
||||
|
@ -287,9 +272,17 @@ const safeProviders: SafeProvider[] = [
|
|||
useFactory: getBgService<DevicesServiceAbstraction>("devicesService"),
|
||||
deps: [],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: OffscreenDocumentService,
|
||||
useClass: DefaultOffscreenDocumentService,
|
||||
deps: [],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: PlatformUtilsService,
|
||||
useFactory: (toastService: ToastService) => {
|
||||
useFactory: (
|
||||
toastService: ToastService,
|
||||
offscreenDocumentService: OffscreenDocumentService,
|
||||
) => {
|
||||
return new ForegroundPlatformUtilsService(
|
||||
toastService,
|
||||
(clipboardValue: string, clearMs: number) => {
|
||||
|
@ -306,9 +299,10 @@ const safeProviders: SafeProvider[] = [
|
|||
return response.result;
|
||||
},
|
||||
window,
|
||||
offscreenDocumentService,
|
||||
);
|
||||
},
|
||||
deps: [ToastService],
|
||||
deps: [ToastService, OffscreenDocumentService],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: PasswordGenerationServiceAbstraction,
|
||||
|
@ -352,7 +346,7 @@ const safeProviders: SafeProvider[] = [
|
|||
safeProvider({
|
||||
provide: ScriptInjectorService,
|
||||
useClass: BrowserScriptInjectorService,
|
||||
deps: [],
|
||||
deps: [PlatformUtilsService, LogService],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: KeyConnectorService,
|
||||
|
|
|
@ -5,6 +5,8 @@ import { PolicyService } from "@bitwarden/common/admin-console/services/policy/p
|
|||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { Importer, ImportResult, ImportServiceAbstraction } from "@bitwarden/importer/core";
|
||||
|
||||
|
@ -38,10 +40,12 @@ describe("FilelessImporterBackground ", () => {
|
|||
const notificationBackground = mock<NotificationBackground>();
|
||||
const importService = mock<ImportServiceAbstraction>();
|
||||
const syncService = mock<SyncService>();
|
||||
const platformUtilsService = mock<PlatformUtilsService>();
|
||||
const logService = mock<LogService>();
|
||||
let scriptInjectorService: BrowserScriptInjectorService;
|
||||
|
||||
beforeEach(() => {
|
||||
scriptInjectorService = new BrowserScriptInjectorService();
|
||||
scriptInjectorService = new BrowserScriptInjectorService(platformUtilsService, logService);
|
||||
filelessImporterBackground = new FilelessImporterBackground(
|
||||
configService,
|
||||
authService,
|
||||
|
|
|
@ -70,13 +70,13 @@ export class Fido2Background implements Fido2BackgroundInterface {
|
|||
*/
|
||||
async injectFido2ContentScriptsInAllTabs() {
|
||||
const tabs = await BrowserApi.tabsQuery({});
|
||||
|
||||
for (let index = 0; index < tabs.length; index++) {
|
||||
const tab = tabs[index];
|
||||
if (!tab.url?.startsWith("https")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
void this.injectFido2ContentScripts(tab);
|
||||
if (tab.url?.startsWith("https")) {
|
||||
void this.injectFido2ContentScripts(tab);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,17 +15,43 @@ jest.mock("../../../autofill/utils", () => ({
|
|||
}),
|
||||
}));
|
||||
|
||||
const originalGlobalThis = globalThis;
|
||||
const mockGlobalThisDocument = {
|
||||
...originalGlobalThis.document,
|
||||
contentType: "text/html",
|
||||
location: {
|
||||
...originalGlobalThis.document.location,
|
||||
href: "https://localhost",
|
||||
origin: "https://localhost",
|
||||
protocol: "https:",
|
||||
},
|
||||
};
|
||||
|
||||
describe("Fido2 Content Script", () => {
|
||||
beforeAll(() => {
|
||||
(jest.spyOn(globalThis, "document", "get") as jest.Mock).mockImplementation(
|
||||
() => mockGlobalThisDocument,
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
let messenger: Messenger;
|
||||
const messengerForDOMCommunicationSpy = jest
|
||||
.spyOn(Messenger, "forDOMCommunication")
|
||||
.mockImplementation((window) => {
|
||||
const windowOrigin = window.location.origin;
|
||||
.mockImplementation((context) => {
|
||||
const windowOrigin = context.location.origin;
|
||||
|
||||
messenger = new Messenger({
|
||||
postMessage: (message, port) => window.postMessage(message, windowOrigin, [port]),
|
||||
addEventListener: (listener) => window.addEventListener("message", listener),
|
||||
removeEventListener: (listener) => window.removeEventListener("message", listener),
|
||||
postMessage: (message, port) => context.postMessage(message, windowOrigin, [port]),
|
||||
addEventListener: (listener) => context.addEventListener("message", listener),
|
||||
removeEventListener: (listener) => context.removeEventListener("message", listener),
|
||||
});
|
||||
messenger.destroy = jest.fn();
|
||||
return messenger;
|
||||
|
@ -33,16 +59,6 @@ describe("Fido2 Content Script", () => {
|
|||
const portSpy: MockProxy<chrome.runtime.Port> = createPortSpyMock(Fido2PortName.InjectedScript);
|
||||
chrome.runtime.connect = jest.fn(() => portSpy);
|
||||
|
||||
afterEach(() => {
|
||||
Object.defineProperty(document, "contentType", {
|
||||
value: "text/html",
|
||||
writable: true,
|
||||
});
|
||||
|
||||
jest.clearAllMocks();
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
it("destroys the messenger when the port is disconnected", () => {
|
||||
require("./content-script");
|
||||
|
||||
|
@ -151,11 +167,31 @@ describe("Fido2 Content Script", () => {
|
|||
await expect(result).rejects.toEqual(errorMessage);
|
||||
});
|
||||
|
||||
it("skips initializing the content script if the document content type is not 'text/html'", () => {
|
||||
Object.defineProperty(document, "contentType", {
|
||||
value: "application/json",
|
||||
writable: true,
|
||||
});
|
||||
it("skips initializing if the document content type is not 'text/html'", () => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
(jest.spyOn(globalThis, "document", "get") as jest.Mock).mockImplementation(() => ({
|
||||
...mockGlobalThisDocument,
|
||||
contentType: "application/json",
|
||||
}));
|
||||
|
||||
require("./content-script");
|
||||
|
||||
expect(messengerForDOMCommunicationSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("skips initializing if the document location protocol is not 'https'", () => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
(jest.spyOn(globalThis, "document", "get") as jest.Mock).mockImplementation(() => ({
|
||||
...mockGlobalThisDocument,
|
||||
location: {
|
||||
...mockGlobalThisDocument.location,
|
||||
href: "http://localhost",
|
||||
origin: "http://localhost",
|
||||
protocol: "http:",
|
||||
},
|
||||
}));
|
||||
|
||||
require("./content-script");
|
||||
|
||||
|
|
|
@ -15,7 +15,11 @@ import {
|
|||
import { MessageWithMetadata, Messenger } from "./messaging/messenger";
|
||||
|
||||
(function (globalContext) {
|
||||
if (globalContext.document.contentType !== "text/html") {
|
||||
const shouldExecuteContentScript =
|
||||
globalContext.document.contentType === "text/html" &&
|
||||
globalContext.document.location.protocol === "https:";
|
||||
|
||||
if (!shouldExecuteContentScript) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,9 +6,14 @@ import { MessageType } from "./messaging/message";
|
|||
import { Messenger } from "./messaging/messenger";
|
||||
|
||||
(function (globalContext) {
|
||||
if (globalContext.document.contentType !== "text/html") {
|
||||
const shouldExecuteContentScript =
|
||||
globalContext.document.contentType === "text/html" &&
|
||||
globalContext.document.location.protocol === "https:";
|
||||
|
||||
if (!shouldExecuteContentScript) {
|
||||
return;
|
||||
}
|
||||
|
||||
const BrowserPublicKeyCredential = globalContext.PublicKeyCredential;
|
||||
const BrowserNavigatorCredentials = navigator.credentials;
|
||||
const BrowserAuthenticatorAttestationResponse = globalContext.AuthenticatorAttestationResponse;
|
||||
|
|
|
@ -10,17 +10,29 @@ import { WebauthnUtils } from "../webauthn-utils";
|
|||
import { MessageType } from "./messaging/message";
|
||||
import { Messenger } from "./messaging/messenger";
|
||||
|
||||
const originalGlobalThis = globalThis;
|
||||
const mockGlobalThisDocument = {
|
||||
...originalGlobalThis.document,
|
||||
contentType: "text/html",
|
||||
location: {
|
||||
...originalGlobalThis.document.location,
|
||||
href: "https://localhost",
|
||||
origin: "https://localhost",
|
||||
protocol: "https:",
|
||||
},
|
||||
};
|
||||
|
||||
let messenger: Messenger;
|
||||
jest.mock("./messaging/messenger", () => {
|
||||
return {
|
||||
Messenger: class extends jest.requireActual("./messaging/messenger").Messenger {
|
||||
static forDOMCommunication: any = jest.fn((window) => {
|
||||
const windowOrigin = window.location.origin;
|
||||
static forDOMCommunication: any = jest.fn((context) => {
|
||||
const windowOrigin = context.location.origin;
|
||||
|
||||
messenger = new Messenger({
|
||||
postMessage: (message, port) => window.postMessage(message, windowOrigin, [port]),
|
||||
addEventListener: (listener) => window.addEventListener("message", listener),
|
||||
removeEventListener: (listener) => window.removeEventListener("message", listener),
|
||||
postMessage: (message, port) => context.postMessage(message, windowOrigin, [port]),
|
||||
addEventListener: (listener) => context.addEventListener("message", listener),
|
||||
removeEventListener: (listener) => context.removeEventListener("message", listener),
|
||||
});
|
||||
messenger.destroy = jest.fn();
|
||||
return messenger;
|
||||
|
@ -31,6 +43,10 @@ jest.mock("./messaging/messenger", () => {
|
|||
jest.mock("../webauthn-utils");
|
||||
|
||||
describe("Fido2 page script with native WebAuthn support", () => {
|
||||
(jest.spyOn(globalThis, "document", "get") as jest.Mock).mockImplementation(
|
||||
() => mockGlobalThisDocument,
|
||||
);
|
||||
|
||||
const mockCredentialCreationOptions = createCredentialCreationOptionsMock();
|
||||
const mockCreateCredentialsResult = createCreateCredentialResultMock();
|
||||
const mockCredentialRequestOptions = createCredentialRequestOptionsMock();
|
||||
|
@ -39,9 +55,12 @@ describe("Fido2 page script with native WebAuthn support", () => {
|
|||
|
||||
require("./page-script");
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
describe("creating WebAuthn credentials", () => {
|
||||
|
@ -118,4 +137,42 @@ describe("Fido2 page script with native WebAuthn support", () => {
|
|||
expect(messenger.destroy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("content script execution", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
it("skips initializing if the document content type is not 'text/html'", () => {
|
||||
jest.spyOn(Messenger, "forDOMCommunication");
|
||||
|
||||
(jest.spyOn(globalThis, "document", "get") as jest.Mock).mockImplementation(() => ({
|
||||
...mockGlobalThisDocument,
|
||||
contentType: "json/application",
|
||||
}));
|
||||
|
||||
require("./content-script");
|
||||
|
||||
expect(Messenger.forDOMCommunication).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("skips initializing if the document location protocol is not 'https'", () => {
|
||||
jest.spyOn(Messenger, "forDOMCommunication");
|
||||
|
||||
(jest.spyOn(globalThis, "document", "get") as jest.Mock).mockImplementation(() => ({
|
||||
...mockGlobalThisDocument,
|
||||
location: {
|
||||
...mockGlobalThisDocument.location,
|
||||
href: "http://localhost",
|
||||
origin: "http://localhost",
|
||||
protocol: "http:",
|
||||
},
|
||||
}));
|
||||
|
||||
require("./content-script");
|
||||
|
||||
expect(Messenger.forDOMCommunication).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,17 +9,29 @@ import { WebauthnUtils } from "../webauthn-utils";
|
|||
import { MessageType } from "./messaging/message";
|
||||
import { Messenger } from "./messaging/messenger";
|
||||
|
||||
const originalGlobalThis = globalThis;
|
||||
const mockGlobalThisDocument = {
|
||||
...originalGlobalThis.document,
|
||||
contentType: "text/html",
|
||||
location: {
|
||||
...originalGlobalThis.document.location,
|
||||
href: "https://localhost",
|
||||
origin: "https://localhost",
|
||||
protocol: "https:",
|
||||
},
|
||||
};
|
||||
|
||||
let messenger: Messenger;
|
||||
jest.mock("./messaging/messenger", () => {
|
||||
return {
|
||||
Messenger: class extends jest.requireActual("./messaging/messenger").Messenger {
|
||||
static forDOMCommunication: any = jest.fn((window) => {
|
||||
const windowOrigin = window.location.origin;
|
||||
static forDOMCommunication: any = jest.fn((context) => {
|
||||
const windowOrigin = context.location.origin;
|
||||
|
||||
messenger = new Messenger({
|
||||
postMessage: (message, port) => window.postMessage(message, windowOrigin, [port]),
|
||||
addEventListener: (listener) => window.addEventListener("message", listener),
|
||||
removeEventListener: (listener) => window.removeEventListener("message", listener),
|
||||
postMessage: (message, port) => context.postMessage(message, windowOrigin, [port]),
|
||||
addEventListener: (listener) => context.addEventListener("message", listener),
|
||||
removeEventListener: (listener) => context.removeEventListener("message", listener),
|
||||
});
|
||||
messenger.destroy = jest.fn();
|
||||
return messenger;
|
||||
|
@ -30,15 +42,22 @@ jest.mock("./messaging/messenger", () => {
|
|||
jest.mock("../webauthn-utils");
|
||||
|
||||
describe("Fido2 page script without native WebAuthn support", () => {
|
||||
(jest.spyOn(globalThis, "document", "get") as jest.Mock).mockImplementation(
|
||||
() => mockGlobalThisDocument,
|
||||
);
|
||||
|
||||
const mockCredentialCreationOptions = createCredentialCreationOptionsMock();
|
||||
const mockCreateCredentialsResult = createCreateCredentialResultMock();
|
||||
const mockCredentialRequestOptions = createCredentialRequestOptionsMock();
|
||||
const mockCredentialAssertResult = createAssertCredentialResultMock();
|
||||
require("./page-script");
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
describe("creating WebAuthn credentials", () => {
|
||||
|
|
|
@ -292,8 +292,6 @@ export class CurrentTabComponent implements OnInit, OnDestroy {
|
|||
const ciphers = await this.cipherService.getAllDecryptedForUrl(
|
||||
this.url,
|
||||
otherTypes.length > 0 ? otherTypes : null,
|
||||
null,
|
||||
false,
|
||||
);
|
||||
|
||||
this.loginCiphers = [];
|
||||
|
|
|
@ -118,58 +118,58 @@
|
|||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Name" xml:space="preserve">
|
||||
<value>Bitwarden Password Manager</value>
|
||||
<value>Bitwarden - Gestor de contrasenyes</value>
|
||||
</data>
|
||||
<data name="Summary" xml:space="preserve">
|
||||
<value>At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.</value>
|
||||
<value>A casa, a la feina o en moviment, Bitwarden protegeix totes les contrasenyes, claus de pas i informació sensible.</value>
|
||||
</data>
|
||||
<data name="Description" xml:space="preserve">
|
||||
<value>Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more!
|
||||
<value>Reconegut com el millor gestor de contrasenyes per PCMag, WIRED, The Verge, CNET, G2 i més!
|
||||
|
||||
SECURE YOUR DIGITAL LIFE
|
||||
Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access.
|
||||
ASSEGURA LA TEUA VIDA DIGITAL
|
||||
Assegureu-vos la vostra vida digital i protegiu-vos de les violacions de dades generant i desant contrasenyes úniques i fortes per a cada compte. Mantingueu-ho tot en una caixa de contrasenyes xifrada d'extrem a extrem a la qual només podeu accedir.
|
||||
|
||||
ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE
|
||||
Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions.
|
||||
ACCEDEIX A LES SEUES DADES, ON, EN QUALSEVOL MOMENT, EN QUALSEVOL DISPOSITIU
|
||||
Gestiona, emmagatzema, protegeix i comparteix fàcilment contrasenyes il·limitades en dispositius il·limitats sense restriccions.
|
||||
|
||||
EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE
|
||||
Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features.
|
||||
TOTHOM HA DE TENIR LES EINES PER ESTAR SEGURETAT EN LÍNIA
|
||||
Utilitzeu Bitwarden de forma gratuïta sense anuncis ni dades de venda. Bitwarden creu que tothom hauria de tenir la capacitat de mantenir-se segur en línia. Els plans Prèmium ofereixen accés a funcions avançades.
|
||||
|
||||
EMPOWER YOUR TEAMS WITH BITWARDEN
|
||||
Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more.
|
||||
EMPODERA ELS TEUS EQUIPS AMB BITWARDEN
|
||||
Els plans per a equips i empreses inclouen funcions empresarials professionals. Alguns exemples inclouen integració SSO, autoallotjament, integració de directoris i subministrament SCIM, polítiques globals, accés a API, registres d'esdeveniments i molt més.
|
||||
|
||||
Use Bitwarden to secure your workforce and share sensitive information with colleagues.
|
||||
Utilitzeu Bitwarden per protegir la vostra força de treball i compartir informació crítica amb els companys.
|
||||
|
||||
|
||||
More reasons to choose Bitwarden:
|
||||
Més raons per a triar Bitwarden:
|
||||
|
||||
World-Class Encryption
|
||||
Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private.
|
||||
Xifratge de classe mundial
|
||||
Les contrasenyes estan protegides amb un xifratge avançat d'extrem a extrem (AES-256 bits, hashtag salat i PBKDF2 SHA-256) perquè les vostres dades es mantinguen segures i privades.
|
||||
|
||||
3rd-party Audits
|
||||
Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications.
|
||||
Auditories de tercers
|
||||
Bitwarden realitza regularment auditories de seguretat exhaustives de tercers amb empreses de seguretat notables. Aquestes auditories anuals inclouen avaluacions del codi font i proves de penetració a les IP, servidors i aplicacions web de Bitwarden.
|
||||
|
||||
Advanced 2FA
|
||||
Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey.
|
||||
2FA avançat
|
||||
Assegureu el vostre inici de sessió amb un autenticador de tercers, codis enviats per correu electrònic o credencials FIDO2 WebAuthn, com ara una clau de seguretat de maquinari o una clau de pas.
|
||||
|
||||
Bitwarden Send
|
||||
Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure.
|
||||
Bitwarden Enviar
|
||||
Transmet dades directament a altres, mantenint la seguretat xifrada d'extrem a extrem i limitant l'exposició.
|
||||
|
||||
Built-in Generator
|
||||
Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy.
|
||||
Generador incorporat
|
||||
Creeu contrasenyes llargues, complexes i diferents i noms d'usuari únics per a cada lloc que visiteu. Integració amb proveïdors d'àlies de correu electrònic per obtenir més privadesa.
|
||||
|
||||
Global Translations
|
||||
Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin.
|
||||
Traduccions globals
|
||||
Les traduccions de Bitwarden existeixen per a més de 60 idiomes, traduïdes per la comunitat global mitjançant Crowdin.
|
||||
|
||||
Cross-Platform Applications
|
||||
Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more.
|
||||
Aplicacions multiplataforma
|
||||
Assegureu-vos i compartiu dades confidencials a la vostra caixa forta Bitwarden des de qualsevol navegador, dispositiu mòbil o sistema operatiu d'escriptori i molt més.
|
||||
|
||||
Bitwarden secures more than just passwords
|
||||
End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev!
|
||||
Bitwarden assegura més que només contrasenyes
|
||||
Les solucions de gestió de credencials xifrades d'extrem a extrem de Bitwarden permeten a les organitzacions protegir-ho tot, inclosos els secrets dels desenvolupadors i les experiències de clau de pas. Visiteu Bitwarden.com per obtenir més informació sobre Bitwarden gestor de secrets i Bitwarden Passwordless.dev!
|
||||
</value>
|
||||
</data>
|
||||
<data name="AssetTitle" xml:space="preserve">
|
||||
<value>At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.</value>
|
||||
<value>A casa, a la feina o en moviment, Bitwarden protegeix totes les contrasenyes, claus de pas i informació sensible.</value>
|
||||
</data>
|
||||
<data name="ScreenshotSync" xml:space="preserve">
|
||||
<value>Sincronitzeu i accediu a la vostra caixa forta des de diversos dispositius</value>
|
||||
|
|
|
@ -118,64 +118,64 @@
|
|||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Name" xml:space="preserve">
|
||||
<value>Bitwarden Password Manager</value>
|
||||
<value>Bitwarden - Správce hesel</value>
|
||||
</data>
|
||||
<data name="Summary" xml:space="preserve">
|
||||
<value>At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.</value>
|
||||
<value>Bitwarden zabezpečí všechna Vaše hesla, přístupové klíče a citlivé informace doma, v práci nebo na cestách.</value>
|
||||
</data>
|
||||
<data name="Description" xml:space="preserve">
|
||||
<value>Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more!
|
||||
<value>PCMag, WIRED, The Verge, CNET, G2 a další ocenili tohoto správce hesel jako nejlepší!
|
||||
|
||||
SECURE YOUR DIGITAL LIFE
|
||||
Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access.
|
||||
ZABEZPEČTE SVŮJ DIGITÁLNÍ ŽIVOT
|
||||
Zabezpečte svůj digitální život a chraňte se před únikem dat tím, že si pro každý účet vytvoříte a uložíte jedinečná, silná hesla. Vše uchováváte v end-to-end šifrovaném trezoru hesel, ke kterému máte přístup jen Vy.
|
||||
|
||||
ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE
|
||||
Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions.
|
||||
PŘÍSTUP K DATŮM ODKUDKOLI, KDYKOLI A Z JAKÉHOKOLI ZAŘÍZENÍ
|
||||
Snadno spravujte, ukládejte, zabezpečujte a sdílejte neomezený počet hesel na neomezeném počtu zařízení bez omezení.
|
||||
|
||||
EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE
|
||||
Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features.
|
||||
KAŽDÝ BY MĚL MÍT K DISPOZICI NÁSTROJE, KTERÉ MU UMOŽNÍ ZŮSTAT V BEZPEČÍ ONLINE
|
||||
Využívejte Bitwarden zdarma bez reklam a prodeje dat. Bitwarden věří, že každý by měl mít možnost zůstat v bezpečí online. Prémiové plány nabízejí přístup k pokročilým funkcím.
|
||||
|
||||
EMPOWER YOUR TEAMS WITH BITWARDEN
|
||||
Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more.
|
||||
POSILTE SVÉ TÝMY POMOCÍ BITWARDEN
|
||||
Plány pro týmy a podniky jsou vybaveny profesionálními podnikovými funkcemi. Mezi příklady patří integrace SSO, selfhosting, integrace adresářů a poskytování SCIM, globální zásady, přístup k API, protokoly událostí a další.
|
||||
|
||||
Use Bitwarden to secure your workforce and share sensitive information with colleagues.
|
||||
Použijte Bitwarden k zabezpečení svých zaměstnanců a sdílení citlivých informací s kolegy.
|
||||
|
||||
|
||||
More reasons to choose Bitwarden:
|
||||
Další důvody, proč si vybrat Bitwarden:
|
||||
|
||||
World-Class Encryption
|
||||
Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private.
|
||||
Šifrování na světové úrovni
|
||||
Hesla jsou chráněna pokročilým end-to-end šifrováním (AES-256 bitů, solený hashtag a PBKDF2 SHA-256), takže Vaše data zůstanou v bezpečí a soukromí.
|
||||
|
||||
3rd-party Audits
|
||||
Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications.
|
||||
Audity třetích stran
|
||||
Společnost Bitwarden pravidelně provádí komplexní bezpečnostní audity třetích stran s významnými bezpečnostními firmami. Tyto každoroční audity zahrnují posouzení zdrojového kódu a penetrační testy napříč IP adresami, servery a webovými aplikacemi společnosti Bitwarden.
|
||||
|
||||
Advanced 2FA
|
||||
Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey.
|
||||
Pokročilé 2FA
|
||||
Zabezpečte své přihlášení pomocí ověření třetí strany, e-mailových kódů nebo ověření FIDO2 WebAuthn, jako je hardwarový bezpečnostní klíč nebo přístupový klíč.
|
||||
|
||||
Bitwarden Send
|
||||
Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure.
|
||||
Přenášejte data přímo ostatním při zachování end-to-end šifrovaného zabezpečení a omezení odhalení.
|
||||
|
||||
Built-in Generator
|
||||
Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy.
|
||||
Vestavěný generátor
|
||||
Vytvářejte dlouhá, složitá a odlišná hesla a jedinečná uživatelská jména pro každou navštívenou stránku. Integrace s poskytovateli e-mailových aliasů pro zajištění dalšího soukromí.
|
||||
|
||||
Global Translations
|
||||
Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin.
|
||||
Globální překlady
|
||||
Pro Bitwarden existují překlady ve více než 60 jazycích, které překládá globální komunita prostřednictvím služby Crowdin.
|
||||
|
||||
Cross-Platform Applications
|
||||
Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more.
|
||||
Aplikace pro více platforem
|
||||
Zabezpečte a sdílejte citlivá data v rámci svého trezoru Bitwarden z jakéhokoli prohlížeče, mobilního zařízení nebo desktopového operačního systému a dalších.
|
||||
|
||||
Bitwarden secures more than just passwords
|
||||
End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev!
|
||||
Bitwarden zabezpečuje více než jen hesla
|
||||
Řešení pro komplexní správu šifrovaných pověření od společnosti Bitwarden umožňují organizacím zabezpečit vše, včetně přístupových a/nebo tajných klíčů pro vývojáře. Navštivte Bitwarden.com a dozvíte se více o Bitwarden Secrets Manager a Bitwarden Passwordless.dev!
|
||||
</value>
|
||||
</data>
|
||||
<data name="AssetTitle" xml:space="preserve">
|
||||
<value>At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.</value>
|
||||
<value>Bitwarden zabezpečí všechna Vaše hesla, přístupové klíče a citlivé informace doma, v práci nebo na cestách.</value>
|
||||
</data>
|
||||
<data name="ScreenshotSync" xml:space="preserve">
|
||||
<value>Synchronizujte a přistupujte ke svému trezoru z různých zařízení</value>
|
||||
</data>
|
||||
<data name="ScreenshotVault" xml:space="preserve">
|
||||
<value>Spravujte veškeré své přihlašovací údaje z bezpečného trezoru</value>
|
||||
<value>Spravujte veškeré své přihlašovací údaje v bezpečném trezoru</value>
|
||||
</data>
|
||||
<data name="ScreenshotAutofill" xml:space="preserve">
|
||||
<value>Rychle vyplňte své přihlašovací údaje na webových stránkách</value>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue