1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-17 08:43:33 +00:00

Merge master into merge/feature/org-admin-refresh (using imerge)

This commit is contained in:
Shane Melton
2022-12-13 07:31:22 -08:00
721 changed files with 18307 additions and 5660 deletions

View File

@@ -86,7 +86,7 @@
"error", "error",
{ {
"zones": [ "zones": [
// Do not allow angular/node/electron code to be imported into common // Do not allow angular/node code to be imported into common
{ {
"target": "./libs/common/**/*", "target": "./libs/common/**/*",
"from": "./libs/angular/**/*" "from": "./libs/angular/**/*"
@@ -94,10 +94,6 @@
{ {
"target": "./libs/common/**/*", "target": "./libs/common/**/*",
"from": "./libs/node/**/*" "from": "./libs/node/**/*"
},
{
"target": "./libs/common/**/*",
"from": "./libs/electron/**/*"
} }
] ]
} }
@@ -131,12 +127,6 @@
"rules": { "rules": {
"no-restricted-imports": ["error", { "patterns": ["@bitwarden/node/*", "src/**/*"] }] "no-restricted-imports": ["error", { "patterns": ["@bitwarden/node/*", "src/**/*"] }]
} }
},
{
"files": ["libs/electron/src/**/*.ts"],
"rules": {
"no-restricted-imports": ["error", { "patterns": ["@bitwarden/electron/*", "src/**/*"] }]
}
} }
] ]
} }

View File

@@ -4,14 +4,10 @@
./apps/browser/src/safari/desktop/Base.lproj ./apps/browser/src/safari/desktop/Base.lproj
./apps/browser/src/services/vaultTimeout ./apps/browser/src/services/vaultTimeout
./apps/browser/store/windows/Assets ./apps/browser/store/windows/Assets
./apps/desktop/src/models/nativeMessaging
./apps/desktop/src/models/nativeMessaging/encryptedMessagePayloads
./apps/desktop/src/models/nativeMessaging/encryptedMessageResponses
./libs/common/spec/misc/logInStrategies ./libs/common/spec/misc/logInStrategies
./libs/common/src/abstractions/fileDownload ./libs/common/src/abstractions/fileDownload
./libs/common/src/abstractions/userVerification ./libs/common/src/abstractions/userVerification
./libs/common/src/abstractions/vaultTimeout ./libs/common/src/abstractions/vaultTimeout
./libs/common/src/emailForwarders
./libs/common/src/misc/logInStrategies ./libs/common/src/misc/logInStrategies
./libs/common/src/services/userVerification ./libs/common/src/services/userVerification
./libs/common/src/services/vaultTimeout ./libs/common/src/services/vaultTimeout
@@ -112,12 +108,6 @@
./libs/common/src/enums/nativeMessagingVersion.ts ./libs/common/src/enums/nativeMessagingVersion.ts
./libs/common/src/enums/cipherRepromptType.ts ./libs/common/src/enums/cipherRepromptType.ts
./libs/common/src/enums/organizationUserType.ts ./libs/common/src/enums/organizationUserType.ts
./libs/common/src/emailForwarders/fastmailForwarder.ts
./libs/common/src/emailForwarders/duckDuckGoForwarder.ts
./libs/common/src/emailForwarders/firefoxRelayForwarder.ts
./libs/common/src/emailForwarders/anonAddyForwarder.ts
./libs/common/src/emailForwarders/simpleLoginForwarder.ts
./libs/common/src/emailForwarders/forwarderOptions.ts
./libs/common/src/factories/accountFactory.ts ./libs/common/src/factories/accountFactory.ts
./libs/common/src/factories/globalStateFactory.ts ./libs/common/src/factories/globalStateFactory.ts
./libs/common/src/factories/stateFactory.ts ./libs/common/src/factories/stateFactory.ts
@@ -162,27 +152,6 @@
./libs/common/src/services/bitwardenFileUpload.service.ts ./libs/common/src/services/bitwardenFileUpload.service.ts
./libs/common/src/services/webCryptoFunction.service.ts ./libs/common/src/services/webCryptoFunction.service.ts
./libs/common/src/interfaces/IEncrypted.ts ./libs/common/src/interfaces/IEncrypted.ts
./libs/node/spec/cli/consoleLog.service.spec.ts
./libs/node/src/cli/models/response/baseResponse.ts
./libs/node/src/cli/models/response/stringResponse.ts
./libs/node/src/cli/models/response/fileResponse.ts
./libs/node/src/cli/models/response/messageResponse.ts
./libs/node/src/cli/models/response/listResponse.ts
./libs/node/src/cli/baseProgram.ts
./libs/node/src/cli/services/consoleLog.service.ts
./libs/node/src/cli/services/cliPlatformUtils.service.ts
./libs/node/src/services/nodeApi.service.ts
./libs/node/src/services/lowdbStorage.service.ts
./libs/electron/spec/services/electronLog.service.spec.ts
./libs/electron/src/baseMenu.ts
./libs/electron/src/services/electronLog.service.ts
./libs/electron/src/services/electronStorage.service.ts
./libs/electron/src/services/electronRendererMessaging.service.ts
./libs/electron/src/services/electronMainMessaging.service.ts
./libs/electron/src/services/electronPlatformUtils.service.ts
./libs/electron/src/services/electronRendererStorage.service.ts
./libs/electron/src/services/electronCrypto.service.ts
./libs/electron/src/services/electronRendererSecureStorage.service.ts
./README.md ./README.md
./LICENSE_BITWARDEN.txt ./LICENSE_BITWARDEN.txt
./CONTRIBUTING.md ./CONTRIBUTING.md
@@ -197,64 +166,11 @@
./apps/desktop/resources/appx/StoreLogo.png ./apps/desktop/resources/appx/StoreLogo.png
./apps/desktop/resources/appx/Wide310x150Logo.png ./apps/desktop/resources/appx/Wide310x150Logo.png
./apps/desktop/resources/appx/Square44x44Logo.png ./apps/desktop/resources/appx/Square44x44Logo.png
./apps/desktop/native-messaging-test-runner/src/ipcService.ts
./apps/desktop/native-messaging-test-runner/src/nativeMessageService.ts
./apps/desktop/native-messaging-test-runner/src/logUtils.ts
./apps/desktop/README.md ./apps/desktop/README.md
./apps/desktop/desktop_native/Cargo.toml ./apps/desktop/desktop_native/Cargo.toml
./apps/desktop/desktop_native/Cargo.lock ./apps/desktop/desktop_native/Cargo.lock
./apps/desktop/src/app/services/desktopFileDownloadService.ts
./apps/desktop/src/models/nativeMessaging/unencryptedCommand.ts
./apps/desktop/src/models/nativeMessaging/encryptedMessage.ts
./apps/desktop/src/models/nativeMessaging/legacyMessageWrapper.ts
./apps/desktop/src/models/nativeMessaging/unencryptedMessage.ts
./apps/desktop/src/models/nativeMessaging/unencryptedMessageResponse.ts
./apps/desktop/src/models/nativeMessaging/encryptedCommand.ts
./apps/desktop/src/models/nativeMessaging/encryptedMessageResponse.ts
./apps/desktop/src/models/nativeMessaging/encryptedMessageResponses/successStatusResponse.ts
./apps/desktop/src/models/nativeMessaging/encryptedMessageResponses/generateResponse.ts
./apps/desktop/src/models/nativeMessaging/encryptedMessageResponses/failureStatusResponse.ts
./apps/desktop/src/models/nativeMessaging/encryptedMessageResponses/cannotDecryptErrorResponse.ts
./apps/desktop/src/models/nativeMessaging/encryptedMessageResponses/userStatusErrorResponse.ts
./apps/desktop/src/models/nativeMessaging/encryptedMessageResponses/accountStatusResponse.ts
./apps/desktop/src/models/nativeMessaging/encryptedMessageResponses/encryptedMessageResponse.ts
./apps/desktop/src/models/nativeMessaging/encryptedMessageResponses/cipherResponse.ts
./apps/desktop/src/models/nativeMessaging/decryptedCommandData.ts
./apps/desktop/src/models/nativeMessaging/messageCommon.ts
./apps/desktop/src/models/nativeMessaging/legacyMessage.ts
./apps/desktop/src/models/nativeMessaging/encryptedMessagePayloads/credentialUpdatePayload.ts
./apps/desktop/src/models/nativeMessaging/encryptedMessagePayloads/passwordGeneratePayload.ts
./apps/desktop/src/models/nativeMessaging/encryptedMessagePayloads/credentialCreatePayload.ts
./apps/desktop/src/models/nativeMessaging/encryptedMessagePayloads/credentialRetrievePayload.ts
./apps/desktop/src/main/desktopCredentialStorageListener.ts
./apps/desktop/src/main/powerMonitor.main.ts
./apps/desktop/src/main/nativeMessaging.main.ts
./apps/desktop/src/services/passwordReprompt.service.ts
./apps/desktop/src/services/encryptedMessageHandlerService.ts
./apps/desktop/src/services/nativeMessaging.service.ts
./apps/desktop/src/services/nativeMessageHandler.service.ts
./apps/cli/stores/chocolatey/tools/VERIFICATION.txt ./apps/cli/stores/chocolatey/tools/VERIFICATION.txt
./apps/cli/README.md ./apps/cli/README.md
./apps/cli/src/models/response/sendFileResponse.ts
./apps/cli/src/models/response/organizationCollectionResponse.ts
./apps/cli/src/models/response/collectionResponse.ts
./apps/cli/src/models/response/templateResponse.ts
./apps/cli/src/models/response/passwordHistoryResponse.ts
./apps/cli/src/models/response/folderResponse.ts
./apps/cli/src/models/response/loginResponse.ts
./apps/cli/src/models/response/sendAccessResponse.ts
./apps/cli/src/models/response/sendResponse.ts
./apps/cli/src/models/response/cipherResponse.ts
./apps/cli/src/models/response/organizationResponse.ts
./apps/cli/src/models/response/organizationUserResponse.ts
./apps/cli/src/models/response/sendTextResponse.ts
./apps/cli/src/models/response/attachmentResponse.ts
./apps/cli/src/models/selectionReadOnly.ts
./apps/cli/src/models/request/organizationCollectionRequest.ts
./apps/cli/src/commands/convertToKeyConnector.command.ts
./apps/cli/src/commands/send/removePassword.command.ts
./apps/cli/src/services/lowdbStorage.service.ts
./apps/cli/src/services/nodeEnvSecureStorage.service.ts
./apps/browser/README.md ./apps/browser/README.md
./apps/browser/store/windows/AppxManifest.xml ./apps/browser/store/windows/AppxManifest.xml
./apps/browser/src/background/nativeMessaging.background.ts ./apps/browser/src/background/nativeMessaging.background.ts

View File

@@ -63,8 +63,8 @@ jobs:
repo_url=https://github.com/$GITHUB_REPOSITORY.git repo_url=https://github.com/$GITHUB_REPOSITORY.git
adj_build_num=${GITHUB_SHA:0:7} adj_build_num=${GITHUB_SHA:0:7}
echo "::set-output name=repo_url::$repo_url" echo "repo_url=$repo_url" >> $GITHUB_OUTPUT
echo "::set-output name=adj_build_number::$adj_build_num" echo "adj_build_number=$adj_build_num" >> $GITHUB_OUTPUT
locales-test: locales-test:

View File

@@ -62,7 +62,7 @@ jobs:
id: retrieve-version id: retrieve-version
run: | run: |
PKG_VERSION=$(jq -r .version package.json) PKG_VERSION=$(jq -r .version package.json)
echo "::set-output name=package_version::$PKG_VERSION" echo "package_version=$PKG_VERSION" >> $GITHUB_OUTPUT
cli: cli:
@@ -72,7 +72,7 @@ jobs:
- setup - setup
env: env:
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }} _PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
_WIN_PKG_FETCH_VERSION: 16.15.0 _WIN_PKG_FETCH_VERSION: 16.16.0
_WIN_PKG_VERSION: 3.4 _WIN_PKG_VERSION: 3.4
steps: steps:
- name: Checkout repo - name: Checkout repo

View File

@@ -87,29 +87,29 @@ jobs:
id: retrieve-version id: retrieve-version
run: | run: |
PKG_VERSION=$(jq -r .version src/package.json) PKG_VERSION=$(jq -r .version src/package.json)
echo "::set-output name=package_version::$PKG_VERSION" echo "package_version=$PKG_VERSION" >> $GITHUB_OUTPUT
- name: Increment Version - name: Increment Version
id: increment-version id: increment-version
run: | run: |
BUILD_NUMBER=$(expr 3000 + $GITHUB_RUN_NUMBER) BUILD_NUMBER=$(expr 3000 + $GITHUB_RUN_NUMBER)
echo "Setting build number to $BUILD_NUMBER" echo "Setting build number to $BUILD_NUMBER"
echo "::set-output name=build_number::$BUILD_NUMBER" echo "build_number=$BUILD_NUMBER" >> $GITHUB_OUTPUT
- name: Get Version Channel - name: Get Version Channel
id: release-channel id: release-channel
run: | run: |
case "${{ steps.retrieve-version.outputs.package_version }}" in case "${{ steps.retrieve-version.outputs.package_version }}" in
*"alpha"*) *"alpha"*)
echo "::set-output name=channel::alpha" echo "channel=alpha" >> $GITHUB_OUTPUT
echo "[!] We do not yet support 'alpha'" echo "[!] We do not yet support 'alpha'"
exit 1 exit 1
;; ;;
*"beta"*) *"beta"*)
echo "::set-output name=channel::beta" echo "channel=beta" >> $GITHUB_OUTPUT
;; ;;
*) *)
echo "::set-output name=channel::latest" echo "channel=latest" >> $GITHUB_OUTPUT
;; ;;
esac esac
@@ -117,15 +117,15 @@ jobs:
id: branch-check id: branch-check
run: | run: |
if [[ $(git ls-remote --heads origin rc) ]]; then if [[ $(git ls-remote --heads origin rc) ]]; then
echo "::set-output name=rc_branch_exists::1" echo "rc_branch_exists=1" >> $GITHUB_OUTPUT
else else
echo "::set-output name=rc_branch_exists::0" echo "rc_branch_exists=0" >> $GITHUB_OUTPUT
fi fi
if [[ $(git ls-remote --heads origin hotfix-rc-desktop) ]]; then if [[ $(git ls-remote --heads origin hotfix-rc-desktop) ]]; then
echo "::set-output name=hotfix_branch_exists::1" echo "hotfix_branch_exists=1" >> $GITHUB_OUTPUT
else else
echo "::set-output name=hotfix_branch_exists::0" echo "hotfix_branch_exists=0" >> $GITHUB_OUTPUT
fi fi

View File

@@ -60,7 +60,7 @@ jobs:
- name: Get GitHub sha as version - name: Get GitHub sha as version
id: version id: version
run: echo "::set-output name=value::${GITHUB_SHA:0:7}" run: echo "value=${GITHUB_SHA:0:7}" >> $GITHUB_OUTPUT
build-artifacts: build-artifacts:
name: Build artifacts name: Build artifacts
@@ -112,7 +112,7 @@ jobs:
if: matrix.name == 'cloud-QA' if: matrix.name == 'cloud-QA'
run: | run: |
VERSION=$( jq -r ".version" package.json) VERSION=$( jq -r ".version" package.json)
jq --arg version "$VERSION - ${GITHUB_SHA:0:7}" '.version = $version' package.json > package.json.tmp jq --arg version "$VERSION+${GITHUB_SHA:0:7}" '.version = $version' package.json > package.json.tmp
mv package.json.tmp package.json mv package.json.tmp package.json
- name: Build ${{ matrix.name }} - name: Build ${{ matrix.name }}
@@ -303,7 +303,7 @@ jobs:
IMAGE_TAG=$IMAGE_TAG-$TAG_EXTENSION IMAGE_TAG=$IMAGE_TAG-$TAG_EXTENSION
fi fi
echo "::set-output name=value::$IMAGE_TAG" echo "value=$IMAGE_TAG" >> $GITHUB_OUTPUT
- name: Tag image - name: Tag image
env: env:

View File

@@ -60,22 +60,22 @@ jobs:
run: | run: |
BUILD_NUMBER=$(expr 3000 + $GITHUB_RUN_NUMBER) BUILD_NUMBER=$(expr 3000 + $GITHUB_RUN_NUMBER)
echo "Setting build number to $BUILD_NUMBER" echo "Setting build number to $BUILD_NUMBER"
echo "::set-output name=build_number::$BUILD_NUMBER" echo "build_number=$BUILD_NUMBER" >> $GITHUB_OUTPUT
- name: Get Version Channel - name: Get Version Channel
id: release-channel id: release-channel
run: | run: |
case "${{ steps.version.outputs.version }}" in case "${{ steps.version.outputs.version }}" in
*"alpha"*) *"alpha"*)
echo "::set-output name=channel::alpha" echo "channel=alpha" >> $GITHUB_OUTPUT
echo "[!] We do not yet support 'alpha'" echo "[!] We do not yet support 'alpha'"
exit 1 exit 1
;; ;;
*"beta"*) *"beta"*)
echo "::set-output name=channel::beta" echo "channel=beta" >> $GITHUB_OUTPUT
;; ;;
*) *)
echo "::set-output name=channel::latest" echo "channel=latest" >> $GITHUB_OUTPUT
;; ;;
esac esac
@@ -102,7 +102,7 @@ jobs:
git push -u origin $branch_name git push -u origin $branch_name
echo "::set-output name=branch-name::$branch_name" echo "branch-name=$branch_name" >> $GITHUB_OUTPUT
linux: linux:
name: Linux Build name: Linux Build

View File

@@ -29,6 +29,16 @@ on:
required: true required: true
default: true default: true
type: boolean type: boolean
electron_publish:
description: 'Publish electron to S3 bucket'
required: true
default: true
type: boolean
github_release:
description: 'Publish github release'
required: true
default: true
type: boolean
defaults: defaults:
run: run:
@@ -70,15 +80,15 @@ jobs:
run: | run: |
case "${{ steps.version.outputs.version }}" in case "${{ steps.version.outputs.version }}" in
*"alpha"*) *"alpha"*)
echo "::set-output name=channel::alpha" echo "channel=alpha" >> $GITHUB_OUTPUT
echo "[!] We do not yet support 'alpha'" echo "[!] We do not yet support 'alpha'"
exit 1 exit 1
;; ;;
*"beta"*) *"beta"*)
echo "::set-output name=channel::beta" echo "channel=beta" >> $GITHUB_OUTPUT
;; ;;
*) *)
echo "::set-output name=channel::latest" echo "channel=latest" >> $GITHUB_OUTPUT
;; ;;
esac esac
@@ -136,6 +146,7 @@ jobs:
run: mv Bitwarden-${{ env.PKG_VERSION }}-universal.pkg Bitwarden-${{ env.PKG_VERSION }}-universal.pkg.archive run: mv Bitwarden-${{ env.PKG_VERSION }}-universal.pkg Bitwarden-${{ env.PKG_VERSION }}-universal.pkg.archive
- name: Set staged rollout percentage - name: Set staged rollout percentage
if: ${{ github.event.inputs.electron_publish }}
env: env:
RELEASE_CHANNEL: ${{ steps.release-channel.outputs.channel }} RELEASE_CHANNEL: ${{ steps.release-channel.outputs.channel }}
ROLLOUT_PCT: ${{ github.event.inputs.rollout_percentage }} ROLLOUT_PCT: ${{ github.event.inputs.rollout_percentage }}
@@ -145,7 +156,7 @@ jobs:
echo "stagingPercentage: ${ROLLOUT_PCT}" >> apps/desktop/artifacts/${RELEASE_CHANNEL}-mac.yml echo "stagingPercentage: ${ROLLOUT_PCT}" >> apps/desktop/artifacts/${RELEASE_CHANNEL}-mac.yml
- name: Publish artifacts to S3 - name: Publish artifacts to S3
if: ${{ github.event.inputs.release_type != 'Dry Run' }} if: ${{ github.event.inputs.release_type != 'Dry Run' && github.event.inputs.electron_publish }}
env: env:
AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.aws-electron-access-id }} AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.aws-electron-access-id }}
AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.aws-electron-access-key }} AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.aws-electron-access-key }}
@@ -159,7 +170,7 @@ jobs:
--quiet --quiet
- name: Publish artifacts to R2 - name: Publish artifacts to R2
if: ${{ github.event.inputs.release_type != 'Dry Run' }} if: ${{ github.event.inputs.release_type != 'Dry Run' && github.event.inputs.electron_publish }}
env: env:
AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.r2-electron-access-id }} AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.r2-electron-access-id }}
AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.r2-electron-access-key }} AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.r2-electron-access-key }}
@@ -175,7 +186,7 @@ jobs:
- name: Create Release - name: Create Release
uses: ncipollo/release-action@95215a3cb6e6a1908b3c44e00b4fdb15548b1e09 uses: ncipollo/release-action@95215a3cb6e6a1908b3c44e00b4fdb15548b1e09
if: ${{ steps.release-channel.outputs.channel == 'latest' && github.event.inputs.release_type != 'Dry Run' }} if: ${{ steps.release-channel.outputs.channel == 'latest' && github.event.inputs.release_type != 'Dry Run' && github.event.inputs.github_release }}
env: env:
PKG_VERSION: ${{ steps.version.outputs.version }} PKG_VERSION: ${{ steps.version.outputs.version }}
RELEASE_CHANNEL: ${{ steps.release-channel.outputs.channel }} RELEASE_CHANNEL: ${{ steps.release-channel.outputs.channel }}

View File

@@ -54,20 +54,6 @@ jobs:
r2-electron-bucket-name, r2-electron-bucket-name,
cf-prod-account" cf-prod-account"
- name: Download channel update info files from S3
env:
AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.aws-electron-access-id }}
AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.aws-electron-access-key }}
AWS_DEFAULT_REGION: 'us-west-2'
AWS_S3_BUCKET_NAME: ${{ steps.retrieve-secrets.outputs.aws-electron-bucket-name }}
run: |
aws s3 cp $AWS_S3_BUCKET_NAME/desktop/latest.yml . \
--quiet
aws s3 cp $AWS_S3_BUCKET_NAME/desktop/latest-linux.yml . \
--quiet
aws s3 cp $AWS_S3_BUCKET_NAME/desktop/latest-mac.yml . \
--quiet
- name: Download channel update info files from R2 - name: Download channel update info files from R2
env: env:
AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.r2-electron-access-id }} AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.r2-electron-access-id }}
@@ -99,7 +85,7 @@ jobs:
echo echo
echo "If you want to pull a staged release because it hasnt gone well, you must increment the version \ echo "If you want to pull a staged release because it hasnt gone well, you must increment the version \
number higher than your broken release. Because some of your users will be on the broken 1.0.1, \ number higher than your broken release. Because some of your users will be on the broken 1.0.1, \
releasing a new 1.0.1 would result in them staying on a broken version. releasing a new 1.0.1 would result in them staying on a broken version."
exit 1 exit 1
fi fi
@@ -118,10 +104,14 @@ jobs:
AWS_DEFAULT_REGION: 'us-west-2' AWS_DEFAULT_REGION: 'us-west-2'
AWS_S3_BUCKET_NAME: ${{ steps.retrieve-secrets.outputs.aws-electron-bucket-name }} AWS_S3_BUCKET_NAME: ${{ steps.retrieve-secrets.outputs.aws-electron-bucket-name }}
run: | run: |
aws s3 cp ./ $AWS_S3_BUCKET_NAME/desktop/ \ aws s3 cp latest.yml $AWS_S3_BUCKET_NAME/desktop/ \
--include "latest*.yml" \ --acl "public-read"
--acl "public-read" \
--quiet aws s3 cp latest-linux.yml $AWS_S3_BUCKET_NAME/desktop/ \
--acl "public-read"
aws s3 cp latest-mac.yml $AWS_S3_BUCKET_NAME/desktop/ \
--acl "public-read"
- name: Publish channel update info files to R2 - name: Publish channel update info files to R2
env: env:
@@ -131,7 +121,11 @@ jobs:
AWS_S3_BUCKET_NAME: ${{ steps.retrieve-secrets.outputs.r2-electron-bucket-name }} AWS_S3_BUCKET_NAME: ${{ steps.retrieve-secrets.outputs.r2-electron-bucket-name }}
CF_ACCOUNT: ${{ steps.retrieve-secrets.outputs.cf-prod-account }} CF_ACCOUNT: ${{ steps.retrieve-secrets.outputs.cf-prod-account }}
run: | run: |
aws s3 cp ./ $AWS_S3_BUCKET_NAME/desktop/ \ aws s3 cp latest.yml $AWS_S3_BUCKET_NAME/desktop/ \
--include "latest*.yml" \ --endpoint-url https://${CF_ACCOUNT}.r2.cloudflarestorage.com
--quiet \
aws s3 cp latest-linux.yml $AWS_S3_BUCKET_NAME/desktop/ \
--endpoint-url https://${CF_ACCOUNT}.r2.cloudflarestorage.com
aws s3 cp latest-mac.yml $AWS_S3_BUCKET_NAME/desktop/ \
--endpoint-url https://${CF_ACCOUNT}.r2.cloudflarestorage.com --endpoint-url https://${CF_ACCOUNT}.r2.cloudflarestorage.com

View File

@@ -36,7 +36,7 @@ jobs:
NEW_VER=$CURR_MAJOR.$NEW_PATCH NEW_VER=$CURR_MAJOR.$NEW_PATCH
echo "New Version: $NEW_VER" echo "New Version: $NEW_VER"
echo "::set-output name=new-version::$NEW_VER" echo "new-version=$NEW_VER" >> $GITHUB_OUTPUT
trigger_version_bump: trigger_version_bump:
name: "Trigger desktop version bump workflow" name: "Trigger desktop version bump workflow"

View File

@@ -56,7 +56,7 @@ jobs:
VERSION: ${{ github.event.inputs.version_number }} VERSION: ${{ github.event.inputs.version_number }}
run: | run: |
CLIENT=$(python -c "print('$CLIENT_NAME'.lower())") CLIENT=$(python -c "print('$CLIENT_NAME'.lower())")
echo "::set-output name=client::$CLIENT" echo "client=$CLIENT" >> $GITHUB_OUTPUT
git switch -c ${CLIENT}_version_bump_${VERSION} git switch -c ${CLIENT}_version_bump_${VERSION}
@@ -131,9 +131,9 @@ jobs:
id: version-changed id: version-changed
run: | run: |
if [ -n "$(git status --porcelain)" ]; then if [ -n "$(git status --porcelain)" ]; then
echo "::set-output name=changes_to_commit::TRUE" echo "changes_to_commit=TRUE" >> $GITHUB_OUTPUT
else else
echo "::set-output name=changes_to_commit::FALSE" echo "changes_to_commit=FALSE" >> $GITHUB_OUTPUT
echo "No changes to commit!"; echo "No changes to commit!";
fi fi
@@ -142,8 +142,7 @@ jobs:
env: env:
CLIENT: ${{ steps.branch.outputs.client }} CLIENT: ${{ steps.branch.outputs.client }}
VERSION: ${{ github.event.inputs.version_number }} VERSION: ${{ github.event.inputs.version_number }}
run: | run: git commit -m "Bumped ${CLIENT} version to ${VERSION}" -a
git commit -m "Bumped ${CLIENT} version to ${VERSION}" -a
- name: Push changes - name: Push changes
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }} if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}

View File

@@ -6,6 +6,8 @@ module.exports = {
"../libs/components/src/**/*.stories.@(js|jsx|ts|tsx)", "../libs/components/src/**/*.stories.@(js|jsx|ts|tsx)",
"../apps/web/src/**/*.stories.mdx", "../apps/web/src/**/*.stories.mdx",
"../apps/web/src/**/*.stories.@(js|jsx|ts|tsx)", "../apps/web/src/**/*.stories.@(js|jsx|ts|tsx)",
"../bitwarden_license/bit-web/src/**/*.stories.mdx",
"../bitwarden_license/bit-web/src/**/*.stories.@(js|jsx|ts|tsx)",
], ],
addons: [ addons: [
"@storybook/addon-links", "@storybook/addon-links",

View File

@@ -25,7 +25,7 @@
This repository houses all Bitwarden client applications except the [Mobile application](https://github.com/bitwarden/mobile). This repository houses all Bitwarden client applications except the [Mobile application](https://github.com/bitwarden/mobile).
Please refer to the [Clients section](https://contributing.bitwarden.com/clients/) of the [Contributing Documentation](https://contributing.bitwarden.com/) for build instructions, recommended tooling, code style tips, and lots of other great information to get you started. Please refer to the [Clients section](https://contributing.bitwarden.com/getting-started/clients/) of the [Contributing Documentation](https://contributing.bitwarden.com/) for build instructions, recommended tooling, code style tips, and lots of other great information to get you started.
## Related projects: ## Related projects:

View File

@@ -19,4 +19,4 @@ The Bitwarden browser extension is written using the Web Extension API and Angul
## Documentation ## Documentation
Please refer to the [Browser section](https://contributing.bitwarden.com/clients/browser/) of the [Contributing Documentation](https://contributing.bitwarden.com/) for build instructions, recommended tooling, code style tips, and lots of other great information to get you started. Please refer to the [Browser section](https://contributing.bitwarden.com/getting-started/clients/browser/) of the [Contributing Documentation](https://contributing.bitwarden.com/) for build instructions, recommended tooling, code style tips, and lots of other great information to get you started.

View File

@@ -482,7 +482,7 @@
"message": "الاسم مطلوب." "message": "الاسم مطلوب."
}, },
"addedFolder": { "addedFolder": {
"message": "Folder added" "message": "أُضيف المجلد"
}, },
"changeMasterPass": { "changeMasterPass": {
"message": "تغيير كلمة المرور الرئيسية" "message": "تغيير كلمة المرور الرئيسية"
@@ -494,7 +494,7 @@
"message": "تسجيل الدخول بخطوتين يجعل حسابك أكثر أمنا من خلال مطالبتك بالتحقق من تسجيل الدخول باستخدام جهاز آخر مثل مفتاح الأمان، تطبيق المصادقة، الرسائل القصيرة، المكالمة الهاتفية، أو البريد الإلكتروني. يمكن تمكين تسجيل الدخول بخطوتين على خزنة الويب bitwarden.com. هل تريد زيارة الموقع الآن؟" "message": "تسجيل الدخول بخطوتين يجعل حسابك أكثر أمنا من خلال مطالبتك بالتحقق من تسجيل الدخول باستخدام جهاز آخر مثل مفتاح الأمان، تطبيق المصادقة، الرسائل القصيرة، المكالمة الهاتفية، أو البريد الإلكتروني. يمكن تمكين تسجيل الدخول بخطوتين على خزنة الويب bitwarden.com. هل تريد زيارة الموقع الآن؟"
}, },
"editedFolder": { "editedFolder": {
"message": "Folder saved" "message": "حُفظ المجلد"
}, },
"deleteFolderConfirmation": { "deleteFolderConfirmation": {
"message": "هل أنت متأكد من حذف هذا المجلّد؟" "message": "هل أنت متأكد من حذف هذا المجلّد؟"
@@ -521,7 +521,7 @@
"message": "عنوان الـ URI" "message": "عنوان الـ URI"
}, },
"uriPosition": { "uriPosition": {
"message": "URI $POSITION$", "message": "رابط $POSITION$",
"description": "A listing of URIs. Ex: URI 1, URI 2, URI 3, etc.", "description": "A listing of URIs. Ex: URI 1, URI 2, URI 3, etc.",
"placeholders": { "placeholders": {
"position": { "position": {
@@ -571,13 +571,13 @@
"description": "This is the folder for uncategorized items" "description": "This is the folder for uncategorized items"
}, },
"enableAddLoginNotification": { "enableAddLoginNotification": {
"message": "Ask to add login" "message": "اطلب إضافة تسجيل الدخول"
}, },
"addLoginNotificationDesc": { "addLoginNotificationDesc": {
"message": "Ask to add an item if one isn't found in your vault." "message": "اطلب إضافة عنصر إذا لم يُعثر عليه في خزنتك."
}, },
"showCardsCurrentTab": { "showCardsCurrentTab": {
"message": "Show cards on Tab page" "message": "أظهر البطاقات في صفحة التبويبات"
}, },
"showCardsCurrentTabDesc": { "showCardsCurrentTabDesc": {
"message": "List card items on the Tab page for easy auto-fill." "message": "List card items on the Tab page for easy auto-fill."

View File

@@ -153,7 +153,7 @@
"description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing."
}, },
"yourAccountsFingerprint": { "yourAccountsFingerprint": {
"message": "Fráze otisku prstu vašeho účtu", "message": "Fráze otisku vašeho účtu",
"description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing."
}, },
"twoStepLogin": { "twoStepLogin": {
@@ -606,7 +606,7 @@
"message": "Zeptat se na aktualizaci existujícího přihlášení" "message": "Zeptat se na aktualizaci existujícího přihlášení"
}, },
"changedPasswordNotificationDesc": { "changedPasswordNotificationDesc": {
"message": "Ask to update a login's password when a change is detected on a website." "message": "Dotázat se na aktualizaci hesla pro přihlášení, pokud je na webové stránce zjištěno použití jiného hesla."
}, },
"notificationChangeDesc": { "notificationChangeDesc": {
"message": "Chcete aktualizovat toto heslo v Bitwarden?" "message": "Chcete aktualizovat toto heslo v Bitwarden?"
@@ -618,7 +618,7 @@
"message": "Zobrazit v kontextovém menu" "message": "Zobrazit v kontextovém menu"
}, },
"contextMenuItemDesc": { "contextMenuItemDesc": {
"message": "Use a secondary click to access password generation and matching logins for the website. " "message": "Použijte pravé tlačítko pro přístup k vytvoření hesla a odpovídajícímu přihlášení pro tuto stránku. "
}, },
"defaultUriMatchDetection": { "defaultUriMatchDetection": {
"message": "Výchozí zjišťování shody URI", "message": "Výchozí zjišťování shody URI",
@@ -1043,10 +1043,10 @@
"message": "Zobrazit rozeznatelný obrázek vedle každého přihlášení." "message": "Zobrazit rozeznatelný obrázek vedle každého přihlášení."
}, },
"enableBadgeCounter": { "enableBadgeCounter": {
"message": "Show badge counter" "message": "Zobrazovat počet uložených přihlašovacích údajů na stránce"
}, },
"badgeCounterDesc": { "badgeCounterDesc": {
"message": "Zobrazit počet přihlašovacích údajů pro aktuální webovou stránku." "message": "Zobrazit počet přihlašovacích údajů pro aktuální webovou stránku na ikoně rozšíření prohlížeče."
}, },
"cardholderName": { "cardholderName": {
"message": "Jméno držitele karty" "message": "Jméno držitele karty"
@@ -1632,7 +1632,7 @@
"message": "Odstraněné heslo" "message": "Odstraněné heslo"
}, },
"deletedSend": { "deletedSend": {
"message": "Smazaný Send", "message": "Send odstraněn",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}, },
"sendLink": { "sendLink": {
@@ -1899,10 +1899,10 @@
"message": "Vypršel časový limit relace. Vraťte se prosím zpět a zkuste se znovu přihlásit." "message": "Vypršel časový limit relace. Vraťte se prosím zpět a zkuste se znovu přihlásit."
}, },
"exportingPersonalVaultTitle": { "exportingPersonalVaultTitle": {
"message": "Exporting individual vault" "message": "Export mého trezoru"
}, },
"exportingPersonalVaultDescription": { "exportingPersonalVaultDescription": {
"message": "Only the individual vault items associated with $EMAIL$ will be exported. Organization vault items will not be included.", "message": "Budou exportovány pouze položky trezoru spojené s účtem $EMAIL$. Nebudou zahrnuty položky trezoru v organizaci.",
"placeholders": { "placeholders": {
"email": { "email": {
"content": "$1", "content": "$1",
@@ -1970,16 +1970,16 @@
"message": "API klíč" "message": "API klíč"
}, },
"ssoKeyConnectorError": { "ssoKeyConnectorError": {
"message": "Key connector error: make sure key connector is available and working correctly." "message": "Chyba Key Connector: ujistěte se, že je Key Connector k dispozici a funguje správně."
}, },
"premiumSubcriptionRequired": { "premiumSubcriptionRequired": {
"message": "Vyžadováno prémiové předplatné" "message": "Vyžadováno prémiové předplatné"
}, },
"organizationIsDisabled": { "organizationIsDisabled": {
"message": "Organization suspended." "message": "Organizace je deaktivována."
}, },
"disabledOrganizationFilterError": { "disabledOrganizationFilterError": {
"message": "Items in suspended Organizations cannot be accessed. Contact your Organization owner for assistance." "message": "K položkám v deaktivované organizaci nemáte přístup. Požádejte o pomoc vlastníka organizace."
}, },
"cardBrandMir": { "cardBrandMir": {
"message": "Mir" "message": "Mir"
@@ -2000,19 +2000,19 @@
"message": "Klikněte zde" "message": "Klikněte zde"
}, },
"environmentEditedReset": { "environmentEditedReset": {
"message": "to reset to pre-configured settings" "message": "pro obnovení do přednastavených nastavení"
}, },
"serverVersion": { "serverVersion": {
"message": "Verze serveru" "message": "Verze serveru"
}, },
"selfHosted": { "selfHosted": {
"message": "Self-hosted" "message": "Vlastní hosting"
}, },
"thirdParty": { "thirdParty": {
"message": "Third-party" "message": "Tretí strana"
}, },
"thirdPartyServerMessage": { "thirdPartyServerMessage": {
"message": "Connected to third-party server implementation, $SERVERNAME$. Please verify bugs using the official server, or report them to the third-party server.", "message": "Připojeno k serveru třetí strany $SERVERNAME$. Ověřte chyby připojením na oficiální server nebo nahlaste problém správci serveru.",
"placeholders": { "placeholders": {
"servername": { "servername": {
"content": "$1", "content": "$1",

View File

@@ -59,7 +59,7 @@
"message": "My vault" "message": "My vault"
}, },
"allVaults": { "allVaults": {
"message": "All Vaults" "message": "All vaults"
}, },
"tools": { "tools": {
"message": "Tools" "message": "Tools"
@@ -92,13 +92,13 @@
"message": "Auto-fill" "message": "Auto-fill"
}, },
"generatePasswordCopied": { "generatePasswordCopied": {
"message": "Generate Password (and Copy)" "message": "Generate password (copied)"
}, },
"copyElementIdentifier": { "copyElementIdentifier": {
"message": "Copy Custom Field Name" "message": "Copy custom field name"
}, },
"noMatchingLogins": { "noMatchingLogins": {
"message": "No matching logins." "message": "No matching logins"
}, },
"unlockVaultMenu": { "unlockVaultMenu": {
"message": "Unlock your vault" "message": "Unlock your vault"

View File

@@ -1485,7 +1485,7 @@
"message": "Uusi pääsalasanasi ei täytä käytännön määrittämiä vaatimuksia." "message": "Uusi pääsalasanasi ei täytä käytännön määrittämiä vaatimuksia."
}, },
"acceptPolicies": { "acceptPolicies": {
"message": "Valitsemalla tämän ruudun hyväksyt seuraavat:" "message": "Valitsemalla tämän hyväksyt seuraavat:"
}, },
"acceptPoliciesRequired": { "acceptPoliciesRequired": {
"message": "Palveluehtoja ja tietosuojakäytäntöä ei ole vahvistettu." "message": "Palveluehtoja ja tietosuojakäytäntöä ei ole vahvistettu."

View File

@@ -577,16 +577,16 @@
"message": "La \"Notification de demande d'ajout d'identifiant\" apparait automatiquement pour vous demander d'enregistrer dans votre coffre les identifiants que vous utilisez pour la première fois." "message": "La \"Notification de demande d'ajout d'identifiant\" apparait automatiquement pour vous demander d'enregistrer dans votre coffre les identifiants que vous utilisez pour la première fois."
}, },
"showCardsCurrentTab": { "showCardsCurrentTab": {
"message": "Show cards on Tab page" "message": "Afficher les cartes sur la page de l'onglet"
}, },
"showCardsCurrentTabDesc": { "showCardsCurrentTabDesc": {
"message": "List card items on the Tab page for easy auto-fill." "message": "Lister les éléments de la carte sur la page de l'onglet pour faciliter le remplissage automatique."
}, },
"showIdentitiesCurrentTab": { "showIdentitiesCurrentTab": {
"message": "Show identities on Tab page" "message": "Afficher les identités sur la page Onglet courant"
}, },
"showIdentitiesCurrentTabDesc": { "showIdentitiesCurrentTabDesc": {
"message": "List identity items on the Tab page for easy auto-fill." "message": "Lister les éléments d'identité sur la page de l'onglet pour faciliter le remplissage automatique."
}, },
"clearClipboard": { "clearClipboard": {
"message": "Effacer le presse-papiers", "message": "Effacer le presse-papiers",
@@ -618,7 +618,7 @@
"message": "Afficher les options du menu contextuel" "message": "Afficher les options du menu contextuel"
}, },
"contextMenuItemDesc": { "contextMenuItemDesc": {
"message": "Use a secondary click to access password generation and matching logins for the website. " "message": "Utilisez un clic secondaire pour accéder à la génération de mots de passe et les identifiants correspondants pour le site Web. "
}, },
"defaultUriMatchDetection": { "defaultUriMatchDetection": {
"message": "Détection de correspondance URI par défaut", "message": "Détection de correspondance URI par défaut",
@@ -816,7 +816,7 @@
"message": "Si une clé d'authentification est rattachée à votre identifiant, alors le code de vérification TOTP est automatiquement copié dans le presse-papiers lorsque vous renseignez l'identifiant." "message": "Si une clé d'authentification est rattachée à votre identifiant, alors le code de vérification TOTP est automatiquement copié dans le presse-papiers lorsque vous renseignez l'identifiant."
}, },
"enableAutoBiometricsPrompt": { "enableAutoBiometricsPrompt": {
"message": "Ask for biometrics on launch" "message": "Demander la biométrie au lancement"
}, },
"premiumRequired": { "premiumRequired": {
"message": "Version Premium requise" "message": "Version Premium requise"
@@ -1043,10 +1043,10 @@
"message": "Afficher une image reconnaissable à côté de chaque identifiant." "message": "Afficher une image reconnaissable à côté de chaque identifiant."
}, },
"enableBadgeCounter": { "enableBadgeCounter": {
"message": "Show badge counter" "message": "Afficher le compteur de badge"
}, },
"badgeCounterDesc": { "badgeCounterDesc": {
"message": "Indicate how many logins you have for the current web page." "message": "Indique le nombre d'identifiants que vous avez pour la page web actuelle."
}, },
"cardholderName": { "cardholderName": {
"message": "Nom du titulaire de la carte" "message": "Nom du titulaire de la carte"
@@ -2000,19 +2000,19 @@
"message": "Cliquez ici" "message": "Cliquez ici"
}, },
"environmentEditedReset": { "environmentEditedReset": {
"message": "to reset to pre-configured settings" "message": "pour réinitialiser aux paramètres par défaut"
}, },
"serverVersion": { "serverVersion": {
"message": "Server version" "message": "Version du serveur"
}, },
"selfHosted": { "selfHosted": {
"message": "Auto-hébergé" "message": "Auto-hébergé"
}, },
"thirdParty": { "thirdParty": {
"message": "Third-party" "message": "Tierce partie"
}, },
"thirdPartyServerMessage": { "thirdPartyServerMessage": {
"message": "Connected to third-party server implementation, $SERVERNAME$. Please verify bugs using the official server, or report them to the third-party server.", "message": "Connecté à l'implémentation du serveur tiers, $SERVERNAME$. Veuillez contrôler les bugs en utilisant le serveur officiel, ou rapportez-les au serveur tiers.",
"placeholders": { "placeholders": {
"servername": { "servername": {
"content": "$1", "content": "$1",
@@ -2021,7 +2021,7 @@
} }
}, },
"lastSeenOn": { "lastSeenOn": {
"message": "last seen on: $DATE$", "message": "vu pour la dernière fois le : $DATE$",
"placeholders": { "placeholders": {
"date": { "date": {
"content": "$1", "content": "$1",
@@ -2030,10 +2030,10 @@
} }
}, },
"loginWithMasterPassword": { "loginWithMasterPassword": {
"message": "Log in with master password" "message": "Connectez-vous avec le mot de passe maître"
}, },
"loggingInAs": { "loggingInAs": {
"message": "Logging in as" "message": "Connexion en tant que"
}, },
"notYou": { "notYou": {
"message": "Ce n'est pas vous ?" "message": "Ce n'est pas vous ?"
@@ -2042,6 +2042,6 @@
"message": "Êtes-vous nouveau ici ?" "message": "Êtes-vous nouveau ici ?"
}, },
"rememberEmail": { "rememberEmail": {
"message": "Remember email" "message": "Se souvenir de l'e-mail"
} }
} }

View File

@@ -2042,6 +2042,6 @@
"message": "Jesteś tu nowy(a)?" "message": "Jesteś tu nowy(a)?"
}, },
"rememberEmail": { "rememberEmail": {
"message": "Zapamiętaj email" "message": "Zapamiętaj adres e-mail"
} }
} }

View File

@@ -642,7 +642,7 @@
"description": "Light color" "description": "Light color"
}, },
"solarizedDark": { "solarizedDark": {
"message": "Солнечная темная", "message": "Solarized dark",
"description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated."
}, },
"exportVault": { "exportVault": {
@@ -921,7 +921,7 @@
"message": "Коды подтверждения будут отправлены вам по электронной почте." "message": "Коды подтверждения будут отправлены вам по электронной почте."
}, },
"selfHostedEnvironment": { "selfHostedEnvironment": {
"message": "Окружение собственного хостинга" "message": "Окружение пользовательского хостинга"
}, },
"selfHostedEnvironmentFooter": { "selfHostedEnvironmentFooter": {
"message": "Укажите URL Bitwarden на вашем сервере." "message": "Укажите URL Bitwarden на вашем сервере."
@@ -1396,7 +1396,7 @@
"message": "Действие по тайм-ауту хранилища" "message": "Действие по тайм-ауту хранилища"
}, },
"lock": { "lock": {
"message": "Заблокировать", "message": "Блокировка",
"description": "Verb form: to make secure or inaccesible by" "description": "Verb form: to make secure or inaccesible by"
}, },
"trash": { "trash": {

View File

@@ -53,13 +53,13 @@
"message": "Zavihek" "message": "Zavihek"
}, },
"vault": { "vault": {
"message": "Sef" "message": "Trezor"
}, },
"myVault": { "myVault": {
"message": "Moj trezor" "message": "Moj trezor"
}, },
"allVaults": { "allVaults": {
"message": "All vaults" "message": "Vsi trezorji"
}, },
"tools": { "tools": {
"message": "Orodja" "message": "Orodja"
@@ -101,7 +101,7 @@
"message": "Nobenih ujemajočih prijav." "message": "Nobenih ujemajočih prijav."
}, },
"unlockVaultMenu": { "unlockVaultMenu": {
"message": "Unlock your vault" "message": "Odkleni svoj trezor"
}, },
"loginToVaultMenu": { "loginToVaultMenu": {
"message": "Log in to your vault" "message": "Log in to your vault"
@@ -131,10 +131,10 @@
"message": "Send a verification code to your email" "message": "Send a verification code to your email"
}, },
"sendCode": { "sendCode": {
"message": "Send code" "message": "Pošlji kodo"
}, },
"codeSent": { "codeSent": {
"message": "Code sent" "message": "Koda poslana"
}, },
"verificationCode": { "verificationCode": {
"message": "Verifikacijska koda" "message": "Verifikacijska koda"
@@ -242,10 +242,10 @@
"message": "Lowercase (a-z)" "message": "Lowercase (a-z)"
}, },
"numbers": { "numbers": {
"message": "Numbers (0-9)" "message": "Številke (0-9)"
}, },
"specialCharacters": { "specialCharacters": {
"message": "Special characters (!@#$%^&*)" "message": "Posebni znaki (!@#$%^&*)"
}, },
"numWords": { "numWords": {
"message": "Število besed" "message": "Število besed"
@@ -864,7 +864,7 @@
"message": "To start the WebAuthn 2FA verification. Click the button below to open a new tab and follow the instructions provided in the new tab." "message": "To start the WebAuthn 2FA verification. Click the button below to open a new tab and follow the instructions provided in the new tab."
}, },
"webAuthnNewTabOpen": { "webAuthnNewTabOpen": {
"message": "Open new tab" "message": "Odpri nov zavihek"
}, },
"webAuthnAuthenticate": { "webAuthnAuthenticate": {
"message": "Authenticate WebAuthn" "message": "Authenticate WebAuthn"
@@ -879,10 +879,10 @@
"message": "Please use a supported web browser (such as Chrome) and/or add additional providers that are better supported across web browsers (such as an authenticator app)." "message": "Please use a supported web browser (such as Chrome) and/or add additional providers that are better supported across web browsers (such as an authenticator app)."
}, },
"twoStepOptions": { "twoStepOptions": {
"message": "Two-step login options" "message": "Možnosti dvostopenjske prijave"
}, },
"recoveryCodeDesc": { "recoveryCodeDesc": {
"message": "Lost access to all of your two-factor providers? Use your recovery code to turn off all two-factor providers from your account." "message": "Ste izgubili dostop do vseh vaših ponudnikov dvostopenjske prijave? Uporabite svojo kodo za obnovitev in tako onemogočite dvostopenjsko prijavo v svoj račun."
}, },
"recoveryCodeTitle": { "recoveryCodeTitle": {
"message": "Koda za obnovitev" "message": "Koda za obnovitev"
@@ -927,7 +927,7 @@
"message": "Specify the base URL of your on-premises hosted Bitwarden installation." "message": "Specify the base URL of your on-premises hosted Bitwarden installation."
}, },
"customEnvironment": { "customEnvironment": {
"message": "Custom environment" "message": "Okolje po meri"
}, },
"customEnvironmentFooter": { "customEnvironmentFooter": {
"message": "For advanced users. You can specify the base URL of each service independently." "message": "For advanced users. You can specify the base URL of each service independently."
@@ -993,7 +993,7 @@
"message": "Generate and copy a new random password to the clipboard" "message": "Generate and copy a new random password to the clipboard"
}, },
"commandLockVaultDesc": { "commandLockVaultDesc": {
"message": "Lock the vault" "message": "Zakleni trezor"
}, },
"privateModeWarning": { "privateModeWarning": {
"message": "Private mode support is experimental and some features are limited." "message": "Private mode support is experimental and some features are limited."
@@ -1002,10 +1002,10 @@
"message": "Custom fields" "message": "Custom fields"
}, },
"copyValue": { "copyValue": {
"message": "Copy value" "message": "Kopiraj vrednost"
}, },
"value": { "value": {
"message": "Value" "message": "Vrednost"
}, },
"newCustomField": { "newCustomField": {
"message": "New custom field" "message": "New custom field"
@@ -1020,7 +1020,7 @@
"message": "Skrito" "message": "Skrito"
}, },
"cfTypeBoolean": { "cfTypeBoolean": {
"message": "Boolean" "message": "Logična vrednost"
}, },
"cfTypeLinked": { "cfTypeLinked": {
"message": "Linked", "message": "Linked",
@@ -1133,7 +1133,7 @@
"message": "Priimek" "message": "Priimek"
}, },
"fullName": { "fullName": {
"message": "Full name" "message": "Polno ime"
}, },
"identityName": { "identityName": {
"message": "Ime identitete" "message": "Ime identitete"
@@ -1190,22 +1190,22 @@
"message": "Prijave" "message": "Prijave"
}, },
"typeSecureNote": { "typeSecureNote": {
"message": "Secure note" "message": "Varni zapisek"
}, },
"typeCard": { "typeCard": {
"message": "Card" "message": "Kartica"
}, },
"typeIdentity": { "typeIdentity": {
"message": "Identity" "message": "Identiteta"
}, },
"passwordHistory": { "passwordHistory": {
"message": "Password history" "message": "Zgodovina gesel"
}, },
"back": { "back": {
"message": "Back" "message": "Nazaj"
}, },
"collections": { "collections": {
"message": "Collections" "message": "Zbirke"
}, },
"favorites": { "favorites": {
"message": "Priljubljeno" "message": "Priljubljeno"
@@ -1226,7 +1226,7 @@
"message": "Prijave" "message": "Prijave"
}, },
"secureNotes": { "secureNotes": {
"message": "Secure notes" "message": "Varni zapiski"
}, },
"clear": { "clear": {
"message": "Počisti", "message": "Počisti",
@@ -1285,15 +1285,15 @@
"description": "Toggle the display of the URIs of the currently open tabs in the browser." "description": "Toggle the display of the URIs of the currently open tabs in the browser."
}, },
"currentUri": { "currentUri": {
"message": "Current URI", "message": "Trenutni URI",
"description": "The URI of one of the current open tabs in the browser." "description": "The URI of one of the current open tabs in the browser."
}, },
"organization": { "organization": {
"message": "Organization", "message": "Organizacija",
"description": "An entity of multiple related people (ex. a team or business organization)." "description": "An entity of multiple related people (ex. a team or business organization)."
}, },
"types": { "types": {
"message": "Types" "message": "Tipi"
}, },
"allItems": { "allItems": {
"message": "All items" "message": "All items"
@@ -1302,13 +1302,13 @@
"message": "There are no passwords to list." "message": "There are no passwords to list."
}, },
"remove": { "remove": {
"message": "Remove" "message": "Odstrani"
}, },
"default": { "default": {
"message": "Default" "message": "Privzeto"
}, },
"dateUpdated": { "dateUpdated": {
"message": "Updated", "message": "Posodobljeno",
"description": "ex. Date this item was updated" "description": "ex. Date this item was updated"
}, },
"dateCreated": { "dateCreated": {
@@ -1329,25 +1329,25 @@
"message": "There are no collections to list." "message": "There are no collections to list."
}, },
"ownership": { "ownership": {
"message": "Ownership" "message": "Lastništvo"
}, },
"whoOwnsThisItem": { "whoOwnsThisItem": {
"message": "Who owns this item?" "message": "Who owns this item?"
}, },
"strong": { "strong": {
"message": "Strong", "message": "Močno",
"description": "ex. A strong password. Scale: Weak -> Good -> Strong" "description": "ex. A strong password. Scale: Weak -> Good -> Strong"
}, },
"good": { "good": {
"message": "Good", "message": "Dobro",
"description": "ex. A good password. Scale: Weak -> Good -> Strong" "description": "ex. A good password. Scale: Weak -> Good -> Strong"
}, },
"weak": { "weak": {
"message": "Weak", "message": "Šibko",
"description": "ex. A weak password. Scale: Weak -> Good -> Strong" "description": "ex. A weak password. Scale: Weak -> Good -> Strong"
}, },
"weakMasterPassword": { "weakMasterPassword": {
"message": "Weak master password" "message": "Šibko glavno geslo"
}, },
"weakMasterPasswordDesc": { "weakMasterPasswordDesc": {
"message": "The master password you have chosen is weak. You should use a strong master password (or a passphrase) to properly protect your Bitwarden account. Are you sure you want to use this master password?" "message": "The master password you have chosen is weak. You should use a strong master password (or a passphrase) to properly protect your Bitwarden account. Are you sure you want to use this master password?"
@@ -1357,19 +1357,19 @@
"description": "PIN code. Ex. The short code (often numeric) that you use to unlock a device." "description": "PIN code. Ex. The short code (often numeric) that you use to unlock a device."
}, },
"unlockWithPin": { "unlockWithPin": {
"message": "Unlock with PIN" "message": "Odkleni s PIN kodo"
}, },
"setYourPinCode": { "setYourPinCode": {
"message": "Set your PIN code for unlocking Bitwarden. Your PIN settings will be reset if you ever fully log out of the application." "message": "Set your PIN code for unlocking Bitwarden. Your PIN settings will be reset if you ever fully log out of the application."
}, },
"pinRequired": { "pinRequired": {
"message": "PIN code is required." "message": "Potrebna je PIN koda."
}, },
"invalidPin": { "invalidPin": {
"message": "Invalid PIN code." "message": "Nepravilna PIN koda."
}, },
"unlockWithBiometrics": { "unlockWithBiometrics": {
"message": "Unlock with biometrics" "message": "Prijava z biometriko"
}, },
"awaitDesktop": { "awaitDesktop": {
"message": "Awaiting confirmation from desktop" "message": "Awaiting confirmation from desktop"
@@ -1387,7 +1387,7 @@
"message": "Clone item" "message": "Clone item"
}, },
"clone": { "clone": {
"message": "Clone" "message": "Kloniraj"
}, },
"passwordGeneratorPolicyInEffect": { "passwordGeneratorPolicyInEffect": {
"message": "One or more organization policies are affecting your generator settings." "message": "One or more organization policies are affecting your generator settings."
@@ -1396,15 +1396,15 @@
"message": "Vault timeout action" "message": "Vault timeout action"
}, },
"lock": { "lock": {
"message": "Lock", "message": "Zakleni",
"description": "Verb form: to make secure or inaccesible by" "description": "Verb form: to make secure or inaccesible by"
}, },
"trash": { "trash": {
"message": "Trash", "message": "Koš",
"description": "Noun: a special folder to hold deleted items" "description": "Noun: a special folder to hold deleted items"
}, },
"searchTrash": { "searchTrash": {
"message": "Search trash" "message": "Preišči koš"
}, },
"permanentlyDeleteItem": { "permanentlyDeleteItem": {
"message": "Permanently delete item" "message": "Permanently delete item"
@@ -1440,7 +1440,7 @@
"message": "Item auto-filled " "message": "Item auto-filled "
}, },
"setMasterPassword": { "setMasterPassword": {
"message": "Set master password" "message": "Nastavi glavno geslo"
}, },
"masterPasswordPolicyInEffect": { "masterPasswordPolicyInEffect": {
"message": "One or more organization policies require your master password to meet the following requirements:" "message": "One or more organization policies require your master password to meet the following requirements:"
@@ -1494,13 +1494,13 @@
"message": "Terms of Service" "message": "Terms of Service"
}, },
"privacyPolicy": { "privacyPolicy": {
"message": "Privacy Policy" "message": "Pravilnik o zasebnosti"
}, },
"hintEqualsPassword": { "hintEqualsPassword": {
"message": "Your password hint cannot be the same as your password." "message": "Your password hint cannot be the same as your password."
}, },
"ok": { "ok": {
"message": "Ok" "message": "V redu"
}, },
"desktopSyncVerificationTitle": { "desktopSyncVerificationTitle": {
"message": "Desktop sync verification" "message": "Desktop sync verification"
@@ -1584,7 +1584,7 @@
} }
}, },
"send": { "send": {
"message": "Send", "message": "Pošlji",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}, },
"searchSends": { "searchSends": {
@@ -1596,10 +1596,10 @@
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}, },
"sendTypeText": { "sendTypeText": {
"message": "Text" "message": "Besedilo"
}, },
"sendTypeFile": { "sendTypeFile": {
"message": "File" "message": "Datoteka"
}, },
"allSends": { "allSends": {
"message": "All Sends", "message": "All Sends",
@@ -1610,7 +1610,7 @@
"description": "This text will be displayed after a Send has been accessed the maximum amount of times." "description": "This text will be displayed after a Send has been accessed the maximum amount of times."
}, },
"expired": { "expired": {
"message": "Expired" "message": "Poteklo"
}, },
"pendingDeletion": { "pendingDeletion": {
"message": "Pending deletion" "message": "Pending deletion"
@@ -1626,7 +1626,7 @@
"message": "Remove Password" "message": "Remove Password"
}, },
"delete": { "delete": {
"message": "Delete" "message": "Izbriši"
}, },
"removedPassword": { "removedPassword": {
"message": "Password removed" "message": "Password removed"
@@ -1636,11 +1636,11 @@
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}, },
"sendLink": { "sendLink": {
"message": "Send link", "message": "Pošlji povezavo",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}, },
"disabled": { "disabled": {
"message": "Disabled" "message": "Onemogočeno"
}, },
"removePasswordConfirmation": { "removePasswordConfirmation": {
"message": "Are you sure you want to remove the password?" "message": "Are you sure you want to remove the password?"
@@ -1683,10 +1683,10 @@
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}, },
"oneDay": { "oneDay": {
"message": "1 day" "message": "1 dan"
}, },
"days": { "days": {
"message": "$DAYS$ days", "message": "$DAYS$ dni",
"placeholders": { "placeholders": {
"days": { "days": {
"content": "$1", "content": "$1",
@@ -1835,10 +1835,10 @@
"message": "In order to complete logging in with SSO, please set a master password to access and protect your vault." "message": "In order to complete logging in with SSO, please set a master password to access and protect your vault."
}, },
"hours": { "hours": {
"message": "Hours" "message": "Ur"
}, },
"minutes": { "minutes": {
"message": "Minutes" "message": "Minut"
}, },
"vaultTimeoutPolicyInEffect": { "vaultTimeoutPolicyInEffect": {
"message": "Your organization policies are affecting your vault timeout. Maximum allowed Vault Timeout is $HOURS$ hour(s) and $MINUTES$ minute(s)", "message": "Your organization policies are affecting your vault timeout. Maximum allowed Vault Timeout is $HOURS$ hour(s) and $MINUTES$ minute(s)",
@@ -1911,7 +1911,7 @@
} }
}, },
"error": { "error": {
"message": "Error" "message": "Napaka"
}, },
"regenerateUsername": { "regenerateUsername": {
"message": "Regenerate username" "message": "Regenerate username"
@@ -1936,13 +1936,13 @@
"message": "Use your domain's configured catch-all inbox." "message": "Use your domain's configured catch-all inbox."
}, },
"random": { "random": {
"message": "Random" "message": "Naključno"
}, },
"randomWord": { "randomWord": {
"message": "Random word" "message": "Naključna beseda"
}, },
"websiteName": { "websiteName": {
"message": "Website name" "message": "Ime spletne strani"
}, },
"whatWouldYouLikeToGenerate": { "whatWouldYouLikeToGenerate": {
"message": "What would you like to generate?" "message": "What would you like to generate?"
@@ -1997,13 +1997,13 @@
"message": "Settings have been edited" "message": "Settings have been edited"
}, },
"environmentEditedClick": { "environmentEditedClick": {
"message": "Click here" "message": "Kliknite tukaj"
}, },
"environmentEditedReset": { "environmentEditedReset": {
"message": "to reset to pre-configured settings" "message": "to reset to pre-configured settings"
}, },
"serverVersion": { "serverVersion": {
"message": "Server version" "message": "Verzija strežnika"
}, },
"selfHosted": { "selfHosted": {
"message": "Self-hosted" "message": "Self-hosted"
@@ -2036,12 +2036,12 @@
"message": "Logging in as" "message": "Logging in as"
}, },
"notYou": { "notYou": {
"message": "Not you?" "message": "Niste vi?"
}, },
"newAroundHere": { "newAroundHere": {
"message": "New around here?" "message": "New around here?"
}, },
"rememberEmail": { "rememberEmail": {
"message": "Remember email" "message": "Zapomni si e-pošto"
} }
} }

View File

@@ -2012,7 +2012,7 @@
"message": "Трећа страна" "message": "Трећа страна"
}, },
"thirdPartyServerMessage": { "thirdPartyServerMessage": {
"message": "Connected to third-party server implementation, $SERVERNAME$. Please verify bugs using the official server, or report them to the third-party server.", "message": "Повезан са имплементацијом сервера треће стране, $SERVERNAME$. Проверите грешке користећи званични сервер или их пријавите серверу треће стране.",
"placeholders": { "placeholders": {
"servername": { "servername": {
"content": "$1", "content": "$1",

View File

@@ -53,13 +53,13 @@
"message": "แท็บ" "message": "แท็บ"
}, },
"vault": { "vault": {
"message": "Vault" "message": "ตู้นิรภัย"
}, },
"myVault": { "myVault": {
"message": "My Vault" "message": "My Vault"
}, },
"allVaults": { "allVaults": {
"message": "All vaults" "message": "ตู้นิรภัยทั้งหมด"
}, },
"tools": { "tools": {
"message": "เครื่องมือ" "message": "เครื่องมือ"
@@ -95,16 +95,16 @@
"message": "Generate Password (copied)" "message": "Generate Password (copied)"
}, },
"copyElementIdentifier": { "copyElementIdentifier": {
"message": "Copy custom field name" "message": "คัดลอกชื่อของช่องที่กำหนดเอง"
}, },
"noMatchingLogins": { "noMatchingLogins": {
"message": "ไม่พบข้อมูลล็อกอินที่ตรงกัน" "message": "ไม่พบข้อมูลล็อกอินที่ตรงกัน"
}, },
"unlockVaultMenu": { "unlockVaultMenu": {
"message": "Unlock your vault" "message": "ปลดล็อกกตู้นิรภัยของคุณ"
}, },
"loginToVaultMenu": { "loginToVaultMenu": {
"message": "Log in to your vault" "message": "ลงชื่อเข้าใช้ตู้นิรภัยของคุณ"
}, },
"autoFillInfo": { "autoFillInfo": {
"message": "ไม่พบข้อมูลล็อกอินเพื่อใช้กรอกข้อมูลอัตโนมัติ สำหรับแท็บปัจจุบันของเบราว์เซอร์" "message": "ไม่พบข้อมูลล็อกอินเพื่อใช้กรอกข้อมูลอัตโนมัติ สำหรับแท็บปัจจุบันของเบราว์เซอร์"
@@ -153,7 +153,7 @@
"description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing."
}, },
"yourAccountsFingerprint": { "yourAccountsFingerprint": {
"message": "Your account's fingerprint phrase", "message": "ข้อความลายนิ้วมือของบัญชีของคุณ",
"description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing."
}, },
"twoStepLogin": { "twoStepLogin": {
@@ -212,7 +212,7 @@
"description": "Short for 'Password Generator'." "description": "Short for 'Password Generator'."
}, },
"passGenInfo": { "passGenInfo": {
"message": "Automatically generate strong, unique passwords for your logins." "message": "สร้างรหัสผ่านที่รัดกุมและไม่ซ้ำใครโดยอัตโนมัติสำหรับการเข้าสู่ระบบของคุณ"
}, },
"bitWebVault": { "bitWebVault": {
"message": "bitwarden Web Vault" "message": "bitwarden Web Vault"
@@ -291,7 +291,7 @@
"message": "รหัสผ่าน" "message": "รหัสผ่าน"
}, },
"passphrase": { "passphrase": {
"message": "Passphrase" "message": "ข้อความรหัสผ่าน"
}, },
"favorite": { "favorite": {
"message": "รายการโปรด" "message": "รายการโปรด"
@@ -333,10 +333,10 @@
"message": "Rate the Extension" "message": "Rate the Extension"
}, },
"rateExtensionDesc": { "rateExtensionDesc": {
"message": "Please consider helping us out with a good review!" "message": "โปรดพิจารณา ช่วยเราด้วยการตรวจสอบที่ดี!"
}, },
"browserNotSupportClipboard": { "browserNotSupportClipboard": {
"message": "Your web browser does not support easy clipboard copying. Copy it manually instead." "message": "เว็บเบราว์เซอร์ของคุณไม่รองรับการคัดลอกคลิปบอร์ดอย่างง่าย คัดลอกด้วยตนเองแทน"
}, },
"verifyIdentity": { "verifyIdentity": {
"message": "ยืนยันตัวตน" "message": "ยืนยันตัวตน"
@@ -424,22 +424,22 @@
"message": "ที่อยู่อีเมลไม่ถูกต้อง" "message": "ที่อยู่อีเมลไม่ถูกต้อง"
}, },
"masterPasswordRequired": { "masterPasswordRequired": {
"message": "Master password is required." "message": "ต้องใช้รหัสผ่านหลัก"
}, },
"confirmMasterPasswordRequired": { "confirmMasterPasswordRequired": {
"message": "Master password retype is required." "message": "ต้องพิมพ์รหัสผ่านหลักอีกครั้ง"
}, },
"masterPasswordMinlength": { "masterPasswordMinlength": {
"message": "Master password must be at least 8 characters long." "message": "รหัสผ่านหลักต้องมีความยาวอย่างน้อย 8 ตัวอักษร"
}, },
"masterPassDoesntMatch": { "masterPassDoesntMatch": {
"message": "Master password confirmation does not match." "message": "การยืนยันรหัสผ่านหลักไม่ตรงกัน"
}, },
"newAccountCreated": { "newAccountCreated": {
"message": "Your new account has been created! You may now log in." "message": "บัญชีใหม่ของคุณถูกสร้างขึ้นแล้ว! ตอนนี้คุณสามารถเข้าสู่ระบบ"
}, },
"masterPassSent": { "masterPassSent": {
"message": "We've sent you an email with your master password hint." "message": "เราได้ส่งอีเมลพร้อมคำใบ้รหัสผ่านหลักของคุณออกไปแล้ว"
}, },
"verificationCodeRequired": { "verificationCodeRequired": {
"message": "ต้องระบุโค้ดยืนยัน" "message": "ต้องระบุโค้ดยืนยัน"
@@ -497,7 +497,7 @@
"message": "Edited Folder" "message": "Edited Folder"
}, },
"deleteFolderConfirmation": { "deleteFolderConfirmation": {
"message": "Are you sure you want to delete this folder?" "message": "คุณแน่ใจหรือไม่ว่าต้องการลบโฟลเดอร์นี้"
}, },
"deletedFolder": { "deletedFolder": {
"message": "ลบโฟลเดอร์แล้ว" "message": "ลบโฟลเดอร์แล้ว"
@@ -506,7 +506,7 @@
"message": "Getting Started Tutorial" "message": "Getting Started Tutorial"
}, },
"gettingStartedTutorialVideo": { "gettingStartedTutorialVideo": {
"message": "Watch our getting started tutorial to learn how to get the most out of the browser extension." "message": "ดูบทช่วยสอนการเริ่มต้นของเราเพื่อเรียนรู้วิธีใช้ประโยชน์สูงสุดจากส่วนขยายเบราว์เซอร์"
}, },
"syncingComplete": { "syncingComplete": {
"message": "การซิงก์เสร็จสมบูรณ์" "message": "การซิงก์เสร็จสมบูรณ์"
@@ -552,48 +552,48 @@
"message": "คุณต้องการเขียนทับรหัสผ่านปัจจุบันใช่หรือไม่?" "message": "คุณต้องการเขียนทับรหัสผ่านปัจจุบันใช่หรือไม่?"
}, },
"overwriteUsername": { "overwriteUsername": {
"message": "Overwrite username" "message": "เขียนทับชื่อผู้ใช้"
}, },
"overwriteUsernameConfirmation": { "overwriteUsernameConfirmation": {
"message": "Are you sure you want to overwrite the current username?" "message": "คุณแน่ใจหรือไม่ว่าต้องการเขียนทับชื่อผู้ใช้ปัจจุบัน"
}, },
"searchFolder": { "searchFolder": {
"message": "ค้นหาในโพลเดอร์" "message": "ค้นหาในโพลเดอร์"
}, },
"searchCollection": { "searchCollection": {
"message": "Search collection" "message": "คอลเลกชันการค้นหา"
}, },
"searchType": { "searchType": {
"message": "Search type" "message": "ประเภทการค้นหา"
}, },
"noneFolder": { "noneFolder": {
"message": "No Folder", "message": "No Folder",
"description": "This is the folder for uncategorized items" "description": "This is the folder for uncategorized items"
}, },
"enableAddLoginNotification": { "enableAddLoginNotification": {
"message": "Ask to add login" "message": "ถามเพื่อให้เพิ่มการเข้าสู่ระบบ"
}, },
"addLoginNotificationDesc": { "addLoginNotificationDesc": {
"message": "The \"Add Login Notification\" automatically prompts you to save new logins to your vault whenever you log into them for the first time." "message": "The \"Add Login Notification\" automatically prompts you to save new logins to your vault whenever you log into them for the first time."
}, },
"showCardsCurrentTab": { "showCardsCurrentTab": {
"message": "Show cards on Tab page" "message": "แสดงการ์ดบนหน้าแท็บ"
}, },
"showCardsCurrentTabDesc": { "showCardsCurrentTabDesc": {
"message": "List card items on the Tab page for easy auto-fill." "message": "บัตรรายการในหน้าแท็บเพื่อให้ป้อนอัตโนมัติได้ง่าย"
}, },
"showIdentitiesCurrentTab": { "showIdentitiesCurrentTab": {
"message": "Show identities on Tab page" "message": "แสดงตัวตนบนหน้าแท็บ"
}, },
"showIdentitiesCurrentTabDesc": { "showIdentitiesCurrentTabDesc": {
"message": "List identity items on the Tab page for easy auto-fill." "message": "แสดงรายการข้อมูลประจำตัวในหน้าแท็บเพื่อให้ป้อนอัตโนมัติได้ง่าย"
}, },
"clearClipboard": { "clearClipboard": {
"message": "ล้างคลิปบอร์ด", "message": "ล้างคลิปบอร์ด",
"description": "Clipboard is the operating system thing where you copy/paste data to on your device." "description": "Clipboard is the operating system thing where you copy/paste data to on your device."
}, },
"clearClipboardDesc": { "clearClipboardDesc": {
"message": "Automatically clear copied values from your clipboard.", "message": "ล้างค่าที่คัดลอกโดยอัตโนมัติจากคลิปบอร์ดของคุณ",
"description": "Clipboard is the operating system thing where you copy/paste data to on your device." "description": "Clipboard is the operating system thing where you copy/paste data to on your device."
}, },
"notificationAddDesc": { "notificationAddDesc": {
@@ -603,7 +603,7 @@
"message": "Yes, Save Now" "message": "Yes, Save Now"
}, },
"enableChangedPasswordNotification": { "enableChangedPasswordNotification": {
"message": "Ask to update existing login" "message": "ขอให้ปรับปรุงการเข้าสู่ระบบที่มีอยู่"
}, },
"changedPasswordNotificationDesc": { "changedPasswordNotificationDesc": {
"message": "Ask to update a login's password when a change is detected on a website." "message": "Ask to update a login's password when a change is detected on a website."
@@ -615,17 +615,17 @@
"message": "Yes, Update Now" "message": "Yes, Update Now"
}, },
"enableContextMenuItem": { "enableContextMenuItem": {
"message": "Show context menu options" "message": "แสดงตัวเลือกเมนูบริบท"
}, },
"contextMenuItemDesc": { "contextMenuItemDesc": {
"message": "Use a secondary click to access password generation and matching logins for the website. " "message": "ใช้การคลิกสำรองเพื่อเข้าถึงการสร้างรหัสผ่านและการเข้าสู่ระบบที่ตรงกันสำหรับเว็บไซต์ "
}, },
"defaultUriMatchDetection": { "defaultUriMatchDetection": {
"message": "Default URI match detection", "message": "การตรวจจับการจับคู่ URI เริ่มต้น",
"description": "Default URI match detection for auto-fill." "description": "Default URI match detection for auto-fill."
}, },
"defaultUriMatchDetectionDesc": { "defaultUriMatchDetectionDesc": {
"message": "Choose the default way that URI match detection is handled for logins when performing actions such as auto-fill." "message": "เลือกวิธีเริ่มต้นในการจัดการการตรวจหาการจับคู่ URI สำหรับการเข้าสู่ระบบเมื่อดำเนินการต่างๆ เช่น การป้อนอัตโนมัติ"
}, },
"theme": { "theme": {
"message": "ธีม" "message": "ธีม"
@@ -656,31 +656,31 @@
"description": "WARNING (should stay in capitalized letters if the language permits)" "description": "WARNING (should stay in capitalized letters if the language permits)"
}, },
"confirmVaultExport": { "confirmVaultExport": {
"message": "Confirm vault export" "message": "ยืนยันการส่งออกตู้นิรภัย"
}, },
"exportWarningDesc": { "exportWarningDesc": {
"message": "This export contains your vault data in an unencrypted format. You should not store or send the exported file over unsecure channels (such as email). Delete it immediately after you are done using it." "message": "This export contains your vault data in an unencrypted format. You should not store or send the exported file over unsecure channels (such as email). Delete it immediately after you are done using it."
}, },
"encExportKeyWarningDesc": { "encExportKeyWarningDesc": {
"message": "This export encrypts your data using your account's encryption key. If you ever rotate your account's encryption key you should export again since you will not be able to decrypt this export file." "message": "การส่งออกนี้เข้ารหัสข้อมูลของคุณโดยใช้คีย์เข้ารหัสของบัญชีของคุณ หากคุณเคยหมุนเวียนคีย์เข้ารหัสของบัญชี คุณควรส่งออกอีกครั้ง เนื่องจากคุณจะไม่สามารถถอดรหัสไฟล์ส่งออกนี้ได้"
}, },
"encExportAccountWarningDesc": { "encExportAccountWarningDesc": {
"message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." "message": "คีย์การเข้ารหัสบัญชีจะไม่ซ้ำกันสำหรับบัญชีผู้ใช้ Bitwarden แต่ละบัญชี ดังนั้นคุณจึงไม่สามารถนำเข้าการส่งออกที่เข้ารหัสไปยังบัญชีอื่นได้"
}, },
"exportMasterPassword": { "exportMasterPassword": {
"message": "Enter your master password to export your vault data." "message": "ป้อนรหัสผ่านหลักของคุณเพื่อส่งออกข้อมูลตู้นิรภัยของคุณ"
}, },
"shared": { "shared": {
"message": "แชร์แล้ว" "message": "แชร์แล้ว"
}, },
"learnOrg": { "learnOrg": {
"message": "Learn about organizations" "message": "เรียนรู้เกี่ยวกับองค์กร"
}, },
"learnOrgConfirmation": { "learnOrgConfirmation": {
"message": "Bitwarden allows you to share your vault items with others by using an organization. Would you like to visit the bitwarden.com website to learn more?" "message": "Bitwarden อนุญาตให้คุณแชร์รายการตู้นิรภัยของคุณกับผู้อื่นโดยใช้องค์กร คุณต้องการเยี่ยมชมเว็บไซต์ bitwarden.com เพื่อเรียนรู้เพิ่มเติมหรือไม่?"
}, },
"moveToOrganization": { "moveToOrganization": {
"message": "Move to organization" "message": "ย้ายไปยังแบบองค์กร"
}, },
"share": { "share": {
"message": "แชร์" "message": "แชร์"
@@ -699,10 +699,10 @@
} }
}, },
"moveToOrgDesc": { "moveToOrgDesc": {
"message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." "message": "เลือกองค์กรที่คุณต้องการย้ายรายการนี้ไป การย้ายไปยังองค์กรจะโอนความเป็นเจ้าของรายการไปยังองค์กรนั้น คุณจะไม่ได้เป็นเจ้าของโดยตรงของรายการนี้อีกต่อไปเมื่อมีการย้ายแล้ว"
}, },
"learnMore": { "learnMore": {
"message": "Learn more" "message": "เรียนรู้เพิ่มเติม"
}, },
"authenticatorKeyTotp": { "authenticatorKeyTotp": {
"message": "Authenticator Key (TOTP)" "message": "Authenticator Key (TOTP)"
@@ -747,7 +747,7 @@
"message": "Feature Unavailable" "message": "Feature Unavailable"
}, },
"updateKey": { "updateKey": {
"message": "You cannot use this feature until you update your encryption key." "message": "คุณไม่สามารถใช้คุณลักษณะนี้ได้จนกว่าคุณจะปรับปรุงคีย์การเข้ารหัสลับของคุณ"
}, },
"premiumMembership": { "premiumMembership": {
"message": "Premium Membership" "message": "Premium Membership"
@@ -756,28 +756,28 @@
"message": "Manage Membership" "message": "Manage Membership"
}, },
"premiumManageAlert": { "premiumManageAlert": {
"message": "You can manage your membership on the bitwarden.com web vault. Do you want to visit the website now?" "message": "คุณสามารถจัดการการเป็นสมาชิกของคุณได้ที่ bitwarden.com web vault คุณต้องการเข้าชมเว็บไซต์ตอนนี้หรือไม่?"
}, },
"premiumRefresh": { "premiumRefresh": {
"message": "Refresh Membership" "message": "Refresh Membership"
}, },
"premiumNotCurrentMember": { "premiumNotCurrentMember": {
"message": "You are not currently a Premium member." "message": "คุณยังไม่ได้เป็นสมาชิกพรีเมียม"
}, },
"premiumSignUpAndGet": { "premiumSignUpAndGet": {
"message": "Sign up for a Premium membership and get:" "message": "สมัครสมาชิกพรีเมี่ยมและรับ:"
}, },
"ppremiumSignUpStorage": { "ppremiumSignUpStorage": {
"message": "1 GB of encrypted file storage." "message": "1 GB of encrypted file storage."
}, },
"ppremiumSignUpTwoStep": { "ppremiumSignUpTwoStep": {
"message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." "message": "ตัวเลือกการเข้าสู่ระบบแบบสองขั้นตอนเพิ่มเติม เช่น YubiKey, FIDO U2F และ Duo"
}, },
"ppremiumSignUpReports": { "ppremiumSignUpReports": {
"message": "Password hygiene, account health, and data breach reports to keep your vault safe." "message": "สุขอนามัยของรหัสผ่าน ความสมบูรณ์ของบัญชี และรายงานการละเมิดข้อมูลเพื่อให้ตู้นิรภัยของคุณปลอดภัย"
}, },
"ppremiumSignUpTotp": { "ppremiumSignUpTotp": {
"message": "TOTP verification code (2FA) generator for logins in your vault." "message": "ตัวสร้างรหัสยืนยัน TOTP (2FA) สำหรับการเข้าสู่ระบบในตู้นิรภัยของคุณ"
}, },
"ppremiumSignUpSupport": { "ppremiumSignUpSupport": {
"message": "Priority customer support." "message": "Priority customer support."
@@ -1055,7 +1055,7 @@
"message": "หมายเลข" "message": "หมายเลข"
}, },
"brand": { "brand": {
"message": "Brand" "message": "แบรนด์"
}, },
"expirationMonth": { "expirationMonth": {
"message": "Expiration Month" "message": "Expiration Month"
@@ -1133,7 +1133,7 @@
"message": "Last Name" "message": "Last Name"
}, },
"fullName": { "fullName": {
"message": "Full name" "message": "ชื่อเต็ม"
}, },
"identityName": { "identityName": {
"message": "Identity Name" "message": "Identity Name"
@@ -1181,7 +1181,7 @@
"message": "ประเทศ" "message": "ประเทศ"
}, },
"type": { "type": {
"message": "Type" "message": "ชนิด"
}, },
"typeLogin": { "typeLogin": {
"message": "ล็อกอิน" "message": "ล็อกอิน"
@@ -1205,35 +1205,35 @@
"message": "ย้อนกลับ" "message": "ย้อนกลับ"
}, },
"collections": { "collections": {
"message": "Collections" "message": "คอลเลกชัน"
}, },
"favorites": { "favorites": {
"message": "รายการโปรด" "message": "รายการโปรด"
}, },
"popOutNewWindow": { "popOutNewWindow": {
"message": "Pop out to a new window" "message": "เปิดหน้าต่างใหม่"
}, },
"refresh": { "refresh": {
"message": "Refresh" "message": "รีเฟรช"
}, },
"cards": { "cards": {
"message": "Cards" "message": "บัตร"
}, },
"identities": { "identities": {
"message": "Identities" "message": "ข้อมูลระบุตัวตน"
}, },
"logins": { "logins": {
"message": "Logins" "message": "เข้าสู่ระบบ"
}, },
"secureNotes": { "secureNotes": {
"message": "Secure Notes" "message": "Secure Notes"
}, },
"clear": { "clear": {
"message": "Clear", "message": "ลบทิ้ง",
"description": "To clear something out. example: To clear browser history." "description": "To clear something out. example: To clear browser history."
}, },
"checkPassword": { "checkPassword": {
"message": "Check if password has been exposed." "message": "ตรวจสอบว่ารหัสผ่านถูกเปิดเผยหรือไม่"
}, },
"passwordExposed": { "passwordExposed": {
"message": "This password has been exposed in data breaches. You should change it.", "message": "This password has been exposed in data breaches. You should change it.",
@@ -1245,28 +1245,28 @@
} }
}, },
"passwordSafe": { "passwordSafe": {
"message": "This password was not found in any known data breaches. It should be safe to use." "message": "ไม่พบรหัสผ่านนี้ในการละเมิดข้อมูลที่มี ควรใช้อย่างปลอดภัย"
}, },
"baseDomain": { "baseDomain": {
"message": "Base domain", "message": "โดเมนพื้นฐาน",
"description": "Domain name. Ex. website.com" "description": "Domain name. Ex. website.com"
}, },
"domainName": { "domainName": {
"message": "Domain name", "message": "ชื่อโดเมน",
"description": "Domain name. Ex. website.com" "description": "Domain name. Ex. website.com"
}, },
"host": { "host": {
"message": "Host", "message": "โฮสต์",
"description": "A URL's host value. For example, the host of https://sub.domain.com:443 is 'sub.domain.com:443'." "description": "A URL's host value. For example, the host of https://sub.domain.com:443 is 'sub.domain.com:443'."
}, },
"exact": { "exact": {
"message": "Exact" "message": "ถูกต้อง"
}, },
"startsWith": { "startsWith": {
"message": "Starts with" "message": "เริ่มต้นด้วย"
}, },
"regEx": { "regEx": {
"message": "Regular expression", "message": "นิพจน์ทั่วไป",
"description": "A programming term, also known as 'RegEx'." "description": "A programming term, also known as 'RegEx'."
}, },
"matchDetection": { "matchDetection": {
@@ -1274,14 +1274,14 @@
"description": "URI match detection for auto-fill." "description": "URI match detection for auto-fill."
}, },
"defaultMatchDetection": { "defaultMatchDetection": {
"message": "Default match detection", "message": "การตรวจจับการจับคู่เริ่มต้น",
"description": "Default URI match detection for auto-fill." "description": "Default URI match detection for auto-fill."
}, },
"toggleOptions": { "toggleOptions": {
"message": "Toggle Options" "message": "Toggle Options"
}, },
"toggleCurrentUris": { "toggleCurrentUris": {
"message": "Toggle current URIs", "message": "สลับ URI ปัจจุบัน",
"description": "Toggle the display of the URIs of the currently open tabs in the browser." "description": "Toggle the display of the URIs of the currently open tabs in the browser."
}, },
"currentUri": { "currentUri": {
@@ -1293,26 +1293,26 @@
"description": "An entity of multiple related people (ex. a team or business organization)." "description": "An entity of multiple related people (ex. a team or business organization)."
}, },
"types": { "types": {
"message": "Types" "message": "ชนิด"
}, },
"allItems": { "allItems": {
"message": "รายการทั้งหมด" "message": "รายการทั้งหมด"
}, },
"noPasswordsInList": { "noPasswordsInList": {
"message": "There are no passwords to list." "message": "ไม่มีรหัสผ่านที่จะแสดง"
}, },
"remove": { "remove": {
"message": "ลบ" "message": "ลบ"
}, },
"default": { "default": {
"message": "Default" "message": "ค่าเริ่มต้น"
}, },
"dateUpdated": { "dateUpdated": {
"message": "อัปเดตแล้ว", "message": "อัปเดตแล้ว",
"description": "ex. Date this item was updated" "description": "ex. Date this item was updated"
}, },
"dateCreated": { "dateCreated": {
"message": "Created", "message": "สร้างเมื่อ",
"description": "ex. Date this item was created" "description": "ex. Date this item was created"
}, },
"datePasswordUpdated": { "datePasswordUpdated": {
@@ -1320,13 +1320,13 @@
"description": "ex. Date this password was updated" "description": "ex. Date this password was updated"
}, },
"neverLockWarning": { "neverLockWarning": {
"message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." "message": "คุณแน่ใจหรือไม่ว่าต้องการใช้ตัวเลือก \"ไม่เคย\" การตั้งค่าตัวเลือกการล็อกเป็น \"ไม่\" จะเก็บคีย์เข้ารหัสของห้องนิรภัยไว้ในอุปกรณ์ของคุณ หากคุณใช้ตัวเลือกนี้ คุณควรตรวจสอบให้แน่ใจว่าคุณปกป้องอุปกรณ์ของคุณอย่างเหมาะสม"
}, },
"noOrganizationsList": { "noOrganizationsList": {
"message": "You do not belong to any organizations. Organizations allow you to securely share items with other users." "message": "You do not belong to any organizations. Organizations allow you to securely share items with other users."
}, },
"noCollectionsInList": { "noCollectionsInList": {
"message": "There are no collections to list." "message": "ไม่มีคอลเลกชันที่จะแสดง"
}, },
"ownership": { "ownership": {
"message": "เจ้าของ" "message": "เจ้าของ"
@@ -1350,7 +1350,7 @@
"message": "Weak Master Password" "message": "Weak Master Password"
}, },
"weakMasterPasswordDesc": { "weakMasterPasswordDesc": {
"message": "The master password you have chosen is weak. You should use a strong master password (or a passphrase) to properly protect your Bitwarden account. Are you sure you want to use this master password?" "message": "รหัสผ่านหลักที่คุณเลือกนั้นไม่รัดกุม คุณควรใช้รหัสผ่านหลักที่รัดกุม (หรือวลีรหัสผ่าน) เพื่อปกป้องบัญชี Bitwarden ของคุณอย่างเหมาะสม คุณแน่ใจหรือไม่ว่าต้องการใช้รหัสผ่านหลักนี้"
}, },
"pin": { "pin": {
"message": "PIN", "message": "PIN",
@@ -1369,31 +1369,31 @@
"message": "PIN ไม่ถูกต้อง" "message": "PIN ไม่ถูกต้อง"
}, },
"unlockWithBiometrics": { "unlockWithBiometrics": {
"message": "Unlock with biometrics" "message": "ปลดล็อกด้วยไบโอเมตริก"
}, },
"awaitDesktop": { "awaitDesktop": {
"message": "Awaiting confirmation from desktop" "message": "Awaiting confirmation from desktop"
}, },
"awaitDesktopDesc": { "awaitDesktopDesc": {
"message": "Please confirm using biometrics in the Bitwarden desktop application to set up biometrics for browser." "message": "โปรดยืนยันการใช้ไบโอเมตริกในแอปพลิเคชันเดสก์ท็อป Bitwarden เพื่อตั้งค่าไบโอเมตริกสำหรับเบราว์เซอร์"
}, },
"lockWithMasterPassOnRestart": { "lockWithMasterPassOnRestart": {
"message": "Lock with master password on browser restart" "message": "ล็อคด้วยรหัสผ่านหลักเมื่อรีสตาร์ทเบราว์เซอร์"
}, },
"selectOneCollection": { "selectOneCollection": {
"message": "You must select at least one collection." "message": "คุณต้องเลือกอย่างน้อยหนึ่งคอลเลกชัน"
}, },
"cloneItem": { "cloneItem": {
"message": "Clone item" "message": "โคลนรายการ"
}, },
"clone": { "clone": {
"message": "Clone" "message": "โคลน"
}, },
"passwordGeneratorPolicyInEffect": { "passwordGeneratorPolicyInEffect": {
"message": "One or more organization policies are affecting your generator settings." "message": "นโยบายองค์กรอย่างน้อยหนึ่งนโยบายส่งผลต่อการตั้งค่าตัวสร้างของคุณ"
}, },
"vaultTimeoutAction": { "vaultTimeoutAction": {
"message": "Vault timeout action" "message": "การดำเนินการหลังหมดเวลาล็อคตู้เซฟ"
}, },
"lock": { "lock": {
"message": "ล็อก", "message": "ล็อก",
@@ -1407,46 +1407,46 @@
"message": "ค้นหาในถังขยะ" "message": "ค้นหาในถังขยะ"
}, },
"permanentlyDeleteItem": { "permanentlyDeleteItem": {
"message": "Permanently delete item" "message": "ลบรายการอย่างถาวร"
}, },
"permanentlyDeleteItemConfirmation": { "permanentlyDeleteItemConfirmation": {
"message": "Are you sure you want to permanently delete this item?" "message": "คุณแน่ใจหรือไม่ว่าต้องการลบรายการนี้อย่างถาวร?"
}, },
"permanentlyDeletedItem": { "permanentlyDeletedItem": {
"message": "Item permanently deleted" "message": "ลบรายการอย่างถาวรแล้ว"
}, },
"restoreItem": { "restoreItem": {
"message": "Restore item" "message": "กู้คืนรายการ"
}, },
"restoreItemConfirmation": { "restoreItemConfirmation": {
"message": "Are you sure you want to restore this item?" "message": "คุณแน่ใจหรือไม่ว่าต้องการกู้คืนรายการนี้"
}, },
"restoredItem": { "restoredItem": {
"message": "Item restored" "message": "คืนค่ารายการแล้ว"
}, },
"vaultTimeoutLogOutConfirmation": { "vaultTimeoutLogOutConfirmation": {
"message": "Logging out will remove all access to your vault and requires online authentication after the timeout period. Are you sure you want to use this setting?" "message": "การออกจากระบบจะลบการเข้าถึงตู้นิรภัยของคุณทั้งหมด และต้องมีการตรวจสอบสิทธิ์ออนไลน์หลังจากหมดเวลา คุณแน่ใจหรือไม่ว่าต้องการใช้การตั้งค่านี้"
}, },
"vaultTimeoutLogOutConfirmationTitle": { "vaultTimeoutLogOutConfirmationTitle": {
"message": "Timeout action confirmation" "message": "การยืนยันการดำเนินการหมดเวลา"
}, },
"autoFillAndSave": { "autoFillAndSave": {
"message": "Auto-fill and save" "message": "กรอกอัตโนมัติและบันทึก"
}, },
"autoFillSuccessAndSavedUri": { "autoFillSuccessAndSavedUri": {
"message": "Item auto-filled and URI saved" "message": "เติมรายการอัตโนมัติและบันทึก URI แล้ว"
}, },
"autoFillSuccess": { "autoFillSuccess": {
"message": "Item auto-filled " "message": "รายการเติมอัตโนมัติ "
}, },
"setMasterPassword": { "setMasterPassword": {
"message": "ตั้งรหัสผ่านหลัก" "message": "ตั้งรหัสผ่านหลัก"
}, },
"masterPasswordPolicyInEffect": { "masterPasswordPolicyInEffect": {
"message": "One or more organization policies require your master password to meet the following requirements:" "message": "นโยบายองค์กรอย่างน้อยหนึ่งนโยบายกำหนดให้รหัสผ่านหลักของคุณเป็นไปตามข้อกำหนดต่อไปนี้:"
}, },
"policyInEffectMinComplexity": { "policyInEffectMinComplexity": {
"message": "Minimum complexity score of $SCORE$", "message": "คะแนนความซับซ้อนขั้นต่ำ $SCORE$",
"placeholders": { "placeholders": {
"score": { "score": {
"content": "$1", "content": "$1",
@@ -1482,7 +1482,7 @@
} }
}, },
"masterPasswordPolicyRequirementsNotMet": { "masterPasswordPolicyRequirementsNotMet": {
"message": "Your new master password does not meet the policy requirements." "message": "รหัสผ่านหลักใหม่ของคุณไม่เป็นไปตามข้อกำหนดของนโยบาย"
}, },
"acceptPolicies": { "acceptPolicies": {
"message": "By checking this box you agree to the following:" "message": "By checking this box you agree to the following:"

View File

@@ -2039,7 +2039,7 @@
"message": "不是你?" "message": "不是你?"
}, },
"newAroundHere": { "newAroundHere": {
"message": "新建在这里" "message": "初来乍到吗"
}, },
"rememberEmail": { "rememberEmail": {
"message": "记住电子邮件地址" "message": "记住电子邮件地址"

View File

@@ -2039,7 +2039,7 @@
"message": "不是您嗎?" "message": "不是您嗎?"
}, },
"newAroundHere": { "newAroundHere": {
"message": "New around here?" "message": "第一次使用?"
}, },
"rememberEmail": { "rememberEmail": {
"message": "記住電子郵件地址" "message": "記住電子郵件地址"

View File

@@ -0,0 +1,66 @@
import { BrowserApi } from "../browser/browserApi";
import { clearClipboardAlarmName } from "../clipboard";
export const alarmKeys = [clearClipboardAlarmName] as const;
export type AlarmKeys = typeof alarmKeys[number];
type AlarmState = { [T in AlarmKeys]: number | undefined };
const alarmState: AlarmState = {
clearClipboard: null,
//TODO once implemented vaultTimeout: null;
//TODO once implemented checkNotifications: null;
//TODO once implemented (if necessary) processReload: null;
};
/**
* Retrieves the set alarm time (planned execution) for a give an commandName {@link AlarmState}
* @param commandName A command that has been previously registered with {@link AlarmState}
* @returns {Promise<number>} null or Unix epoch timestamp when the alarm action is supposed to execute
* @example
* // getAlarmTime(clearClipboard)
*/
export async function getAlarmTime(commandName: AlarmKeys): Promise<number> {
let alarmTime: number;
if (BrowserApi.manifestVersion == 3) {
const fromSessionStore = await chrome.storage.session.get(commandName);
alarmTime = fromSessionStore[commandName];
} else {
alarmTime = alarmState[commandName];
}
return alarmTime;
}
/**
* Registers an action that should execute after the given time has passed
* @param commandName A command that has been previously registered with {@link AlarmState}
* @param delay_ms The number of ms from now in which the command should execute from
* @example
* // setAlarmTime(clearClipboard, 5000) register the clearClipboard action which will execute when at least 5 seconds from now have passed
*/
export async function setAlarmTime(commandName: AlarmKeys, delay_ms: number): Promise<void> {
if (!delay_ms || delay_ms === 0) {
await this.clearAlarmTime(commandName);
return;
}
const time = Date.now() + delay_ms;
await setAlarmTimeInternal(commandName, time);
}
/**
* Clears the time currently set for a given command
* @param commandName A command that has been previously registered with {@link AlarmState}
*/
export async function clearAlarmTime(commandName: AlarmKeys): Promise<void> {
await setAlarmTimeInternal(commandName, null);
}
async function setAlarmTimeInternal(commandName: AlarmKeys, time: number): Promise<void> {
if (BrowserApi.manifestVersion == 3) {
await chrome.storage.session.set({ [commandName]: time });
} else {
alarmState[commandName] = time;
}
}

View File

@@ -0,0 +1,26 @@
import { ClearClipboard, clearClipboardAlarmName } from "../clipboard";
import { alarmKeys, clearAlarmTime, getAlarmTime } from "./alarm-state";
export const onAlarmListener = async (alarm: chrome.alarms.Alarm) => {
alarmKeys.forEach(async (key) => {
const executionTime = await getAlarmTime(key);
if (!executionTime) {
return;
}
const currentDate = Date.now();
if (executionTime > currentDate) {
return;
}
await clearAlarmTime(key);
switch (key) {
case clearClipboardAlarmName:
ClearClipboard.run();
break;
default:
}
});
};

View File

@@ -0,0 +1,29 @@
const NUMBER_OF_ALARMS = 6;
export function registerAlarms() {
alarmsToBeCreated(NUMBER_OF_ALARMS);
}
/**
* Creates staggered alarms that periodically (1min) raise OnAlarm events. The staggering is calculated based on the numnber of alarms passed in.
* @param numberOfAlarms Number of named alarms, that shall be registered
* @example
* // alarmsToBeCreated(2) results in 2 alarms separated by 30 seconds
* @example
* // alarmsToBeCreated(4) results in 4 alarms separated by 15 seconds
* @example
* // alarmsToBeCreated(6) results in 6 alarms separated by 10 seconds
* @example
* // alarmsToBeCreated(60) results in 60 alarms separated by 1 second
*/
function alarmsToBeCreated(numberOfAlarms: number): void {
const oneMinuteInMs = 60 * 1000;
const offset = oneMinuteInMs / numberOfAlarms;
let calculatedWhen: number = Date.now() + offset;
for (let index = 0; index < numberOfAlarms; index++) {
chrome.alarms.create(`bw_alarm${index}`, { periodInMinutes: 1, when: calculatedWhen });
calculatedWhen += offset;
}
}

View File

@@ -1,6 +1,7 @@
import { onAlarmListener } from "./alarms/on-alarm-listener";
import { registerAlarms } from "./alarms/register-alarms";
import MainBackground from "./background/main.background"; import MainBackground from "./background/main.background";
import { BrowserApi } from "./browser/browserApi"; import { BrowserApi } from "./browser/browserApi";
import { ClearClipboard } from "./clipboard";
import { onCommandListener } from "./listeners/onCommandListener"; import { onCommandListener } from "./listeners/onCommandListener";
import { onInstallListener } from "./listeners/onInstallListener"; import { onInstallListener } from "./listeners/onInstallListener";
import { UpdateBadge } from "./listeners/update-badge"; import { UpdateBadge } from "./listeners/update-badge";
@@ -9,13 +10,12 @@ const manifestV3MessageListeners: ((
serviceCache: Record<string, unknown>, serviceCache: Record<string, unknown>,
message: { command: string } message: { command: string }
) => void | Promise<void>)[] = [UpdateBadge.messageListener]; ) => void | Promise<void>)[] = [UpdateBadge.messageListener];
type AlarmAction = (executionTime: Date, serviceCache: Record<string, unknown>) => void;
const AlarmActions: AlarmAction[] = [ClearClipboard.run];
if (BrowserApi.manifestVersion === 3) { if (BrowserApi.manifestVersion === 3) {
chrome.commands.onCommand.addListener(onCommandListener); chrome.commands.onCommand.addListener(onCommandListener);
chrome.runtime.onInstalled.addListener(onInstallListener); chrome.runtime.onInstalled.addListener(onInstallListener);
chrome.alarms.onAlarm.addListener(onAlarmListener);
registerAlarms();
chrome.tabs.onActivated.addListener(UpdateBadge.tabsOnActivatedListener); chrome.tabs.onActivated.addListener(UpdateBadge.tabsOnActivatedListener);
chrome.tabs.onReplaced.addListener(UpdateBadge.tabsOnReplacedListener); chrome.tabs.onReplaced.addListener(UpdateBadge.tabsOnReplacedListener);
chrome.tabs.onUpdated.addListener(UpdateBadge.tabsOnUpdatedListener); chrome.tabs.onUpdated.addListener(UpdateBadge.tabsOnUpdatedListener);
@@ -26,14 +26,6 @@ if (BrowserApi.manifestVersion === 3) {
listener(serviceCache, message); listener(serviceCache, message);
}); });
}); });
chrome.alarms.onAlarm.addListener((_alarm) => {
const executionTime = new Date();
const serviceCache = {};
for (const alarmAction of AlarmActions) {
alarmAction(executionTime, serviceCache);
}
});
} else { } else {
const bitwardenMain = ((window as any).bitwardenMain = new MainBackground()); const bitwardenMain = ((window as any).bitwardenMain = new MainBackground());
bitwardenMain.bootstrap().then(() => { bitwardenMain.bootstrap().then(() => {

View File

@@ -1,6 +1,6 @@
import { AuthService } from "@bitwarden/common/abstractions/auth.service"; import { AuthService } from "@bitwarden/common/abstractions/auth.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { EventService } from "@bitwarden/common/abstractions/event.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service"; import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { TotpService } from "@bitwarden/common/abstractions/totp.service"; import { TotpService } from "@bitwarden/common/abstractions/totp.service";
@@ -24,7 +24,7 @@ export default class ContextMenusBackground {
private passwordGenerationService: PasswordGenerationService, private passwordGenerationService: PasswordGenerationService,
private platformUtilsService: PlatformUtilsService, private platformUtilsService: PlatformUtilsService,
private authService: AuthService, private authService: AuthService,
private eventService: EventService, private eventCollectionService: EventCollectionService,
private totpService: TotpService private totpService: TotpService
) { ) {
this.contextMenus = chrome.contextMenus; this.contextMenus = chrome.contextMenus;
@@ -124,7 +124,7 @@ export default class ContextMenusBackground {
this.platformUtilsService.copyToClipboard(cipher.login.username, { window: window }); this.platformUtilsService.copyToClipboard(cipher.login.username, { window: window });
} else if (info.parentMenuItemId === "copy-password") { } else if (info.parentMenuItemId === "copy-password") {
this.platformUtilsService.copyToClipboard(cipher.login.password, { window: window }); this.platformUtilsService.copyToClipboard(cipher.login.password, { window: window });
this.eventService.collect(EventType.Cipher_ClientCopiedPassword, cipher.id); this.eventCollectionService.collect(EventType.Cipher_ClientCopiedPassword, cipher.id);
} else if (info.parentMenuItemId === "copy-totp") { } else if (info.parentMenuItemId === "copy-totp") {
const totpValue = await this.totpService.getCode(cipher.login.totp); const totpValue = await this.totpService.getCode(cipher.login.totp);
this.platformUtilsService.copyToClipboard(totpValue, { window: window }); this.platformUtilsService.copyToClipboard(totpValue, { window: window });

View File

@@ -1,7 +1,7 @@
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service"; import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service"; import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
import { StateService } from "../services/abstractions/state.service"; import { BrowserStateService } from "../services/abstractions/browser-state.service";
const IdleInterval = 60 * 5; // 5 minutes const IdleInterval = 60 * 5; // 5 minutes
@@ -12,7 +12,7 @@ export default class IdleBackground {
constructor( constructor(
private vaultTimeoutService: VaultTimeoutService, private vaultTimeoutService: VaultTimeoutService,
private stateService: StateService, private stateService: BrowserStateService,
private notificationsService: NotificationsService private notificationsService: NotificationsService
) { ) {
this.idle = chrome.idle || (browser != null ? browser.idle : null); this.idle = chrome.idle || (browser != null ? browser.idle : null);

View File

@@ -7,7 +7,8 @@ import { CollectionService as CollectionServiceAbstraction } from "@bitwarden/co
import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/abstractions/crypto.service"; import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/abstractions/crypto.service";
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/abstractions/cryptoFunction.service"; import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/abstractions/cryptoFunction.service";
import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service";
import { EventService as EventServiceAbstraction } from "@bitwarden/common/abstractions/event.service"; import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service";
import { ExportService as ExportServiceAbstraction } from "@bitwarden/common/abstractions/export.service"; import { ExportService as ExportServiceAbstraction } from "@bitwarden/common/abstractions/export.service";
import { FileUploadService as FileUploadServiceAbstraction } from "@bitwarden/common/abstractions/fileUpload.service"; import { FileUploadService as FileUploadServiceAbstraction } from "@bitwarden/common/abstractions/fileUpload.service";
import { FolderApiServiceAbstraction } from "@bitwarden/common/abstractions/folder/folder-api.service.abstraction"; import { FolderApiServiceAbstraction } from "@bitwarden/common/abstractions/folder/folder-api.service.abstraction";
@@ -17,7 +18,7 @@ import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarde
import { LogService as LogServiceAbstraction } from "@bitwarden/common/abstractions/log.service"; import { LogService as LogServiceAbstraction } from "@bitwarden/common/abstractions/log.service";
import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/abstractions/messaging.service"; import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/abstractions/messaging.service";
import { NotificationsService as NotificationsServiceAbstraction } from "@bitwarden/common/abstractions/notifications.service"; import { NotificationsService as NotificationsServiceAbstraction } from "@bitwarden/common/abstractions/notifications.service";
import { OrganizationService as OrganizationServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; import { InternalOrganizationService as InternalOrganizationServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PasswordGenerationService as PasswordGenerationServiceAbstraction } from "@bitwarden/common/abstractions/passwordGeneration.service"; import { PasswordGenerationService as PasswordGenerationServiceAbstraction } from "@bitwarden/common/abstractions/passwordGeneration.service";
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/abstractions/platformUtils.service"; import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/abstractions/policy/policy-api.service.abstraction"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/abstractions/policy/policy-api.service.abstraction";
@@ -54,21 +55,19 @@ import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service
import { ContainerService } from "@bitwarden/common/services/container.service"; import { ContainerService } from "@bitwarden/common/services/container.service";
import { EncryptServiceImplementation } from "@bitwarden/common/services/cryptography/encrypt.service.implementation"; import { EncryptServiceImplementation } from "@bitwarden/common/services/cryptography/encrypt.service.implementation";
import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/services/cryptography/multithread-encrypt.service.implementation"; import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/services/cryptography/multithread-encrypt.service.implementation";
import { EventService } from "@bitwarden/common/services/event.service"; import { EventCollectionService } from "@bitwarden/common/services/event/event-collection.service";
import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service";
import { ExportService } from "@bitwarden/common/services/export.service"; import { ExportService } from "@bitwarden/common/services/export.service";
import { FileUploadService } from "@bitwarden/common/services/fileUpload.service"; import { FileUploadService } from "@bitwarden/common/services/fileUpload.service";
import { FolderApiService } from "@bitwarden/common/services/folder/folder-api.service"; import { FolderApiService } from "@bitwarden/common/services/folder/folder-api.service";
import { KeyConnectorService } from "@bitwarden/common/services/keyConnector.service"; import { KeyConnectorService } from "@bitwarden/common/services/keyConnector.service";
import { MemoryStorageService } from "@bitwarden/common/services/memoryStorage.service"; import { MemoryStorageService } from "@bitwarden/common/services/memoryStorage.service";
import { NotificationsService } from "@bitwarden/common/services/notifications.service"; import { NotificationsService } from "@bitwarden/common/services/notifications.service";
import { OrganizationService } from "@bitwarden/common/services/organization/organization.service";
import { PasswordGenerationService } from "@bitwarden/common/services/passwordGeneration.service"; import { PasswordGenerationService } from "@bitwarden/common/services/passwordGeneration.service";
import { PolicyApiService } from "@bitwarden/common/services/policy/policy-api.service"; import { PolicyApiService } from "@bitwarden/common/services/policy/policy-api.service";
import { PolicyService } from "@bitwarden/common/services/policy/policy.service";
import { ProviderService } from "@bitwarden/common/services/provider.service"; import { ProviderService } from "@bitwarden/common/services/provider.service";
import { SearchService } from "@bitwarden/common/services/search.service"; import { SearchService } from "@bitwarden/common/services/search.service";
import { SendService } from "@bitwarden/common/services/send.service"; import { SendService } from "@bitwarden/common/services/send.service";
import { SettingsService } from "@bitwarden/common/services/settings.service";
import { StateMigrationService } from "@bitwarden/common/services/stateMigration.service"; import { StateMigrationService } from "@bitwarden/common/services/stateMigration.service";
import { SyncService } from "@bitwarden/common/services/sync/sync.service"; import { SyncService } from "@bitwarden/common/services/sync/sync.service";
import { SyncNotifierService } from "@bitwarden/common/services/sync/syncNotifier.service"; import { SyncNotifierService } from "@bitwarden/common/services/sync/syncNotifier.service";
@@ -89,19 +88,22 @@ import { UpdateBadge } from "../listeners/update-badge";
import { Account } from "../models/account"; import { Account } from "../models/account";
import { PopupUtilsService } from "../popup/services/popup-utils.service"; import { PopupUtilsService } from "../popup/services/popup-utils.service";
import { AutofillService as AutofillServiceAbstraction } from "../services/abstractions/autofill.service"; import { AutofillService as AutofillServiceAbstraction } from "../services/abstractions/autofill.service";
import { StateService as StateServiceAbstraction } from "../services/abstractions/state.service"; import { BrowserStateService as StateServiceAbstraction } from "../services/abstractions/browser-state.service";
import AutofillService from "../services/autofill.service"; import AutofillService from "../services/autofill.service";
import { BrowserEnvironmentService } from "../services/browser-environment.service"; import { BrowserEnvironmentService } from "../services/browser-environment.service";
import { BrowserFolderService } from "../services/browser-folder.service";
import { BrowserOrganizationService } from "../services/browser-organization.service";
import { BrowserPolicyService } from "../services/browser-policy.service";
import { BrowserSettingsService } from "../services/browser-settings.service";
import { BrowserStateService } from "../services/browser-state.service";
import { BrowserCryptoService } from "../services/browserCrypto.service"; import { BrowserCryptoService } from "../services/browserCrypto.service";
import BrowserLocalStorageService from "../services/browserLocalStorage.service"; import BrowserLocalStorageService from "../services/browserLocalStorage.service";
import BrowserMessagingService from "../services/browserMessaging.service"; import BrowserMessagingService from "../services/browserMessaging.service";
import BrowserMessagingPrivateModeBackgroundService from "../services/browserMessagingPrivateModeBackground.service"; import BrowserMessagingPrivateModeBackgroundService from "../services/browserMessagingPrivateModeBackground.service";
import BrowserPlatformUtilsService from "../services/browserPlatformUtils.service"; import BrowserPlatformUtilsService from "../services/browserPlatformUtils.service";
import { FolderService } from "../services/folders/folder.service";
import I18nService from "../services/i18n.service"; import I18nService from "../services/i18n.service";
import { KeyGenerationService } from "../services/keyGeneration.service"; import { KeyGenerationService } from "../services/keyGeneration.service";
import { LocalBackedSessionStorageService } from "../services/localBackedSessionStorage.service"; import { LocalBackedSessionStorageService } from "../services/localBackedSessionStorage.service";
import { StateService } from "../services/state.service";
import { VaultFilterService } from "../services/vaultFilter.service"; import { VaultFilterService } from "../services/vaultFilter.service";
import VaultTimeoutService from "../services/vaultTimeout/vaultTimeout.service"; import VaultTimeoutService from "../services/vaultTimeout/vaultTimeout.service";
@@ -148,12 +150,13 @@ export default class MainBackground {
stateService: StateServiceAbstraction; stateService: StateServiceAbstraction;
stateMigrationService: StateMigrationService; stateMigrationService: StateMigrationService;
systemService: SystemServiceAbstraction; systemService: SystemServiceAbstraction;
eventService: EventServiceAbstraction; eventCollectionService: EventCollectionServiceAbstraction;
eventUploadService: EventUploadServiceAbstraction;
policyService: InternalPolicyServiceAbstraction; policyService: InternalPolicyServiceAbstraction;
popupUtilsService: PopupUtilsService; popupUtilsService: PopupUtilsService;
sendService: SendServiceAbstraction; sendService: SendServiceAbstraction;
fileUploadService: FileUploadServiceAbstraction; fileUploadService: FileUploadServiceAbstraction;
organizationService: OrganizationServiceAbstraction; organizationService: InternalOrganizationServiceAbstraction;
providerService: ProviderServiceAbstraction; providerService: ProviderServiceAbstraction;
keyConnectorService: KeyConnectorServiceAbstraction; keyConnectorService: KeyConnectorServiceAbstraction;
userVerificationService: UserVerificationServiceAbstraction; userVerificationService: UserVerificationServiceAbstraction;
@@ -227,7 +230,7 @@ export default class MainBackground {
this.secureStorageService, this.secureStorageService,
new StateFactory(GlobalState, Account) new StateFactory(GlobalState, Account)
); );
this.stateService = new StateService( this.stateService = new BrowserStateService(
this.storageService, this.storageService,
this.secureStorageService, this.secureStorageService,
this.memoryStorageService, this.memoryStorageService,
@@ -282,7 +285,7 @@ export default class MainBackground {
this.appIdService, this.appIdService,
(expired: boolean) => this.logout(expired) (expired: boolean) => this.logout(expired)
); );
this.settingsService = new SettingsService(this.stateService); this.settingsService = new BrowserSettingsService(this.stateService);
this.fileUploadService = new FileUploadService(this.logService, this.apiService); this.fileUploadService = new FileUploadService(this.logService, this.apiService);
this.cipherService = new CipherService( this.cipherService = new CipherService(
this.cryptoService, this.cryptoService,
@@ -295,7 +298,7 @@ export default class MainBackground {
this.stateService, this.stateService,
this.encryptService this.encryptService
); );
this.folderService = new FolderService( this.folderService = new BrowserFolderService(
this.cryptoService, this.cryptoService,
this.i18nService, this.i18nService,
this.cipherService, this.cipherService,
@@ -317,8 +320,8 @@ export default class MainBackground {
this.stateService this.stateService
); );
this.syncNotifierService = new SyncNotifierService(); this.syncNotifierService = new SyncNotifierService();
this.organizationService = new OrganizationService(this.stateService, this.syncNotifierService); this.organizationService = new BrowserOrganizationService(this.stateService);
this.policyService = new PolicyService(this.stateService, this.organizationService); this.policyService = new BrowserPolicyService(this.stateService, this.organizationService);
this.policyApiService = new PolicyApiService( this.policyApiService = new PolicyApiService(
this.policyService, this.policyService,
this.apiService, this.apiService,
@@ -409,15 +412,19 @@ export default class MainBackground {
this.stateService, this.stateService,
this.providerService, this.providerService,
this.folderApiService, this.folderApiService,
this.syncNotifierService, this.organizationService,
logoutCallback logoutCallback
); );
this.eventService = new EventService( this.eventUploadService = new EventUploadService(
this.apiService, this.apiService,
this.stateService,
this.logService
);
this.eventCollectionService = new EventCollectionService(
this.cipherService, this.cipherService,
this.stateService, this.stateService,
this.logService, this.organizationService,
this.organizationService this.eventUploadService
); );
this.passwordGenerationService = new PasswordGenerationService( this.passwordGenerationService = new PasswordGenerationService(
this.cryptoService, this.cryptoService,
@@ -429,7 +436,7 @@ export default class MainBackground {
this.cipherService, this.cipherService,
this.stateService, this.stateService,
this.totpService, this.totpService,
this.eventService, this.eventCollectionService,
this.logService this.logService
); );
this.containerService = new ContainerService(this.cryptoService, this.encryptService); this.containerService = new ContainerService(this.cryptoService, this.encryptService);
@@ -532,7 +539,7 @@ export default class MainBackground {
this.passwordGenerationService, this.passwordGenerationService,
this.platformUtilsService, this.platformUtilsService,
this.authService, this.authService,
this.eventService, this.eventCollectionService,
this.totpService this.totpService
); );
this.idleBackground = new IdleBackground( this.idleBackground = new IdleBackground(
@@ -560,7 +567,7 @@ export default class MainBackground {
await (this.vaultTimeoutService as VaultTimeoutService).init(true); await (this.vaultTimeoutService as VaultTimeoutService).init(true);
await (this.i18nService as I18nService).init(); await (this.i18nService as I18nService).init();
await (this.eventService as EventService).init(true); await (this.eventUploadService as EventUploadService).init(true);
await this.runtimeBackground.init(); await this.runtimeBackground.init();
await this.notificationBackground.init(); await this.notificationBackground.init();
await this.commandsBackground.init(); await this.commandsBackground.init();
@@ -626,10 +633,9 @@ export default class MainBackground {
} }
async logout(expired: boolean, userId?: string) { async logout(expired: boolean, userId?: string) {
await this.eventService.uploadEvents(userId); await this.eventUploadService.uploadEvents(userId);
await Promise.all([ await Promise.all([
this.eventService.clearEvents(userId),
this.syncService.setLastSync(new Date(0), userId), this.syncService.setLastSync(new Date(0), userId),
this.cryptoService.clearKeys(userId), this.cryptoService.clearKeys(userId),
this.settingsService.clear(userId), this.settingsService.clear(userId),

View File

@@ -15,7 +15,7 @@ import { LoginView } from "@bitwarden/common/models/view/login.view";
import { BrowserApi } from "../browser/browserApi"; import { BrowserApi } from "../browser/browserApi";
import { AutofillService } from "../services/abstractions/autofill.service"; import { AutofillService } from "../services/abstractions/autofill.service";
import { StateService } from "../services/abstractions/state.service"; import { BrowserStateService } from "../services/abstractions/browser-state.service";
import AddChangePasswordQueueMessage from "./models/addChangePasswordQueueMessage"; import AddChangePasswordQueueMessage from "./models/addChangePasswordQueueMessage";
import AddLoginQueueMessage from "./models/addLoginQueueMessage"; import AddLoginQueueMessage from "./models/addLoginQueueMessage";
@@ -33,7 +33,7 @@ export default class NotificationBackground {
private authService: AuthService, private authService: AuthService,
private policyService: PolicyService, private policyService: PolicyService,
private folderService: FolderService, private folderService: FolderService,
private stateService: StateService private stateService: BrowserStateService
) {} ) {}
async init() { async init() {

View File

@@ -2,7 +2,10 @@ import { AutofillService as AbstractAutoFillService } from "../../services/abstr
import AutofillService from "../../services/autofill.service"; import AutofillService from "../../services/autofill.service";
import { cipherServiceFactory, CipherServiceInitOptions } from "./cipher-service.factory"; import { cipherServiceFactory, CipherServiceInitOptions } from "./cipher-service.factory";
import { EventServiceInitOptions, eventServiceFactory } from "./event-service.factory"; import {
EventCollectionServiceInitOptions,
eventCollectionServiceFactory,
} from "./event-collection-service.factory";
import { CachedServices, factory, FactoryOptions } from "./factory-options"; import { CachedServices, factory, FactoryOptions } from "./factory-options";
import { logServiceFactory, LogServiceInitOptions } from "./log-service.factory"; import { logServiceFactory, LogServiceInitOptions } from "./log-service.factory";
import { stateServiceFactory, StateServiceInitOptions } from "./state-service.factory"; import { stateServiceFactory, StateServiceInitOptions } from "./state-service.factory";
@@ -14,7 +17,7 @@ export type AutoFillServiceInitOptions = AutoFillServiceOptions &
CipherServiceInitOptions & CipherServiceInitOptions &
StateServiceInitOptions & StateServiceInitOptions &
TotpServiceInitOptions & TotpServiceInitOptions &
EventServiceInitOptions & EventCollectionServiceInitOptions &
LogServiceInitOptions; LogServiceInitOptions;
export function autofillServiceFactory( export function autofillServiceFactory(
@@ -30,7 +33,7 @@ export function autofillServiceFactory(
await cipherServiceFactory(cache, opts), await cipherServiceFactory(cache, opts),
await stateServiceFactory(cache, opts), await stateServiceFactory(cache, opts),
await totpServiceFactory(cache, opts), await totpServiceFactory(cache, opts),
await eventServiceFactory(cache, opts), await eventCollectionServiceFactory(cache, opts),
await logServiceFactory(cache, opts) await logServiceFactory(cache, opts)
) )
); );

View File

@@ -0,0 +1,40 @@
import { EventCollectionService as AbstractEventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { EventCollectionService } from "@bitwarden/common/services/event/event-collection.service";
import { cipherServiceFactory, CipherServiceInitOptions } from "./cipher-service.factory";
import {
eventUploadServiceFactory,
EventUploadServiceInitOptions,
} from "./event-upload-service.factory";
import { FactoryOptions, CachedServices, factory } from "./factory-options";
import {
organizationServiceFactory,
OrganizationServiceInitOptions,
} from "./organization-service.factory";
import { stateServiceFactory, StateServiceInitOptions } from "./state-service.factory";
type EventCollectionServiceOptions = FactoryOptions;
export type EventCollectionServiceInitOptions = EventCollectionServiceOptions &
CipherServiceInitOptions &
StateServiceInitOptions &
OrganizationServiceInitOptions &
EventUploadServiceInitOptions;
export function eventCollectionServiceFactory(
cache: { eventCollectionService?: AbstractEventCollectionService } & CachedServices,
opts: EventCollectionServiceInitOptions
): Promise<AbstractEventCollectionService> {
return factory(
cache,
"eventCollectionService",
opts,
async () =>
new EventCollectionService(
await cipherServiceFactory(cache, opts),
await stateServiceFactory(cache, opts),
await organizationServiceFactory(cache, opts),
await eventUploadServiceFactory(cache, opts)
)
);
}

View File

@@ -1,40 +0,0 @@
import { EventService as AbstractEventService } from "@bitwarden/common/abstractions/event.service";
import { EventService } from "@bitwarden/common/services/event.service";
import { apiServiceFactory, ApiServiceInitOptions } from "./api-service.factory";
import { cipherServiceFactory, CipherServiceInitOptions } from "./cipher-service.factory";
import { FactoryOptions, CachedServices, factory } from "./factory-options";
import { logServiceFactory, LogServiceInitOptions } from "./log-service.factory";
import {
organizationServiceFactory,
OrganizationServiceInitOptions,
} from "./organization-service.factory";
import { stateServiceFactory, StateServiceInitOptions } from "./state-service.factory";
type EventServiceOptions = FactoryOptions;
export type EventServiceInitOptions = EventServiceOptions &
ApiServiceInitOptions &
CipherServiceInitOptions &
StateServiceInitOptions &
LogServiceInitOptions &
OrganizationServiceInitOptions;
export function eventServiceFactory(
cache: { eventService?: AbstractEventService } & CachedServices,
opts: EventServiceInitOptions
): Promise<AbstractEventService> {
return factory(
cache,
"eventService",
opts,
async () =>
new EventService(
await apiServiceFactory(cache, opts),
await cipherServiceFactory(cache, opts),
await stateServiceFactory(cache, opts),
await logServiceFactory(cache, opts),
await organizationServiceFactory(cache, opts)
)
);
}

View File

@@ -0,0 +1,31 @@
import { EventUploadService as AbstractEventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service";
import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service";
import { apiServiceFactory, ApiServiceInitOptions } from "./api-service.factory";
import { FactoryOptions, CachedServices, factory } from "./factory-options";
import { logServiceFactory, LogServiceInitOptions } from "./log-service.factory";
import { stateServiceFactory, StateServiceInitOptions } from "./state-service.factory";
type EventUploadServiceOptions = FactoryOptions;
export type EventUploadServiceInitOptions = EventUploadServiceOptions &
ApiServiceInitOptions &
StateServiceInitOptions &
LogServiceInitOptions;
export function eventUploadServiceFactory(
cache: { eventUploadService?: AbstractEventUploadService } & CachedServices,
opts: EventUploadServiceInitOptions
): Promise<AbstractEventUploadService> {
return factory(
cache,
"eventUploadService",
opts,
async () =>
new EventUploadService(
await apiServiceFactory(cache, opts),
await stateServiceFactory(cache, opts),
await logServiceFactory(cache, opts)
)
);
}

View File

@@ -1,6 +1,6 @@
import { FolderService as AbstractFolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction"; import { FolderService as AbstractFolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
import { FolderService } from "../../services/folders/folder.service"; import { BrowserFolderService } from "../../services/browser-folder.service";
import { cipherServiceFactory, CipherServiceInitOptions } from "./cipher-service.factory"; import { cipherServiceFactory, CipherServiceInitOptions } from "./cipher-service.factory";
import { cryptoServiceFactory, CryptoServiceInitOptions } from "./crypto-service.factory"; import { cryptoServiceFactory, CryptoServiceInitOptions } from "./crypto-service.factory";
@@ -28,7 +28,7 @@ export function folderServiceFactory(
"folderService", "folderService",
opts, opts,
async () => async () =>
new FolderService( new BrowserFolderService(
await cryptoServiceFactory(cache, opts), await cryptoServiceFactory(cache, opts),
await i18nServiceFactory(cache, opts), await i18nServiceFactory(cache, opts),
await cipherServiceFactory(cache, opts), await cipherServiceFactory(cache, opts),

View File

@@ -1,17 +1,13 @@
import { OrganizationService as AbstractOrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; import { OrganizationService as AbstractOrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { OrganizationService } from "@bitwarden/common/services/organization/organization.service";
import { BrowserOrganizationService } from "../../services/browser-organization.service";
import { FactoryOptions, CachedServices, factory } from "./factory-options"; import { FactoryOptions, CachedServices, factory } from "./factory-options";
import { stateServiceFactory, StateServiceInitOptions } from "./state-service.factory"; import { stateServiceFactory, StateServiceInitOptions } from "./state-service.factory";
import {
syncNotifierServiceFactory,
SyncNotifierServiceInitOptions,
} from "./sync-notifier-service.factory";
type OrganizationServiceFactoryOptions = FactoryOptions; type OrganizationServiceFactoryOptions = FactoryOptions;
export type OrganizationServiceInitOptions = OrganizationServiceFactoryOptions & export type OrganizationServiceInitOptions = OrganizationServiceFactoryOptions &
SyncNotifierServiceInitOptions &
StateServiceInitOptions; StateServiceInitOptions;
export function organizationServiceFactory( export function organizationServiceFactory(
@@ -22,10 +18,6 @@ export function organizationServiceFactory(
cache, cache,
"organizationService", "organizationService",
opts, opts,
async () => async () => new BrowserOrganizationService(await stateServiceFactory(cache, opts))
new OrganizationService(
await stateServiceFactory(cache, opts),
await syncNotifierServiceFactory(cache, opts)
)
); );
} }

View File

@@ -1,5 +1,6 @@
import { PolicyService as AbstractPolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction"; import { PolicyService as AbstractPolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { PolicyService } from "@bitwarden/common/services/policy/policy.service";
import { BrowserPolicyService } from "../../services/browser-policy.service";
import { CachedServices, factory, FactoryOptions } from "./factory-options"; import { CachedServices, factory, FactoryOptions } from "./factory-options";
import { import {
@@ -26,7 +27,7 @@ export function policyServiceFactory(
"policyService", "policyService",
opts, opts,
async () => async () =>
new PolicyService( new BrowserPolicyService(
await stateServiceFactory(cache, opts), await stateServiceFactory(cache, opts),
await organizationServiceFactory(cache, opts) await organizationServiceFactory(cache, opts)
) )

View File

@@ -1,5 +1,6 @@
import { SettingsService as AbstractSettingsService } from "@bitwarden/common/abstractions/settings.service"; import { SettingsService as AbstractSettingsService } from "@bitwarden/common/abstractions/settings.service";
import { SettingsService } from "@bitwarden/common/services/settings.service";
import { BrowserSettingsService } from "../../services/browser-settings.service";
import { FactoryOptions, CachedServices, factory } from "./factory-options"; import { FactoryOptions, CachedServices, factory } from "./factory-options";
import { stateServiceFactory, StateServiceInitOptions } from "./state-service.factory"; import { stateServiceFactory, StateServiceInitOptions } from "./state-service.factory";
@@ -16,6 +17,6 @@ export function settingsServiceFactory(
cache, cache,
"settingsService", "settingsService",
opts, opts,
async () => new SettingsService(await stateServiceFactory(cache, opts)) async () => new BrowserSettingsService(await stateServiceFactory(cache, opts))
); );
} }

View File

@@ -2,7 +2,7 @@ import { StateFactory } from "@bitwarden/common/factories/stateFactory";
import { GlobalState } from "@bitwarden/common/models/domain/global-state"; import { GlobalState } from "@bitwarden/common/models/domain/global-state";
import { Account } from "../../models/account"; import { Account } from "../../models/account";
import { StateService } from "../../services/state.service"; import { BrowserStateService } from "../../services/browser-state.service";
import { CachedServices, factory, FactoryOptions } from "./factory-options"; import { CachedServices, factory, FactoryOptions } from "./factory-options";
import { logServiceFactory, LogServiceInitOptions } from "./log-service.factory"; import { logServiceFactory, LogServiceInitOptions } from "./log-service.factory";
@@ -34,15 +34,15 @@ export type StateServiceInitOptions = StateServiceFactoryOptions &
StateMigrationServiceInitOptions; StateMigrationServiceInitOptions;
export async function stateServiceFactory( export async function stateServiceFactory(
cache: { stateService?: StateService } & CachedServices, cache: { stateService?: BrowserStateService } & CachedServices,
opts: StateServiceInitOptions opts: StateServiceInitOptions
): Promise<StateService> { ): Promise<BrowserStateService> {
const service = await factory( const service = await factory(
cache, cache,
"stateService", "stateService",
opts, opts,
async () => async () =>
await new StateService( await new BrowserStateService(
await diskStorageServiceFactory(cache, opts), await diskStorageServiceFactory(cache, opts),
await secureStorageServiceFactory(cache, opts), await secureStorageServiceFactory(cache, opts),
await memoryStorageServiceFactory(cache, opts), await memoryStorageServiceFactory(cache, opts),

View File

@@ -0,0 +1,39 @@
import { BrowserApi } from "../browser/browserApi";
import { ClearClipboard } from "./clear-clipboard";
describe("clearClipboard", () => {
describe("run", () => {
it("Does not clear clipboard when no active tabs are retrieved", async () => {
jest.spyOn(BrowserApi, "getActiveTabs").mockResolvedValue([] as any);
jest.spyOn(BrowserApi, "sendTabsMessage").mockReturnValue();
await ClearClipboard.run();
expect(jest.spyOn(BrowserApi, "sendTabsMessage")).not.toHaveBeenCalled();
expect(jest.spyOn(BrowserApi, "sendTabsMessage")).not.toHaveBeenCalledWith(1, {
command: "clearClipboard",
});
});
it("Sends a message to the content script to clear the clipboard", async () => {
jest.spyOn(BrowserApi, "getActiveTabs").mockResolvedValue([
{
id: 1,
},
] as any);
jest.spyOn(BrowserApi, "sendTabsMessage").mockReturnValue();
await ClearClipboard.run();
expect(jest.spyOn(BrowserApi, "sendTabsMessage")).toHaveBeenCalledTimes(1);
expect(jest.spyOn(BrowserApi, "sendTabsMessage")).toHaveBeenCalledWith(1, {
command: "clearClipboard",
});
});
});
});

View File

@@ -1,79 +0,0 @@
import { mock, MockProxy } from "jest-mock-extended";
import { BrowserApi } from "../browser/browserApi";
import { StateService } from "../services/abstractions/state.service";
import { ClearClipboard } from "./clear-clipboard";
import { getClearClipboardTime, setClearClipboardTime } from "./clipboard-state";
jest.mock("./clipboard-state", () => {
return {
getClearClipboardTime: jest.fn(),
setClearClipboardTime: jest.fn(),
};
});
const getClearClipboardTimeMock = getClearClipboardTime as jest.Mock;
const setClearClipboardTimeMock = setClearClipboardTime as jest.Mock;
describe("clearClipboard", () => {
describe("run", () => {
let stateService: MockProxy<StateService>;
let serviceCache: Record<string, unknown>;
beforeEach(() => {
stateService = mock<StateService>();
serviceCache = {
stateService: stateService,
};
});
afterEach(() => {
jest.resetAllMocks();
});
it("has a clear time that is past execution time", async () => {
const executionTime = new Date(2022, 1, 1, 12);
const clearTime = new Date(2022, 1, 1, 12, 1);
jest.spyOn(BrowserApi, "getActiveTabs").mockResolvedValue([
{
id: 1,
},
] as any);
jest.spyOn(BrowserApi, "sendTabsMessage").mockReturnValue();
getClearClipboardTimeMock.mockResolvedValue(clearTime.getTime());
await ClearClipboard.run(executionTime, serviceCache);
expect(jest.spyOn(BrowserApi, "sendTabsMessage")).toHaveBeenCalledTimes(1);
expect(jest.spyOn(BrowserApi, "sendTabsMessage")).toHaveBeenCalledWith(1, {
command: "clearClipboard",
});
});
it("has a clear time before execution time", async () => {
const executionTime = new Date(2022, 1, 1, 12);
const clearTime = new Date(2022, 1, 1, 11);
setClearClipboardTimeMock.mockResolvedValue(clearTime.getTime());
await ClearClipboard.run(executionTime, serviceCache);
expect(jest.spyOn(BrowserApi, "getActiveTabs")).not.toHaveBeenCalled();
});
it("has an undefined clearTime", async () => {
const executionTime = new Date(2022, 1, 1);
getClearClipboardTimeMock.mockResolvedValue(undefined);
await ClearClipboard.run(executionTime, serviceCache);
expect(jest.spyOn(BrowserApi, "getActiveTabs")).not.toHaveBeenCalled();
});
});
});

View File

@@ -1,43 +1,15 @@
import { StateFactory } from "@bitwarden/common/factories/stateFactory";
import { GlobalState } from "@bitwarden/common/models/domain/global-state";
import { stateServiceFactory } from "../background/service_factories/state-service.factory";
import { BrowserApi } from "../browser/browserApi"; import { BrowserApi } from "../browser/browserApi";
import { Account } from "../models/account";
import { getClearClipboardTime } from "./clipboard-state"; export const clearClipboardAlarmName = "clearClipboard";
export class ClearClipboard { export class ClearClipboard {
static async run(executionTime: Date, serviceCache: Record<string, unknown>) { /**
const stateFactory = new StateFactory(GlobalState, Account); We currently rely on an active tab with an injected content script (`../content/misc-utils.ts`) to clear the clipboard via `window.navigator.clipboard.writeText(text)`
const stateService = await stateServiceFactory(serviceCache, {
cryptoFunctionServiceOptions: {
win: self,
},
encryptServiceOptions: {
logMacFailures: false,
},
logServiceOptions: {
isDev: false,
},
stateMigrationServiceOptions: {
stateFactory: stateFactory,
},
stateServiceOptions: {
stateFactory: stateFactory,
},
});
const clearClipboardTime = await getClearClipboardTime(stateService);
if (!clearClipboardTime) {
return;
}
if (clearClipboardTime < executionTime.getTime()) {
return;
}
With https://bugs.chromium.org/p/chromium/issues/detail?id=1160302 it was said that service workers,
would have access to the clipboard api and then we could migrate to a simpler solution
*/
static async run() {
const activeTabs = await BrowserApi.getActiveTabs(); const activeTabs = await BrowserApi.getActiveTabs();
if (!activeTabs || activeTabs.length === 0) { if (!activeTabs || activeTabs.length === 0) {
return; return;

View File

@@ -1,10 +1,10 @@
import { StateService } from "../services/abstractions/state.service"; import { BrowserStateService } from "../services/abstractions/browser-state.service";
const clearClipboardStorageKey = "clearClipboardTime"; const clearClipboardStorageKey = "clearClipboardTime";
export const getClearClipboardTime = async (stateService: StateService) => { export const getClearClipboardTime = async (stateService: BrowserStateService) => {
return await stateService.getFromSessionMemory<number>(clearClipboardStorageKey); return await stateService.getFromSessionMemory<number>(clearClipboardStorageKey);
}; };
export const setClearClipboardTime = async (stateService: StateService, time: number) => { export const setClearClipboardTime = async (stateService: BrowserStateService, time: number) => {
await stateService.setInSessionMemory(clearClipboardStorageKey, time); await stateService.setInSessionMemory(clearClipboardStorageKey, time);
}; };

View File

@@ -2,30 +2,30 @@ import { mock, MockProxy } from "jest-mock-extended";
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service"; import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
import { setAlarmTime } from "../alarms/alarm-state";
import { BrowserApi } from "../browser/browserApi"; import { BrowserApi } from "../browser/browserApi";
import { StateService } from "../services/abstractions/state.service"; import { BrowserStateService } from "../services/abstractions/browser-state.service";
import { setClearClipboardTime } from "./clipboard-state"; import { clearClipboardAlarmName } from "./clear-clipboard";
import { GeneratePasswordToClipboardCommand } from "./generate-password-to-clipboard-command"; import { GeneratePasswordToClipboardCommand } from "./generate-password-to-clipboard-command";
jest.mock("./clipboard-state", () => { jest.mock("../alarms/alarm-state", () => {
return { return {
getClearClipboardTime: jest.fn(), setAlarmTime: jest.fn(),
setClearClipboardTime: jest.fn(),
}; };
}); });
const setClearClipboardTimeMock = setClearClipboardTime as jest.Mock; const setAlarmTimeMock = setAlarmTime as jest.Mock;
describe("GeneratePasswordToClipboardCommand", () => { describe("GeneratePasswordToClipboardCommand", () => {
let passwordGenerationService: MockProxy<PasswordGenerationService>; let passwordGenerationService: MockProxy<PasswordGenerationService>;
let stateService: MockProxy<StateService>; let stateService: MockProxy<BrowserStateService>;
let sut: GeneratePasswordToClipboardCommand; let sut: GeneratePasswordToClipboardCommand;
beforeEach(() => { beforeEach(() => {
passwordGenerationService = mock<PasswordGenerationService>(); passwordGenerationService = mock<PasswordGenerationService>();
stateService = mock<StateService>(); stateService = mock<BrowserStateService>();
passwordGenerationService.getOptions.mockResolvedValue([{ length: 8 }, {} as any]); passwordGenerationService.getOptions.mockResolvedValue([{ length: 8 }, {} as any]);
@@ -53,9 +53,9 @@ describe("GeneratePasswordToClipboardCommand", () => {
text: "PASSWORD", text: "PASSWORD",
}); });
expect(setClearClipboardTimeMock).toHaveBeenCalledTimes(1); expect(setAlarmTimeMock).toHaveBeenCalledTimes(1);
expect(setClearClipboardTimeMock).toHaveBeenCalledWith(stateService, expect.any(Number)); expect(setAlarmTimeMock).toHaveBeenCalledWith(clearClipboardAlarmName, expect.any(Number));
}); });
it("does not have clear clipboard value", async () => { it("does not have clear clipboard value", async () => {
@@ -70,7 +70,7 @@ describe("GeneratePasswordToClipboardCommand", () => {
text: "PASSWORD", text: "PASSWORD",
}); });
expect(setClearClipboardTimeMock).not.toHaveBeenCalled(); expect(setAlarmTimeMock).not.toHaveBeenCalled();
}); });
}); });
}); });

View File

@@ -1,14 +1,15 @@
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service"; import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
import { StateService } from "../services/abstractions/state.service"; import { setAlarmTime } from "../alarms/alarm-state";
import { BrowserStateService } from "../services/abstractions/browser-state.service";
import { setClearClipboardTime } from "./clipboard-state"; import { clearClipboardAlarmName } from "./clear-clipboard";
import { copyToClipboard } from "./copy-to-clipboard-command"; import { copyToClipboard } from "./copy-to-clipboard-command";
export class GeneratePasswordToClipboardCommand { export class GeneratePasswordToClipboardCommand {
constructor( constructor(
private passwordGenerationService: PasswordGenerationService, private passwordGenerationService: PasswordGenerationService,
private stateService: StateService private stateService: BrowserStateService
) {} ) {}
async generatePasswordToClipboard(tab: chrome.tabs.Tab) { async generatePasswordToClipboard(tab: chrome.tabs.Tab) {
@@ -20,7 +21,7 @@ export class GeneratePasswordToClipboardCommand {
const clearClipboard = await this.stateService.getClearClipboard(); const clearClipboard = await this.stateService.getClearClipboard();
if (clearClipboard != null) { if (clearClipboard != null) {
await setClearClipboardTime(this.stateService, Date.now() + clearClipboard * 1000); await setAlarmTime(clearClipboardAlarmName, clearClipboard * 1000);
} }
} }
} }

View File

@@ -1,6 +1,6 @@
import { BehaviorSubject } from "rxjs"; import { BehaviorSubject } from "rxjs";
import { StateService } from "../../services/state.service"; import { BrowserStateService } from "../../services/browser-state.service";
import { browserSession } from "./browser-session.decorator"; import { browserSession } from "./browser-session.decorator";
import { SessionStorable } from "./session-storable"; import { SessionStorable } from "./session-storable";
@@ -22,25 +22,25 @@ describe("browserSession decorator", () => {
}); });
it("should create if StateService is a constructor argument", () => { it("should create if StateService is a constructor argument", () => {
const stateService = Object.create(StateService.prototype, {}); const stateService = Object.create(BrowserStateService.prototype, {});
@browserSession @browserSession
class TestClass { class TestClass {
constructor(private stateService: StateService) {} constructor(private stateService: BrowserStateService) {}
} }
expect(new TestClass(stateService)).toBeDefined(); expect(new TestClass(stateService)).toBeDefined();
}); });
describe("interaction with @sessionSync decorator", () => { describe("interaction with @sessionSync decorator", () => {
let stateService: StateService; let stateService: BrowserStateService;
@browserSession @browserSession
class TestClass { class TestClass {
@sessionSync({ initializer: (s: string) => s }) @sessionSync({ initializer: (s: string) => s })
private behaviorSubject = new BehaviorSubject(""); private behaviorSubject = new BehaviorSubject("");
constructor(private stateService: StateService) {} constructor(private stateService: BrowserStateService) {}
fromJSON(json: any) { fromJSON(json: any) {
this.behaviorSubject.next(json); this.behaviorSubject.next(json);
@@ -48,7 +48,7 @@ describe("browserSession decorator", () => {
} }
beforeEach(() => { beforeEach(() => {
stateService = Object.create(StateService.prototype, {}) as StateService; stateService = Object.create(BrowserStateService.prototype, {}) as BrowserStateService;
}); });
it("should create a session syncer", () => { it("should create a session syncer", () => {

View File

@@ -1,6 +1,6 @@
import { Constructor } from "type-fest"; import { Constructor } from "type-fest";
import { StateService } from "../../services/state.service"; import { BrowserStateService } from "../../services/browser-state.service";
import { SessionStorable } from "./session-storable"; import { SessionStorable } from "./session-storable";
import { SessionSyncer } from "./session-syncer"; import { SessionSyncer } from "./session-syncer";
@@ -22,7 +22,13 @@ export function browserSession<TCtor extends Constructor<any>>(constructor: TCto
super(...args); super(...args);
// Require state service to be injected // Require state service to be injected
const stateService = args.find((arg) => arg instanceof StateService); const stateService: BrowserStateService = [this as any]
.concat(args)
.find(
(arg) =>
typeof arg.setInSessionMemory === "function" &&
typeof arg.getFromSessionMemory === "function"
);
if (!stateService) { if (!stateService) {
throw new Error( throw new Error(
`Cannot decorate ${constructor.name} with browserSession, Browser's StateService must be injected` `Cannot decorate ${constructor.name} with browserSession, Browser's StateService must be injected`
@@ -38,7 +44,7 @@ export function browserSession<TCtor extends Constructor<any>>(constructor: TCto
); );
} }
buildSyncer(metadata: SyncedItemMetadata, stateService: StateService) { buildSyncer(metadata: SyncedItemMetadata, stateService: BrowserStateService) {
const syncer = new SessionSyncer((this as any)[metadata.propertyKey], stateService, metadata); const syncer = new SessionSyncer((this as any)[metadata.propertyKey], stateService, metadata);
syncer.init(); syncer.init();
return syncer; return syncer;

View File

@@ -8,9 +8,12 @@ describe("sessionSync decorator", () => {
class TestClass { class TestClass {
@sessionSync({ ctor: ctor, initializer: initializer }) @sessionSync({ ctor: ctor, initializer: initializer })
private testProperty = new BehaviorSubject(""); private testProperty = new BehaviorSubject("");
@sessionSync({ ctor: ctor, initializer: initializer, initializeAs: "array" })
private secondTestProperty = new BehaviorSubject("");
complete() { complete() {
this.testProperty.complete(); this.testProperty.complete();
this.secondTestProperty.complete();
} }
} }
@@ -19,11 +22,40 @@ describe("sessionSync decorator", () => {
expect((testClass as any).__syncedItemMetadata).toEqual([ expect((testClass as any).__syncedItemMetadata).toEqual([
expect.objectContaining({ expect.objectContaining({
propertyKey: "testProperty", propertyKey: "testProperty",
sessionKey: "TestClass_testProperty", sessionKey: "testProperty_0",
ctor: ctor, ctor: ctor,
initializer: initializer, initializer: initializer,
}), }),
testClass.complete(), expect.objectContaining({
propertyKey: "secondTestProperty",
sessionKey: "secondTestProperty_1",
ctor: ctor,
initializer: initializer,
initializeAs: "array",
}),
]); ]);
testClass.complete();
});
class TestClass2 {
@sessionSync({ ctor: ctor, initializer: initializer })
private testProperty = new BehaviorSubject("");
complete() {
this.testProperty.complete();
}
}
it("should maintain sessionKey index count for other test classes", () => {
const testClass = new TestClass2();
expect((testClass as any).__syncedItemMetadata).toEqual([
expect.objectContaining({
propertyKey: "testProperty",
sessionKey: "testProperty_2",
ctor: ctor,
initializer: initializer,
}),
]);
testClass.complete();
}); });
}); });

View File

@@ -1,13 +1,17 @@
import { Jsonify } from "type-fest"; import { Jsonify } from "type-fest";
import { SessionStorable } from "./session-storable"; import { SessionStorable } from "./session-storable";
import { InitializeOptions } from "./sync-item-metadata";
class BuildOptions<T> { class BuildOptions<T, TJson = Jsonify<T>> {
ctor?: new () => T; ctor?: new () => T;
initializer?: (keyValuePair: Jsonify<T>) => T; initializer?: (keyValuePair: TJson) => T;
initializeAsArray? = false; initializeAs?: InitializeOptions;
} }
// Used to ensure uniqueness for each synced observable
let index = 0;
/** /**
* A decorator used to indicate the BehaviorSubject should be synced for this browser session across all contexts. * A decorator used to indicate the BehaviorSubject should be synced for this browser session across all contexts.
* *
@@ -20,10 +24,10 @@ class BuildOptions<T> {
* @param buildOptions * @param buildOptions
* Builders for the value, requires either a constructor (ctor) for your BehaviorSubject type or an * Builders for the value, requires either a constructor (ctor) for your BehaviorSubject type or an
* initializer function that takes a key value pair representation of the BehaviorSubject data * initializer function that takes a key value pair representation of the BehaviorSubject data
* and returns your instantiated BehaviorSubject value. `initializeAsArray can optionally be used to indicate * and returns your instantiated BehaviorSubject value. `initializeAs can optionally be used to indicate
* the provided initializer function should be used to build an array of values. For example, * the provided initializer function should be used to build an array of values. For example,
* ```ts * ```ts
* \@sessionSync({ initializer: Foo.fromJSON, initializeAsArray: true }) * \@sessionSync({ initializer: Foo.fromJSON, initializeAs: 'array' })
* ``` * ```
* is equivalent to * is equivalent to
* ``` * ```
@@ -43,10 +47,10 @@ export function sessionSync<T>(buildOptions: BuildOptions<T>) {
p.__syncedItemMetadata.push({ p.__syncedItemMetadata.push({
propertyKey, propertyKey,
sessionKey: `${prototype.constructor.name}_${propertyKey}`, sessionKey: `${propertyKey}_${index++}`,
ctor: buildOptions.ctor, ctor: buildOptions.ctor,
initializer: buildOptions.initializer, initializer: buildOptions.initializer,
initializeAsArray: buildOptions.initializeAsArray, initializeAs: buildOptions.initializeAs ?? "object",
}); });
}; };
} }

View File

@@ -1,8 +1,9 @@
import { awaitAsync, awaitAsync as flushAsyncObservables } from "@bitwarden/angular/../test-utils";
import { mock, MockProxy } from "jest-mock-extended"; import { mock, MockProxy } from "jest-mock-extended";
import { BehaviorSubject } from "rxjs"; import { BehaviorSubject, ReplaySubject } from "rxjs";
import { BrowserApi } from "../../browser/browserApi"; import { BrowserApi } from "../../browser/browserApi";
import { StateService } from "../../services/abstractions/state.service"; import { BrowserStateService } from "../../services/abstractions/browser-state.service";
import { SessionSyncer } from "./session-syncer"; import { SessionSyncer } from "./session-syncer";
import { SyncedItemMetadata } from "./sync-item-metadata"; import { SyncedItemMetadata } from "./sync-item-metadata";
@@ -10,8 +11,13 @@ import { SyncedItemMetadata } from "./sync-item-metadata";
describe("session syncer", () => { describe("session syncer", () => {
const propertyKey = "behaviorSubject"; const propertyKey = "behaviorSubject";
const sessionKey = "Test__" + propertyKey; const sessionKey = "Test__" + propertyKey;
const metaData = { propertyKey, sessionKey, initializer: (s: string) => s }; const metaData: SyncedItemMetadata = {
let stateService: MockProxy<StateService>; propertyKey,
sessionKey,
initializer: (s: string) => s,
initializeAs: "object",
};
let stateService: MockProxy<BrowserStateService>;
let sut: SessionSyncer; let sut: SessionSyncer;
let behaviorSubject: BehaviorSubject<string>; let behaviorSubject: BehaviorSubject<string>;
@@ -23,7 +29,8 @@ describe("session syncer", () => {
manifest_version: 3, manifest_version: 3,
}); });
stateService = mock<StateService>(); stateService = mock<BrowserStateService>();
stateService.hasInSessionMemory.mockResolvedValue(false);
sut = new SessionSyncer(behaviorSubject, stateService, metaData); sut = new SessionSyncer(behaviorSubject, stateService, metaData);
}); });
@@ -34,53 +41,87 @@ describe("session syncer", () => {
}); });
describe("constructor", () => { describe("constructor", () => {
it("should throw if behaviorSubject is not an instance of BehaviorSubject", () => { it("should throw if subject is not an instance of Subject", () => {
expect(() => { expect(() => {
new SessionSyncer({} as any, stateService, null); new SessionSyncer({} as any, stateService, null);
}).toThrowError("behaviorSubject must be an instance of BehaviorSubject"); }).toThrowError("subject must inherit from Subject");
}); });
it("should create if either ctor or initializer is provided", () => { it("should create if either ctor or initializer is provided", () => {
expect( expect(
new SessionSyncer(behaviorSubject, stateService, { propertyKey, sessionKey, ctor: String }) new SessionSyncer(behaviorSubject, stateService, {
propertyKey,
sessionKey,
ctor: String,
initializeAs: "object",
})
).toBeDefined(); ).toBeDefined();
expect( expect(
new SessionSyncer(behaviorSubject, stateService, { new SessionSyncer(behaviorSubject, stateService, {
propertyKey, propertyKey,
sessionKey, sessionKey,
initializer: (s: any) => s, initializer: (s: any) => s,
initializeAs: "object",
}) })
).toBeDefined(); ).toBeDefined();
}); });
it("should throw if neither ctor or initializer is provided", () => { it("should throw if neither ctor or initializer is provided", () => {
expect(() => { expect(() => {
new SessionSyncer(behaviorSubject, stateService, { propertyKey, sessionKey }); new SessionSyncer(behaviorSubject, stateService, {
propertyKey,
sessionKey,
initializeAs: "object",
});
}).toThrowError("ctor or initializer must be provided"); }).toThrowError("ctor or initializer must be provided");
}); });
}); });
describe("manifest v2 init", () => { describe("init", () => {
let observeSpy: jest.SpyInstance; it("should ignore all updates currently in a ReplaySubject's buffer", () => {
let listenForUpdatesSpy: jest.SpyInstance; const replaySubject = new ReplaySubject<string>(Infinity);
replaySubject.next("1");
beforeEach(() => { replaySubject.next("2");
observeSpy = jest.spyOn(behaviorSubject, "subscribe").mockReturnThis(); replaySubject.next("3");
listenForUpdatesSpy = jest.spyOn(BrowserApi, "messageListener").mockReturnValue(); sut = new SessionSyncer(replaySubject, stateService, metaData);
jest.spyOn(chrome.runtime, "getManifest").mockReturnValue({ // block observing the subject
name: "bitwarden-test", jest.spyOn(sut as any, "observe").mockImplementation();
version: "0.0.0",
manifest_version: 2,
});
sut.init(); sut.init();
expect(sut["ignoreNUpdates"]).toBe(3);
}); });
it("should not start observing", () => { it("should ignore BehaviorSubject's initial value", () => {
expect(observeSpy).not.toHaveBeenCalled(); const behaviorSubject = new BehaviorSubject<string>("initial");
sut = new SessionSyncer(behaviorSubject, stateService, metaData);
// block observing the subject
jest.spyOn(sut as any, "observe").mockImplementation();
sut.init();
expect(sut["ignoreNUpdates"]).toBe(1);
}); });
it("should not start listening", () => { it("should grab an initial value from storage if it exists", async () => {
expect(listenForUpdatesSpy).not.toHaveBeenCalled(); stateService.hasInSessionMemory.mockResolvedValue(true);
//Block a call to update
const updateSpy = jest.spyOn(sut as any, "update").mockImplementation();
sut.init();
await awaitAsync();
expect(updateSpy).toHaveBeenCalledWith();
});
it("should not grab an initial value from storage if it does not exist", async () => {
stateService.hasInSessionMemory.mockResolvedValue(false);
//Block a call to update
const updateSpy = jest.spyOn(sut as any, "update").mockImplementation();
sut.init();
await awaitAsync();
expect(updateSpy).not.toHaveBeenCalled();
}); });
}); });
@@ -146,6 +187,7 @@ describe("session syncer", () => {
stateService.getFromSessionMemory.mockResolvedValue("test"); stateService.getFromSessionMemory.mockResolvedValue("test");
await sut.updateFromMessage({ command: `${sessionKey}_update`, id: "different_id" }); await sut.updateFromMessage({ command: `${sessionKey}_update`, id: "different_id" });
await flushAsyncObservables();
expect(stateService.getFromSessionMemory).toHaveBeenCalledTimes(1); expect(stateService.getFromSessionMemory).toHaveBeenCalledTimes(1);
expect(stateService.getFromSessionMemory).toHaveBeenCalledWith(sessionKey, builder); expect(stateService.getFromSessionMemory).toHaveBeenCalledWith(sessionKey, builder);

View File

@@ -1,9 +1,9 @@
import { BehaviorSubject, concatMap, Subscription } from "rxjs"; import { BehaviorSubject, concatMap, ReplaySubject, Subject, Subscription } from "rxjs";
import { Utils } from "@bitwarden/common/misc/utils"; import { Utils } from "@bitwarden/common/misc/utils";
import { BrowserApi } from "../../browser/browserApi"; import { BrowserApi } from "../../browser/browserApi";
import { StateService } from "../../services/abstractions/state.service"; import { BrowserStateService } from "../../services/abstractions/browser-state.service";
import { SyncedItemMetadata } from "./sync-item-metadata"; import { SyncedItemMetadata } from "./sync-item-metadata";
@@ -11,16 +11,16 @@ export class SessionSyncer {
subscription: Subscription; subscription: Subscription;
id = Utils.newGuid(); id = Utils.newGuid();
// everyone gets the same initial values // ignore initial values
private ignoreNextUpdate = true; private ignoreNUpdates = 0;
constructor( constructor(
private behaviorSubject: BehaviorSubject<any>, private subject: Subject<any>,
private stateService: StateService, private stateService: BrowserStateService,
private metaData: SyncedItemMetadata private metaData: SyncedItemMetadata
) { ) {
if (!(behaviorSubject instanceof BehaviorSubject)) { if (!(subject instanceof Subject)) {
throw new Error("behaviorSubject must be an instance of BehaviorSubject"); throw new Error("subject must inherit from Subject");
} }
if (metaData.ctor == null && metaData.initializer == null) { if (metaData.ctor == null && metaData.initializer == null) {
@@ -29,11 +29,26 @@ export class SessionSyncer {
} }
init() { init() {
if (BrowserApi.manifestVersion !== 3) { switch (this.subject.constructor) {
return; case ReplaySubject:
// ignore all updates currently in the buffer
this.ignoreNUpdates = (this.subject as any)._buffer.length;
break;
case BehaviorSubject:
this.ignoreNUpdates = 1;
break;
default:
break;
} }
this.observe(); this.observe();
// must be synchronous
this.stateService.hasInSessionMemory(this.metaData.sessionKey).then((hasInSessionMemory) => {
if (hasInSessionMemory) {
this.update();
}
});
this.listenForUpdates(); this.listenForUpdates();
} }
@@ -41,11 +56,11 @@ export class SessionSyncer {
// This may be a memory leak. // This may be a memory leak.
// There is no good time to unsubscribe from this observable. Hopefully Manifest V3 clears memory from temporary // There is no good time to unsubscribe from this observable. Hopefully Manifest V3 clears memory from temporary
// contexts. If so, this is handled by destruction of the context. // contexts. If so, this is handled by destruction of the context.
this.subscription = this.behaviorSubject this.subscription = this.subject
.pipe( .pipe(
concatMap(async (next) => { concatMap(async (next) => {
if (this.ignoreNextUpdate) { if (this.ignoreNUpdates > 0) {
this.ignoreNextUpdate = false; this.ignoreNUpdates -= 1;
return; return;
} }
await this.updateSession(next); await this.updateSession(next);
@@ -66,10 +81,14 @@ export class SessionSyncer {
if (message.command != this.updateMessageCommand || message.id === this.id) { if (message.command != this.updateMessageCommand || message.id === this.id) {
return; return;
} }
this.update();
}
async update() {
const builder = SyncedItemMetadata.builder(this.metaData); const builder = SyncedItemMetadata.builder(this.metaData);
const value = await this.stateService.getFromSessionMemory(this.metaData.sessionKey, builder); const value = await this.stateService.getFromSessionMemory(this.metaData.sessionKey, builder);
this.ignoreNextUpdate = true; this.ignoreNUpdates = 1;
this.behaviorSubject.next(value); this.subject.next(value);
} }
private async updateSession(value: any) { private async updateSession(value: any) {

View File

@@ -1,17 +1,27 @@
export type InitializeOptions = "array" | "record" | "object";
export class SyncedItemMetadata { export class SyncedItemMetadata {
propertyKey: string; propertyKey: string;
sessionKey: string; sessionKey: string;
ctor?: new () => any; ctor?: new () => any;
initializer?: (keyValuePair: any) => any; initializer?: (keyValuePair: any) => any;
initializeAsArray?: boolean; initializeAs: InitializeOptions;
static builder(metadata: SyncedItemMetadata): (o: any) => any { static builder(metadata: SyncedItemMetadata): (o: any) => any {
const itemBuilder = const itemBuilder =
metadata.initializer != null metadata.initializer != null
? metadata.initializer ? metadata.initializer
: (o: any) => Object.assign(new metadata.ctor(), o); : (o: any) => Object.assign(new metadata.ctor(), o);
if (metadata.initializeAsArray) { if (metadata.initializeAs === "array") {
return (keyValuePair: any) => keyValuePair.map((o: any) => itemBuilder(o)); return (keyValuePair: any) => keyValuePair.map((o: any) => itemBuilder(o));
} else if (metadata.initializeAs === "record") {
return (keyValuePair: any) => {
const record: Record<any, any> = {};
for (const key in keyValuePair) {
record[key] = itemBuilder(keyValuePair[key]);
}
return record;
};
} else { } else {
return (keyValuePair: any) => itemBuilder(keyValuePair); return (keyValuePair: any) => itemBuilder(keyValuePair);
} }

View File

@@ -8,32 +8,60 @@ describe("builder", () => {
const ctor = TestClass; const ctor = TestClass;
it("should use initializer if provided", () => { it("should use initializer if provided", () => {
const metadata = { propertyKey, sessionKey: key, initializer }; const metadata: SyncedItemMetadata = {
propertyKey,
sessionKey: key,
initializer,
initializeAs: "object",
};
const builder = SyncedItemMetadata.builder(metadata); const builder = SyncedItemMetadata.builder(metadata);
expect(builder({})).toBe("used initializer"); expect(builder({})).toBe("used initializer");
}); });
it("should use ctor if initializer is not provided", () => { it("should use ctor if initializer is not provided", () => {
const metadata = { propertyKey, sessionKey: key, ctor }; const metadata: SyncedItemMetadata = {
propertyKey,
sessionKey: key,
ctor,
initializeAs: "object",
};
const builder = SyncedItemMetadata.builder(metadata); const builder = SyncedItemMetadata.builder(metadata);
expect(builder({})).toBeInstanceOf(TestClass); expect(builder({})).toBeInstanceOf(TestClass);
}); });
it("should prefer initializer over ctor", () => { it("should prefer initializer over ctor", () => {
const metadata = { propertyKey, sessionKey: key, ctor, initializer }; const metadata: SyncedItemMetadata = {
propertyKey,
sessionKey: key,
ctor,
initializer,
initializeAs: "object",
};
const builder = SyncedItemMetadata.builder(metadata); const builder = SyncedItemMetadata.builder(metadata);
expect(builder({})).toBe("used initializer"); expect(builder({})).toBe("used initializer");
}); });
it("should honor initialize as array", () => { it("should honor initialize as array", () => {
const metadata = { const metadata: SyncedItemMetadata = {
propertyKey, propertyKey,
sessionKey: key, sessionKey: key,
initializer: initializer, initializer: initializer,
initializeAsArray: true, initializeAs: "array",
}; };
const builder = SyncedItemMetadata.builder(metadata); const builder = SyncedItemMetadata.builder(metadata);
expect(builder([{}])).toBeInstanceOf(Array); expect(builder([{}])).toBeInstanceOf(Array);
expect(builder([{}])[0]).toBe("used initializer"); expect(builder([{}])[0]).toBe("used initializer");
}); });
it("should honor initialize as record", () => {
const metadata: SyncedItemMetadata = {
propertyKey,
sessionKey: key,
initializer: initializer,
initializeAs: "record",
};
const builder = SyncedItemMetadata.builder(metadata);
expect(builder({ key: "" })).toBeInstanceOf(Object);
expect(builder({ key: "" })).toStrictEqual({ key: "used initializer" });
});
}); });

View File

@@ -15,7 +15,7 @@ import { searchServiceFactory } from "../background/service_factories/search-ser
import { stateServiceFactory } from "../background/service_factories/state-service.factory"; import { stateServiceFactory } from "../background/service_factories/state-service.factory";
import { BrowserApi } from "../browser/browserApi"; import { BrowserApi } from "../browser/browserApi";
import { Account } from "../models/account"; import { Account } from "../models/account";
import { StateService } from "../services/abstractions/state.service"; import { BrowserStateService } from "../services/abstractions/browser-state.service";
import BrowserPlatformUtilsService from "../services/browserPlatformUtils.service"; import BrowserPlatformUtilsService from "../services/browserPlatformUtils.service";
export type BadgeOptions = { export type BadgeOptions = {
@@ -25,7 +25,7 @@ export type BadgeOptions = {
export class UpdateBadge { export class UpdateBadge {
private authService: AuthService; private authService: AuthService;
private stateService: StateService; private stateService: BrowserStateService;
private cipherService: CipherService; private cipherService: CipherService;
private badgeAction: typeof chrome.action; private badgeAction: typeof chrome.action;
private sidebarAction: OperaSidebarAction | FirefoxSidebarAction; private sidebarAction: OperaSidebarAction | FirefoxSidebarAction;

View File

@@ -1,3 +1,5 @@
import { Jsonify } from "type-fest";
import { import {
Account as BaseAccount, Account as BaseAccount,
AccountSettings as BaseAccountSettings, AccountSettings as BaseAccountSettings,
@@ -9,6 +11,14 @@ import { BrowserSendComponentState } from "./browserSendComponentState";
export class AccountSettings extends BaseAccountSettings { export class AccountSettings extends BaseAccountSettings {
vaultTimeout = -1; // On Restart vaultTimeout = -1; // On Restart
static fromJSON(json: Jsonify<AccountSettings>): AccountSettings {
if (json == null) {
return null;
}
return Object.assign(new AccountSettings(), json, super.fromJSON(json));
}
} }
export class Account extends BaseAccount { export class Account extends BaseAccount {
@@ -29,4 +39,18 @@ export class Account extends BaseAccount {
this.ciphers = init?.ciphers ?? new BrowserComponentState(); this.ciphers = init?.ciphers ?? new BrowserComponentState();
this.sendType = init?.sendType ?? new BrowserComponentState(); this.sendType = init?.sendType ?? new BrowserComponentState();
} }
static fromJSON(json: Jsonify<Account>): Account {
if (json == null) {
return null;
}
return Object.assign(new Account({}), json, super.fromJSON(json), {
settings: AccountSettings.fromJSON(json.settings),
groupings: BrowserGroupingsComponentState.fromJSON(json.groupings),
send: BrowserSendComponentState.fromJSON(json.send),
ciphers: BrowserComponentState.fromJSON(json.ciphers),
sendType: BrowserComponentState.fromJSON(json.sendType),
});
}
} }

View File

@@ -1,4 +1,14 @@
import { Jsonify } from "type-fest";
export class BrowserComponentState { export class BrowserComponentState {
scrollY: number; scrollY: number;
searchText: string; searchText: string;
static fromJSON(json: Jsonify<BrowserComponentState>) {
if (json == null) {
return null;
}
return Object.assign(new BrowserComponentState(), json);
}
} }

View File

@@ -1,7 +1,9 @@
import { CipherType } from "@bitwarden/common/enums/cipherType"; import { CipherType } from "@bitwarden/common/enums/cipherType";
import { Utils } from "@bitwarden/common/misc/utils";
import { CipherView } from "@bitwarden/common/models/view/cipher.view"; import { CipherView } from "@bitwarden/common/models/view/cipher.view";
import { CollectionView } from "@bitwarden/common/models/view/collection.view"; import { CollectionView } from "@bitwarden/common/models/view/collection.view";
import { FolderView } from "@bitwarden/common/models/view/folder.view"; import { FolderView } from "@bitwarden/common/models/view/folder.view";
import { DeepJsonify } from "@bitwarden/common/types/deep-jsonify";
import { BrowserComponentState } from "./browserComponentState"; import { BrowserComponentState } from "./browserComponentState";
@@ -15,4 +17,28 @@ export class BrowserGroupingsComponentState extends BrowserComponentState {
folders: FolderView[]; folders: FolderView[];
collections: CollectionView[]; collections: CollectionView[];
deletedCount: number; deletedCount: number;
toJSON() {
return Utils.merge(this, {
collectionCounts: Utils.mapToRecord(this.collectionCounts),
folderCounts: Utils.mapToRecord(this.folderCounts),
typeCounts: Utils.mapToRecord(this.typeCounts),
});
}
static fromJSON(json: DeepJsonify<BrowserGroupingsComponentState>) {
if (json == null) {
return null;
}
return Object.assign(new BrowserGroupingsComponentState(), json, {
favoriteCiphers: json.favoriteCiphers?.map((c) => CipherView.fromJSON(c)),
noFolderCiphers: json.noFolderCiphers?.map((c) => CipherView.fromJSON(c)),
ciphers: json.ciphers?.map((c) => CipherView.fromJSON(c)),
collectionCounts: Utils.recordToMap(json.collectionCounts),
folderCounts: Utils.recordToMap(json.folderCounts),
typeCounts: Utils.recordToMap(json.typeCounts),
folders: json.folders?.map((f) => FolderView.fromJSON(f)),
});
}
} }

View File

@@ -1,9 +1,28 @@
import { SendType } from "@bitwarden/common/enums/sendType"; import { SendType } from "@bitwarden/common/enums/sendType";
import { Utils } from "@bitwarden/common/misc/utils";
import { SendView } from "@bitwarden/common/models/view/send.view"; import { SendView } from "@bitwarden/common/models/view/send.view";
import { DeepJsonify } from "@bitwarden/common/types/deep-jsonify";
import { BrowserComponentState } from "./browserComponentState"; import { BrowserComponentState } from "./browserComponentState";
export class BrowserSendComponentState extends BrowserComponentState { export class BrowserSendComponentState extends BrowserComponentState {
sends: SendView[]; sends: SendView[];
typeCounts: Map<SendType, number>; typeCounts: Map<SendType, number>;
toJSON() {
return Utils.merge(this, {
typeCounts: Utils.mapToRecord(this.typeCounts),
});
}
static fromJSON(json: DeepJsonify<BrowserSendComponentState>) {
if (json == null) {
return null;
}
return Object.assign(new BrowserSendComponentState(), json, {
sends: json.sends?.map((s) => SendView.fromJSON(s)),
typeCounts: Utils.recordToMap(json.typeCounts),
});
}
} }

View File

@@ -39,13 +39,10 @@
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<div class="box-content-row-flex"> <div class="box-content-row-flex">
<div class="row-main"> <div class="row-main">
<label for="masterPassword" <label for="masterPassword">
>{{ "masterPass" | i18n }} {{ "masterPass" | i18n }}
<strong <strong class="sub-label text-{{ color }}" *ngIf="text">
class="sub-label text-{{ passwordStrengthComponent?.masterPasswordScoreColor }}" {{ text }}
*ngIf="passwordStrengthComponent?.masterPasswordScoreText"
>
{{ passwordStrengthComponent?.masterPasswordScoreText }}
</strong> </strong>
</label> </label>
<input <input

View File

@@ -86,7 +86,9 @@
</div> </div>
</ng-container> </ng-container>
<ng-container *ngIf="selectedProviderType === providerType.WebAuthn && !webAuthnNewTab"> <ng-container *ngIf="selectedProviderType === providerType.WebAuthn && !webAuthnNewTab">
<div id="web-authn-frame"><iframe id="webauthn_iframe" [allow]="webAuthnAllow"></iframe></div> <div id="web-authn-frame">
<iframe id="webauthn_iframe" [attr.allow]="webAuthnAllow"></iframe>
</div>
<div class="box"> <div class="box">
<div class="box-content"> <div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow> <div class="box-content-row box-content-row-checkbox" appBoxRow>

View File

@@ -34,12 +34,12 @@ import { SyncComponent } from "./settings/sync.component";
import { TabsComponent } from "./tabs.component"; import { TabsComponent } from "./tabs.component";
import { AddEditComponent } from "./vault/add-edit.component"; import { AddEditComponent } from "./vault/add-edit.component";
import { AttachmentsComponent } from "./vault/attachments.component"; import { AttachmentsComponent } from "./vault/attachments.component";
import { CiphersComponent } from "./vault/ciphers.component";
import { CollectionsComponent } from "./vault/collections.component"; import { CollectionsComponent } from "./vault/collections.component";
import { CurrentTabComponent } from "./vault/current-tab.component"; import { CurrentTabComponent } from "./vault/current-tab.component";
import { PasswordHistoryComponent } from "./vault/password-history.component"; import { PasswordHistoryComponent } from "./vault/password-history.component";
import { ShareComponent } from "./vault/share.component"; import { ShareComponent } from "./vault/share.component";
import { VaultFilterComponent } from "./vault/vault-filter.component"; import { VaultFilterComponent } from "./vault/vault-filter.component";
import { VaultItemsComponent } from "./vault/vault-items.component";
import { ViewComponent } from "./vault/view.component"; import { ViewComponent } from "./vault/view.component";
const routes: Routes = [ const routes: Routes = [
@@ -120,7 +120,7 @@ const routes: Routes = [
}, },
{ {
path: "ciphers", path: "ciphers",
component: CiphersComponent, component: VaultItemsComponent,
canActivate: [AuthGuard], canActivate: [AuthGuard],
data: { state: "ciphers" }, data: { state: "ciphers" },
}, },

View File

@@ -19,7 +19,7 @@ import { MessagingService } from "@bitwarden/common/abstractions/messaging.servi
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { BrowserApi } from "../browser/browserApi"; import { BrowserApi } from "../browser/browserApi";
import { StateService } from "../services/abstractions/state.service"; import { BrowserStateService } from "../services/abstractions/browser-state.service";
import { routerTransition } from "./app-routing.animations"; import { routerTransition } from "./app-routing.animations";
@@ -43,7 +43,7 @@ export class AppComponent implements OnInit, OnDestroy {
private authService: AuthService, private authService: AuthService,
private i18nService: I18nService, private i18nService: I18nService,
private router: Router, private router: Router,
private stateService: StateService, private stateService: BrowserStateService,
private messagingService: MessagingService, private messagingService: MessagingService,
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
private ngZone: NgZone, private ngZone: NgZone,
@@ -276,7 +276,7 @@ export class AppComponent implements OnInit, OnDestroy {
await Promise.all([ await Promise.all([
this.stateService.setBrowserGroupingComponentState(null), this.stateService.setBrowserGroupingComponentState(null),
this.stateService.setBrowserCipherComponentState(null), this.stateService.setBrowserVaultItemsComponentState(null),
this.stateService.setBrowserSendComponentState(null), this.stateService.setBrowserSendComponentState(null),
this.stateService.setBrowserSendTypeComponentState(null), this.stateService.setBrowserSendTypeComponentState(null),
]); ]);

View File

@@ -108,12 +108,12 @@ import { TabsComponent } from "./tabs.component";
import { AddEditCustomFieldsComponent } from "./vault/add-edit-custom-fields.component"; import { AddEditCustomFieldsComponent } from "./vault/add-edit-custom-fields.component";
import { AddEditComponent } from "./vault/add-edit.component"; import { AddEditComponent } from "./vault/add-edit.component";
import { AttachmentsComponent } from "./vault/attachments.component"; import { AttachmentsComponent } from "./vault/attachments.component";
import { CiphersComponent } from "./vault/ciphers.component";
import { CollectionsComponent } from "./vault/collections.component"; import { CollectionsComponent } from "./vault/collections.component";
import { CurrentTabComponent } from "./vault/current-tab.component"; import { CurrentTabComponent } from "./vault/current-tab.component";
import { PasswordHistoryComponent } from "./vault/password-history.component"; import { PasswordHistoryComponent } from "./vault/password-history.component";
import { ShareComponent } from "./vault/share.component"; import { ShareComponent } from "./vault/share.component";
import { VaultFilterComponent } from "./vault/vault-filter.component"; import { VaultFilterComponent } from "./vault/vault-filter.component";
import { VaultItemsComponent } from "./vault/vault-items.component";
import { VaultSelectComponent } from "./vault/vault-select.component"; import { VaultSelectComponent } from "./vault/vault-select.component";
import { ViewCustomFieldsComponent } from "./vault/view-custom-fields.component"; import { ViewCustomFieldsComponent } from "./vault/view-custom-fields.component";
import { ViewComponent } from "./vault/view.component"; import { ViewComponent } from "./vault/view.component";
@@ -200,7 +200,7 @@ registerLocaleData(localeZhTw, "zh-TW");
AppComponent, AppComponent,
AttachmentsComponent, AttachmentsComponent,
CipherRowComponent, CipherRowComponent,
CiphersComponent, VaultItemsComponent,
CollectionsComponent, CollectionsComponent,
CurrentTabComponent, CurrentTabComponent,
EnvironmentComponent, EnvironmentComponent,

View File

@@ -1,6 +1,6 @@
import { Component, EventEmitter, Input, Output } from "@angular/core"; import { Component, EventEmitter, Input, Output } from "@angular/core";
import { EventService } from "@bitwarden/common/abstractions/event.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service"; import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
@@ -27,7 +27,7 @@ export class ActionButtonsComponent {
constructor( constructor(
private i18nService: I18nService, private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService, private platformUtilsService: PlatformUtilsService,
private eventService: EventService, private eventCollectionService: EventCollectionService,
private totpService: TotpService, private totpService: TotpService,
private stateService: StateService, private stateService: StateService,
private passwordRepromptService: PasswordRepromptService private passwordRepromptService: PasswordRepromptService
@@ -68,9 +68,12 @@ export class ActionButtonsComponent {
); );
if (typeI18nKey === "password" || typeI18nKey === "verificationCodeTotp") { if (typeI18nKey === "password" || typeI18nKey === "verificationCodeTotp") {
this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, cipher.id); this.eventCollectionService.collect(
EventType.Cipher_ClientToggledHiddenFieldVisible,
cipher.id
);
} else if (typeI18nKey === "securityCode") { } else if (typeI18nKey === "securityCode") {
this.eventService.collect(EventType.Cipher_ClientCopiedCardCode, cipher.id); this.eventCollectionService.collect(EventType.Cipher_ClientCopiedCardCode, cipher.id);
} }
} }

View File

@@ -57,11 +57,11 @@
appStopClick appStopClick
appA11yTitle="{{ 'regenerateUsername' | i18n }}" appA11yTitle="{{ 'regenerateUsername' | i18n }}"
(click)="regenerate()" (click)="regenerate()"
[disabled]="form.loading" [disabled]="$any(form).loading"
> >
<i <i
class="bwi bwi-lg bwi-generate" class="bwi bwi-lg bwi-generate"
[ngClass]="form.loading ? 'bwi-spin' : ''" [ngClass]="$any(form).loading ? 'bwi-spin' : ''"
aria-hidden="true" aria-hidden="true"
></i> ></i>
</button> </button>
@@ -320,22 +320,18 @@
</div> </div>
<div class="box" *ngIf="usernameOptions.type === 'forwarded'"> <div class="box" *ngIf="usernameOptions.type === 'forwarded'">
<div class="box-content"> <div class="box-content">
<div class="box-content-row" role="radiogroup" aria-labelledby="forwardTypeHeading"> <div class="box-content-row" role="listbox" aria-labelledby="forwardTypeHeading">
<label id="forwardTypeHeading" class="radio-header">{{ "service" | i18n }}</label> <label id="forwardTypeHeading">{{ "service" | i18n }}</label>
<div class="radio-group text-default" appBoxRow *ngFor="let o of forwardOptions"> <select
<input id="ForwardTypeDropdown"
type="radio" name="ForwardType"
[(ngModel)]="usernameOptions.forwardedService" [(ngModel)]="usernameOptions.forwardedService"
name="ForwardType" (change)="saveUsernameOptions()"
id="forwardtype_{{ o.value }}" >
[value]="o.value" <option *ngFor="let o of forwardOptions" [ngValue]="o.value" role="option">
(change)="saveUsernameOptions()"
[checked]="usernameOptions.forwardedService === o.value"
/>
<label for="forwardtype_{{ o.value }}">
{{ o.name }} {{ o.name }}
</label> </option>
</div> </select>
</div> </div>
<ng-container *ngIf="usernameOptions.forwardedService === 'simplelogin'"> <ng-container *ngIf="usernameOptions.forwardedService === 'simplelogin'">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
@@ -355,7 +351,7 @@
<input <input
id="duckduckgo-apikey" id="duckduckgo-apikey"
type="password" type="password"
name="DuckDudkGoApiKey" name="DuckDuckGoApiKey"
[(ngModel)]="usernameOptions.forwardedDuckDuckGoToken" [(ngModel)]="usernameOptions.forwardedDuckDuckGoToken"
(blur)="saveUsernameOptions()" (blur)="saveUsernameOptions()"
/> />

View File

@@ -432,6 +432,12 @@ app-vault-view .box-footer {
user-select: auto; user-select: auto;
} }
/* tweak for inconsistent line heights in cipher view */
.box-footer button,
.box-footer a {
line-height: 1;
}
// Workaround for slow performance on external monitors on Chrome + MacOS // Workaround for slow performance on external monitors on Chrome + MacOS
// See: https://bugs.chromium.org/p/chromium/issues/detail?id=971701#c64 // See: https://bugs.chromium.org/p/chromium/issues/detail?id=971701#c64
@keyframes redraw { @keyframes redraw {

View File

@@ -305,8 +305,11 @@
> >
<div class="row-main text-danger"> <div class="row-main text-danger">
<div class="icon text-danger" aria-hidden="true"> <div class="icon text-danger" aria-hidden="true">
<i class="bwi bwi-trash bwi-lg bwi-fw" [hidden]="deleteBtn.loading"></i> <i class="bwi bwi-trash bwi-lg bwi-fw" [hidden]="$any(deleteBtn).loading"></i>
<i class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw" [hidden]="!deleteBtn.loading"></i> <i
class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw"
[hidden]="!$any(deleteBtn).loading"
></i>
</div> </div>
<span>{{ "deleteSend" | i18n }}</span> <span>{{ "deleteSend" | i18n }}</span>
</div> </div>

View File

@@ -12,7 +12,7 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { SendService } from "@bitwarden/common/abstractions/send.service"; import { SendService } from "@bitwarden/common/abstractions/send.service";
import { StateService } from "../../services/abstractions/state.service"; import { BrowserStateService } from "../../services/abstractions/browser-state.service";
import { PopupUtilsService } from "../services/popup-utils.service"; import { PopupUtilsService } from "../services/popup-utils.service";
@Component({ @Component({
@@ -33,7 +33,7 @@ export class SendAddEditComponent extends BaseAddEditComponent {
constructor( constructor(
i18nService: I18nService, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, platformUtilsService: PlatformUtilsService,
stateService: StateService, stateService: BrowserStateService,
messagingService: MessagingService, messagingService: MessagingService,
policyService: PolicyService, policyService: PolicyService,
environmentService: EnvironmentService, environmentService: EnvironmentService,

View File

@@ -15,7 +15,7 @@ import { SendType } from "@bitwarden/common/enums/sendType";
import { SendView } from "@bitwarden/common/models/view/send.view"; import { SendView } from "@bitwarden/common/models/view/send.view";
import { BrowserSendComponentState } from "../../models/browserSendComponentState"; import { BrowserSendComponentState } from "../../models/browserSendComponentState";
import { StateService } from "../../services/abstractions/state.service"; import { BrowserStateService } from "../../services/abstractions/browser-state.service";
import { PopupUtilsService } from "../services/popup-utils.service"; import { PopupUtilsService } from "../services/popup-utils.service";
const ComponentId = "SendComponent"; const ComponentId = "SendComponent";
@@ -42,7 +42,7 @@ export class SendGroupingsComponent extends BaseSendComponent {
policyService: PolicyService, policyService: PolicyService,
searchService: SearchService, searchService: SearchService,
private popupUtils: PopupUtilsService, private popupUtils: PopupUtilsService,
private stateService: StateService, private stateService: BrowserStateService,
private router: Router, private router: Router,
private syncService: SyncService, private syncService: SyncService,
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
@@ -165,12 +165,12 @@ export class SendGroupingsComponent extends BaseSendComponent {
} }
private async saveState() { private async saveState() {
this.state = { this.state = Object.assign(new BrowserSendComponentState(), {
scrollY: this.popupUtils.getContentScrollY(window), scrollY: this.popupUtils.getContentScrollY(window),
searchText: this.searchText, searchText: this.searchText,
sends: this.sends, sends: this.sends,
typeCounts: this.typeCounts, typeCounts: this.typeCounts,
}; });
await this.stateService.setBrowserSendComponentState(this.state); await this.stateService.setBrowserSendComponentState(this.state);
} }

View File

@@ -16,7 +16,7 @@ import { SendType } from "@bitwarden/common/enums/sendType";
import { SendView } from "@bitwarden/common/models/view/send.view"; import { SendView } from "@bitwarden/common/models/view/send.view";
import { BrowserComponentState } from "../../models/browserComponentState"; import { BrowserComponentState } from "../../models/browserComponentState";
import { StateService } from "../../services/abstractions/state.service"; import { BrowserStateService } from "../../services/abstractions/browser-state.service";
import { PopupUtilsService } from "../services/popup-utils.service"; import { PopupUtilsService } from "../services/popup-utils.service";
const ComponentId = "SendTypeComponent"; const ComponentId = "SendTypeComponent";
@@ -41,7 +41,7 @@ export class SendTypeComponent extends BaseSendComponent {
policyService: PolicyService, policyService: PolicyService,
searchService: SearchService, searchService: SearchService,
private popupUtils: PopupUtilsService, private popupUtils: PopupUtilsService,
private stateService: StateService, private stateService: BrowserStateService,
private route: ActivatedRoute, private route: ActivatedRoute,
private location: Location, private location: Location,
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,

View File

@@ -5,7 +5,7 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService as LogServiceAbstraction } from "@bitwarden/common/abstractions/log.service"; import { LogService as LogServiceAbstraction } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { StateService as StateServiceAbstraction } from "../../services/abstractions/state.service"; import { BrowserStateService as StateServiceAbstraction } from "../../services/abstractions/browser-state.service";
import { PopupUtilsService } from "./popup-utils.service"; import { PopupUtilsService } from "./popup-utils.service";

View File

@@ -15,7 +15,8 @@ import { CollectionService } from "@bitwarden/common/abstractions/collection.ser
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service"; import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service";
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
import { EventService } from "@bitwarden/common/abstractions/event.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { EventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service";
import { ExportService } from "@bitwarden/common/abstractions/export.service"; import { ExportService } from "@bitwarden/common/abstractions/export.service";
import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service"; import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service";
import { FileUploadService } from "@bitwarden/common/abstractions/fileUpload.service"; import { FileUploadService } from "@bitwarden/common/abstractions/fileUpload.service";
@@ -38,6 +39,7 @@ import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abs
import { SendService } from "@bitwarden/common/abstractions/send.service"; import { SendService } from "@bitwarden/common/abstractions/send.service";
import { SettingsService } from "@bitwarden/common/abstractions/settings.service"; import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/abstractions/state.service"; import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/abstractions/state.service";
import { StateMigrationService } from "@bitwarden/common/abstractions/stateMigration.service";
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service"; import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction"; import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { TokenService } from "@bitwarden/common/abstractions/token.service"; import { TokenService } from "@bitwarden/common/abstractions/token.service";
@@ -47,6 +49,8 @@ import { UserVerificationService } from "@bitwarden/common/abstractions/userVeri
import { UsernameGenerationService } from "@bitwarden/common/abstractions/usernameGeneration.service"; import { UsernameGenerationService } from "@bitwarden/common/abstractions/usernameGeneration.service";
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service"; import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service"; import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service";
import { StateFactory } from "@bitwarden/common/factories/stateFactory";
import { GlobalState } from "@bitwarden/common/models/domain/global-state";
import { AuthService } from "@bitwarden/common/services/auth.service"; import { AuthService } from "@bitwarden/common/services/auth.service";
import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service"; import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service";
import { LoginService } from "@bitwarden/common/services/login.service"; import { LoginService } from "@bitwarden/common/services/login.service";
@@ -54,9 +58,14 @@ import { SearchService } from "@bitwarden/common/services/search.service";
import MainBackground from "../../background/main.background"; import MainBackground from "../../background/main.background";
import { BrowserApi } from "../../browser/browserApi"; import { BrowserApi } from "../../browser/browserApi";
import { Account } from "../../models/account";
import { AutofillService } from "../../services/abstractions/autofill.service"; import { AutofillService } from "../../services/abstractions/autofill.service";
import { StateService as StateServiceAbstraction } from "../../services/abstractions/state.service"; import { BrowserStateService as StateServiceAbstraction } from "../../services/abstractions/browser-state.service";
import { BrowserEnvironmentService } from "../../services/browser-environment.service"; import { BrowserEnvironmentService } from "../../services/browser-environment.service";
import { BrowserOrganizationService } from "../../services/browser-organization.service";
import { BrowserPolicyService } from "../../services/browser-policy.service";
import { BrowserSettingsService } from "../../services/browser-settings.service";
import { BrowserStateService } from "../../services/browser-state.service";
import { BrowserFileDownloadService } from "../../services/browserFileDownloadService"; import { BrowserFileDownloadService } from "../../services/browserFileDownloadService";
import BrowserMessagingService from "../../services/browserMessaging.service"; import BrowserMessagingService from "../../services/browserMessaging.service";
import BrowserMessagingPrivateModePopupService from "../../services/browserMessagingPrivateModePopup.service"; import BrowserMessagingPrivateModePopupService from "../../services/browserMessagingPrivateModePopup.service";
@@ -187,11 +196,25 @@ function getBgService<T>(service: keyof MainBackground) {
{ provide: TokenService, useFactory: getBgService<TokenService>("tokenService"), deps: [] }, { provide: TokenService, useFactory: getBgService<TokenService>("tokenService"), deps: [] },
{ provide: I18nService, useFactory: getBgService<I18nService>("i18nService"), deps: [] }, { provide: I18nService, useFactory: getBgService<I18nService>("i18nService"), deps: [] },
{ provide: CryptoService, useFactory: getBgService<CryptoService>("cryptoService"), deps: [] }, { provide: CryptoService, useFactory: getBgService<CryptoService>("cryptoService"), deps: [] },
{ provide: EventService, useFactory: getBgService<EventService>("eventService"), deps: [] }, {
provide: EventUploadService,
useFactory: getBgService<EventUploadService>("eventUploadService"),
deps: [],
},
{
provide: EventCollectionService,
useFactory: getBgService<EventCollectionService>("eventCollectionService"),
deps: [],
},
{ {
provide: PolicyService, provide: PolicyService,
useFactory: getBgService<PolicyService>("policyService"), useFactory: (
deps: [], stateService: StateServiceAbstraction,
organizationService: OrganizationService
) => {
return new BrowserPolicyService(stateService, organizationService);
},
deps: [StateServiceAbstraction, OrganizationService],
}, },
{ {
provide: PolicyApiServiceAbstraction, provide: PolicyApiServiceAbstraction,
@@ -212,8 +235,10 @@ function getBgService<T>(service: keyof MainBackground) {
{ provide: SyncService, useFactory: getBgService<SyncService>("syncService"), deps: [] }, { provide: SyncService, useFactory: getBgService<SyncService>("syncService"), deps: [] },
{ {
provide: SettingsService, provide: SettingsService,
useFactory: getBgService<SettingsService>("settingsService"), useFactory: (stateService: StateServiceAbstraction) => {
deps: [], return new BrowserSettingsService(stateService);
},
deps: [StateServiceAbstraction],
}, },
{ {
provide: AbstractStorageService, provide: AbstractStorageService,
@@ -261,8 +286,10 @@ function getBgService<T>(service: keyof MainBackground) {
{ provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService }, { provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService },
{ {
provide: OrganizationService, provide: OrganizationService,
useFactory: getBgService<OrganizationService>("organizationService"), useFactory: (stateService: StateServiceAbstraction) => {
deps: [], return new BrowserOrganizationService(stateService);
},
deps: [StateServiceAbstraction],
}, },
{ {
provide: VaultFilterService, provide: VaultFilterService,
@@ -293,10 +320,36 @@ function getBgService<T>(service: keyof MainBackground) {
useFactory: getBgService<AbstractStorageService>("memoryStorageService"), useFactory: getBgService<AbstractStorageService>("memoryStorageService"),
}, },
{ {
provide: StateServiceAbstraction, provide: StateMigrationService,
useFactory: getBgService<StateServiceAbstraction>("stateService"), useFactory: getBgService<StateMigrationService>("stateMigrationService"),
deps: [], deps: [],
}, },
{
provide: StateServiceAbstraction,
useFactory: (
storageService: AbstractStorageService,
secureStorageService: AbstractStorageService,
memoryStorageService: AbstractStorageService,
logService: LogServiceAbstraction,
stateMigrationService: StateMigrationService
) => {
return new BrowserStateService(
storageService,
secureStorageService,
memoryStorageService,
logService,
stateMigrationService,
new StateFactory(GlobalState, Account)
);
},
deps: [
AbstractStorageService,
SECURE_STORAGE,
MEMORY_STORAGE,
LogServiceAbstraction,
StateMigrationService,
],
},
{ {
provide: UsernameGenerationService, provide: UsernameGenerationService,
useFactory: getBgService<UsernameGenerationService>("usernameGenerationService"), useFactory: getBgService<UsernameGenerationService>("usernameGenerationService"),
@@ -317,17 +370,19 @@ function getBgService<T>(service: keyof MainBackground) {
}, },
{ {
provide: AbstractThemingService, provide: AbstractThemingService,
useFactory: () => { useFactory: (
stateService: StateServiceAbstraction,
platformUtilsService: PlatformUtilsService
) => {
return new ThemingService( return new ThemingService(
getBgService<StateServiceAbstraction>("stateService")(), stateService,
// Safari doesn't properly handle the (prefers-color-scheme) media query in the popup window, it always returns light. // Safari doesn't properly handle the (prefers-color-scheme) media query in the popup window, it always returns light.
// In Safari we have to use the background page instead, which comes with limitations like not dynamically changing the extension theme when the system theme is changed. // In Safari we have to use the background page instead, which comes with limitations like not dynamically changing the extension theme when the system theme is changed.
getBgService<PlatformUtilsService>("platformUtilsService")().isSafari() platformUtilsService.isSafari() ? getBgService<Window>("backgroundWindow")() : window,
? getBgService<Window>("backgroundWindow")()
: window,
document document
); );
}, },
deps: [StateServiceAbstraction, PlatformUtilsService],
}, },
], ],
}) })

View File

@@ -4,7 +4,7 @@ import { Router } from "@angular/router";
import { ExportComponent as BaseExportComponent } from "@bitwarden/angular/components/export.component"; import { ExportComponent as BaseExportComponent } from "@bitwarden/angular/components/export.component";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { EventService } from "@bitwarden/common/abstractions/event.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { ExportService } from "@bitwarden/common/abstractions/export.service"; import { ExportService } from "@bitwarden/common/abstractions/export.service";
import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service"; import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@@ -23,7 +23,7 @@ export class ExportComponent extends BaseExportComponent {
i18nService: I18nService, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, platformUtilsService: PlatformUtilsService,
exportService: ExportService, exportService: ExportService,
eventService: EventService, eventCollectionService: EventCollectionService,
policyService: PolicyService, policyService: PolicyService,
private router: Router, private router: Router,
logService: LogService, logService: LogService,
@@ -36,7 +36,7 @@ export class ExportComponent extends BaseExportComponent {
i18nService, i18nService,
platformUtilsService, platformUtilsService,
exportService, exportService,
eventService, eventCollectionService,
policyService, policyService,
window, window,
logService, logService,

View File

@@ -40,8 +40,11 @@
> >
<div class="row-main text-danger"> <div class="row-main text-danger">
<div class="icon text-danger" aria-hidden="true"> <div class="icon text-danger" aria-hidden="true">
<i class="bwi bwi-trash bwi-lg bwi-fw" [hidden]="deleteBtn.loading"></i> <i class="bwi bwi-trash bwi-lg bwi-fw" [hidden]="$any(deleteBtn).loading"></i>
<i class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw" [hidden]="!deleteBtn.loading"></i> <i
class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw"
[hidden]="!$any(deleteBtn).loading"
></i>
</div> </div>
<span>{{ "deleteFolder" | i18n }}</span> <span>{{ "deleteFolder" | i18n }}</span>
</div> </div>

View File

@@ -49,14 +49,14 @@
#refreshBtn #refreshBtn
type="button" type="button"
(click)="refresh()" (click)="refresh()"
[disabled]="refreshBtn.loading" [disabled]="$any(refreshBtn).loading"
[appApiAction]="refreshPromise" [appApiAction]="refreshPromise"
class="btn link block" class="btn link block"
> >
<span [hidden]="refreshBtn.loading">{{ "premiumRefresh" | i18n }}</span> <span [hidden]="$any(refreshBtn).loading">{{ "premiumRefresh" | i18n }}</span>
<i <i
class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw" class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw"
[hidden]="!refreshBtn.loading" [hidden]="!$any(refreshBtn).loading"
aria-hidden="true" aria-hidden="true"
></i> ></i>
</button> </button>

View File

@@ -17,11 +17,15 @@
class="btn block primary" class="btn block primary"
(click)="sync()" (click)="sync()"
#syncBtn #syncBtn
[disabled]="syncBtn.loading" [disabled]="$any(syncBtn).loading"
[appApiAction]="syncPromise" [appApiAction]="syncPromise"
> >
<span [hidden]="syncBtn.loading">{{ "syncVaultNow" | i18n }}</span> <span [hidden]="$any(syncBtn).loading">{{ "syncVaultNow" | i18n }}</span>
<i class="bwi bwi-spinner bwi-lg bwi-spin" [hidden]="!syncBtn.loading" aria-hidden="true"></i> <i
class="bwi bwi-spinner bwi-lg bwi-spin"
[hidden]="!$any(syncBtn).loading"
aria-hidden="true"
></i>
</button> </button>
<p class="text-center text-muted small">{{ "lastSync" | i18n }} {{ lastSync }}</p> <p class="text-center text-muted small">{{ "lastSync" | i18n }} {{ lastSync }}</p>
</div> </div>

View File

@@ -81,8 +81,8 @@
[(ngModel)]="f.value" [(ngModel)]="f.value"
*ngIf="f.type === fieldType.Boolean" *ngIf="f.type === fieldType.Boolean"
appTrueFalseValue appTrueFalseValue
trueValue="true" [trueValue]="true"
falseValue="false" [falseValue]="false"
attr.aria-describedby="fieldName{{ i }}" attr.aria-describedby="fieldName{{ i }}"
[readonly]="!cipher.edit && editMode" [readonly]="!cipher.edit && editMode"
/> />

View File

@@ -1,7 +1,7 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { AddEditCustomFieldsComponent as BaseAddEditCustomFieldsComponent } from "@bitwarden/angular/components/add-edit-custom-fields.component"; import { AddEditCustomFieldsComponent as BaseAddEditCustomFieldsComponent } from "@bitwarden/angular/components/add-edit-custom-fields.component";
import { EventService } from "@bitwarden/common/abstractions/event.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@Component({ @Component({
@@ -9,7 +9,7 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
templateUrl: "add-edit-custom-fields.component.html", templateUrl: "add-edit-custom-fields.component.html",
}) })
export class AddEditCustomFieldsComponent extends BaseAddEditCustomFieldsComponent { export class AddEditCustomFieldsComponent extends BaseAddEditCustomFieldsComponent {
constructor(i18nService: I18nService, eventService: EventService) { constructor(i18nService: I18nService, eventCollectionService: EventCollectionService) {
super(i18nService, eventService); super(i18nService, eventCollectionService);
} }
} }

View File

@@ -88,17 +88,17 @@
appA11yTitle="{{ 'checkPassword' | i18n }}" appA11yTitle="{{ 'checkPassword' | i18n }}"
(click)="checkPassword()" (click)="checkPassword()"
[appApiAction]="checkPasswordPromise" [appApiAction]="checkPasswordPromise"
[disabled]="checkPasswordBtn.loading" [disabled]="$any(checkPasswordBtn).loading"
*ngIf="cipher.viewPassword" *ngIf="cipher.viewPassword"
> >
<i <i
class="bwi bwi-lg bwi-check-circle" class="bwi bwi-lg bwi-check-circle"
[hidden]="checkPasswordBtn.loading" [hidden]="$any(checkPasswordBtn).loading"
aria-hidden="true" aria-hidden="true"
></i> ></i>
<i <i
class="bwi bwi-lg bwi-spinner bwi-spin" class="bwi bwi-lg bwi-spinner bwi-spin"
[hidden]="!checkPasswordBtn.loading" [hidden]="!$any(checkPasswordBtn).loading"
aria-hidden="true" aria-hidden="true"
></i> ></i>
</button> </button>
@@ -487,7 +487,7 @@
type="text" type="text"
name="Login.Uris[{{ i }}].Uri" name="Login.Uris[{{ i }}].Uri"
[(ngModel)]="u.uri" [(ngModel)]="u.uri"
[hidden]="u.showUriOptionsInput === true" [hidden]="$any(u).showUriOptionsInput === true"
placeholder="{{ 'ex' | i18n }} https://google.com" placeholder="{{ 'ex' | i18n }} https://google.com"
inputmode="url" inputmode="url"
appInputVerbatim appInputVerbatim
@@ -500,7 +500,7 @@
id="currentUris{{ i }}" id="currentUris{{ i }}"
name="Login.Uris[{{ i }}].CurrentUris" name="Login.Uris[{{ i }}].CurrentUris"
[(ngModel)]="u.uri" [(ngModel)]="u.uri"
[hidden]="!u.showCurrentUris" [hidden]="!$any(u).showCurrentUris"
> >
<option [ngValue]="null">-- {{ "select" | i18n }} --</option> <option [ngValue]="null">-- {{ "select" | i18n }} --</option>
<option *ngFor="let u of currentUris" [ngValue]="u">{{ u }}</option> <option *ngFor="let u of currentUris" [ngValue]="u">{{ u }}</option>
@@ -512,7 +512,9 @@
id="loginUriMatch{{ i }}" id="loginUriMatch{{ i }}"
name="Login.Uris[{{ i }}].Match" name="Login.Uris[{{ i }}].Match"
[(ngModel)]="u.match" [(ngModel)]="u.match"
[hidden]="u.showOptions === false || (u.showOptions == null && u.match == null)" [hidden]="
$any(u).showOptions === false || ($any(u).showOptions == null && u.match == null)
"
(change)="loginUriMatchChanged(u)" (change)="loginUriMatchChanged(u)"
> >
<option *ngFor="let o of uriMatchOptions" [ngValue]="o.value">{{ o.name }}</option> <option *ngFor="let o of uriMatchOptions" [ngValue]="o.value">{{ o.name }}</option>
@@ -526,7 +528,7 @@
appStopClick appStopClick
appA11yTitle="{{ 'toggleCurrentUris' | i18n }}" appA11yTitle="{{ 'toggleCurrentUris' | i18n }}"
(click)="toggleUriInput(u)" (click)="toggleUriInput(u)"
[attr.aria-pressed]="u.showCurrentUris === true" [attr.aria-pressed]="$any(u).showCurrentUris === true"
> >
<i aria-hidden="true" class="bwi bwi-lg bwi-list"></i> <i aria-hidden="true" class="bwi bwi-lg bwi-list"></i>
</button> </button>
@@ -536,7 +538,7 @@
appStopClick appStopClick
appA11yTitle="{{ 'toggleOptions' | i18n }}" appA11yTitle="{{ 'toggleOptions' | i18n }}"
(click)="toggleUriOptions(u)" (click)="toggleUriOptions(u)"
[attr.aria-pressed]="u.showOptions === true" [attr.aria-pressed]="$any(u).showOptions === true"
> >
<i class="bwi bwi-lg bwi-cog" aria-hidden="true"></i> <i class="bwi bwi-lg bwi-cog" aria-hidden="true"></i>
</button> </button>
@@ -695,7 +697,7 @@
<input <input
id="collection_{{ i }}" id="collection_{{ i }}"
type="checkbox" type="checkbox"
[(ngModel)]="c.checked" [(ngModel)]="$any(c).checked"
name="Collection[{{ i }}].Checked" name="Collection[{{ i }}].Checked"
/> />
</div> </div>
@@ -713,8 +715,11 @@
> >
<div class="row-main text-danger"> <div class="row-main text-danger">
<div class="icon text-danger" aria-hidden="true"> <div class="icon text-danger" aria-hidden="true">
<i class="bwi bwi-trash bwi-lg bwi-fw" [hidden]="deleteBtn.loading"></i> <i class="bwi bwi-trash bwi-lg bwi-fw" [hidden]="$any(deleteBtn).loading"></i>
<i class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw" [hidden]="!deleteBtn.loading"></i> <i
class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw"
[hidden]="!$any(deleteBtn).loading"
></i>
</div> </div>
<span>{{ "deleteItem" | i18n }}</span> <span>{{ "deleteItem" | i18n }}</span>
</div> </div>

View File

@@ -7,7 +7,7 @@ import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/com
import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/abstractions/collection.service"; import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
import { EventService } from "@bitwarden/common/abstractions/event.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction"; import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service"; import { LogService } from "@bitwarden/common/abstractions/log.service";
@@ -46,7 +46,7 @@ export class AddEditComponent extends BaseAddEditComponent {
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router, private router: Router,
private location: Location, private location: Location,
eventService: EventService, eventCollectionService: EventCollectionService,
policyService: PolicyService, policyService: PolicyService,
private popupUtilsService: PopupUtilsService, private popupUtilsService: PopupUtilsService,
organizationService: OrganizationService, organizationService: OrganizationService,
@@ -62,7 +62,7 @@ export class AddEditComponent extends BaseAddEditComponent {
stateService, stateService,
collectionService, collectionService,
messagingService, messagingService,
eventService, eventCollectionService,
policyService, policyService,
logService, logService,
passwordRepromptService, passwordRepromptService,

View File

@@ -37,16 +37,16 @@
(click)="delete(a)" (click)="delete(a)"
#deleteBtn #deleteBtn
[appApiAction]="deletePromises[a.id]" [appApiAction]="deletePromises[a.id]"
[disabled]="deleteBtn.loading" [disabled]="$any(deleteBtn).loading"
> >
<i <i
class="bwi bwi-trash bwi-lg bwi-fw" class="bwi bwi-trash bwi-lg bwi-fw"
[hidden]="deleteBtn.loading" [hidden]="$any(deleteBtn).loading"
aria-hidden="true" aria-hidden="true"
></i> ></i>
<i <i
class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw" class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw"
[hidden]="!deleteBtn.loading" [hidden]="!$any(deleteBtn).loading"
aria-hidden="true" aria-hidden="true"
></i> ></i>
</button> </button>

View File

@@ -33,7 +33,7 @@
<input <input
id="collection_{{ i }}" id="collection_{{ i }}"
type="checkbox" type="checkbox"
[(ngModel)]="c.checked" [(ngModel)]="$any(c).checked"
name="Collection[{{ i }}].Checked" name="Collection[{{ i }}].Checked"
/> />
</div> </div>

View File

@@ -200,7 +200,7 @@ export class CurrentTabComponent implements OnInit, OnDestroy {
} }
} }
private async load() { protected async load() {
this.isLoading = false; this.isLoading = false;
this.tab = await BrowserApi.getTabFromCurrentWindow(); this.tab = await BrowserApi.getTabFromCurrentWindow();
if (this.tab != null) { if (this.tab != null) {

View File

@@ -18,7 +18,7 @@ import { FolderView } from "@bitwarden/common/models/view/folder.view";
import { BrowserApi } from "../../browser/browserApi"; import { BrowserApi } from "../../browser/browserApi";
import { BrowserGroupingsComponentState } from "../../models/browserGroupingsComponentState"; import { BrowserGroupingsComponentState } from "../../models/browserGroupingsComponentState";
import { StateService } from "../../services/abstractions/state.service"; import { BrowserStateService } from "../../services/abstractions/browser-state.service";
import { VaultFilterService } from "../../services/vaultFilter.service"; import { VaultFilterService } from "../../services/vaultFilter.service";
import { PopupUtilsService } from "../services/popup-utils.service"; import { PopupUtilsService } from "../services/popup-utils.service";
@@ -83,7 +83,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
private platformUtilsService: PlatformUtilsService, private platformUtilsService: PlatformUtilsService,
private searchService: SearchService, private searchService: SearchService,
private location: Location, private location: Location,
private browserStateService: StateService, private browserStateService: BrowserStateService,
private vaultFilterService: VaultFilterService private vaultFilterService: VaultFilterService
) { ) {
this.noFolderListSize = 100; this.noFolderListSize = 100;
@@ -94,7 +94,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
this.showLeftHeader = !( this.showLeftHeader = !(
this.popupUtils.inSidebar(window) && this.platformUtilsService.isFirefox() this.popupUtils.inSidebar(window) && this.platformUtilsService.isFirefox()
); );
await this.browserStateService.setBrowserCipherComponentState(null); await this.browserStateService.setBrowserVaultItemsComponentState(null);
this.broadcasterService.subscribe(ComponentId, (message: any) => { this.broadcasterService.subscribe(ComponentId, (message: any) => {
this.ngZone.run(async () => { this.ngZone.run(async () => {
@@ -373,7 +373,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
} }
private async saveState() { private async saveState() {
this.state = { this.state = Object.assign(new BrowserGroupingsComponentState(), {
scrollY: this.popupUtils.getContentScrollY(window), scrollY: this.popupUtils.getContentScrollY(window),
searchText: this.searchText, searchText: this.searchText,
favoriteCiphers: this.favoriteCiphers, favoriteCiphers: this.favoriteCiphers,
@@ -385,7 +385,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
folders: this.folders, folders: this.folders,
collections: this.collections, collections: this.collections,
deletedCount: this.deletedCount, deletedCount: this.deletedCount,
}; });
await this.browserStateService.setBrowserGroupingComponentState(this.state); await this.browserStateService.setBrowserGroupingComponentState(this.state);
} }

View File

@@ -3,7 +3,7 @@ import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angula
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs/operators"; import { first } from "rxjs/operators";
import { CiphersComponent as BaseCiphersComponent } from "@bitwarden/angular/components/ciphers.component"; import { VaultItemsComponent as BaseVaultItemsComponent } from "@bitwarden/angular/components/vault-items.component";
import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-filter.model"; import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-filter.model";
import { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.service"; import { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
@@ -21,17 +21,17 @@ import { FolderView } from "@bitwarden/common/models/view/folder.view";
import { BrowserApi } from "../../browser/browserApi"; import { BrowserApi } from "../../browser/browserApi";
import { BrowserComponentState } from "../../models/browserComponentState"; import { BrowserComponentState } from "../../models/browserComponentState";
import { StateService } from "../../services/abstractions/state.service"; import { BrowserStateService } from "../../services/abstractions/browser-state.service";
import { VaultFilterService } from "../../services/vaultFilter.service"; import { VaultFilterService } from "../../services/vaultFilter.service";
import { PopupUtilsService } from "../services/popup-utils.service"; import { PopupUtilsService } from "../services/popup-utils.service";
const ComponentId = "CiphersComponent"; const ComponentId = "VaultItemsComponent";
@Component({ @Component({
selector: "app-vault-ciphers", selector: "app-vault-items",
templateUrl: "ciphers.component.html", templateUrl: "vault-items.component.html",
}) })
export class CiphersComponent extends BaseCiphersComponent implements OnInit, OnDestroy { export class VaultItemsComponent extends BaseVaultItemsComponent implements OnInit, OnDestroy {
groupingTitle: string; groupingTitle: string;
state: BrowserComponentState; state: BrowserComponentState;
folderId: string = null; folderId: string = null;
@@ -60,7 +60,7 @@ export class CiphersComponent extends BaseCiphersComponent implements OnInit, On
private ngZone: NgZone, private ngZone: NgZone,
private broadcasterService: BroadcasterService, private broadcasterService: BroadcasterService,
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
private stateService: StateService, private stateService: BrowserStateService,
private popupUtils: PopupUtilsService, private popupUtils: PopupUtilsService,
private i18nService: I18nService, private i18nService: I18nService,
private folderService: FolderService, private folderService: FolderService,
@@ -82,7 +82,7 @@ export class CiphersComponent extends BaseCiphersComponent implements OnInit, On
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.queryParams.pipe(first()).subscribe(async (params) => { this.route.queryParams.pipe(first()).subscribe(async (params) => {
if (this.applySavedState) { if (this.applySavedState) {
this.state = await this.stateService.getBrowserCipherComponentState(); this.state = await this.stateService.getBrowserVaultItemsComponentState();
if (this.state?.searchText) { if (this.state?.searchText) {
this.searchText = this.state.searchText; this.searchText = this.state.searchText;
} }
@@ -161,7 +161,7 @@ export class CiphersComponent extends BaseCiphersComponent implements OnInit, On
0 0
); );
} }
await this.stateService.setBrowserCipherComponentState(null); await this.stateService.setBrowserVaultItemsComponentState(null);
}); });
this.broadcasterService.subscribe(ComponentId, (message: any) => { this.broadcasterService.subscribe(ComponentId, (message: any) => {
@@ -291,6 +291,6 @@ export class CiphersComponent extends BaseCiphersComponent implements OnInit, On
scrollY: this.popupUtils.getContentScrollY(window, this.scrollingContainer), scrollY: this.popupUtils.getContentScrollY(window, this.scrollingContainer),
searchText: this.searchText, searchText: this.searchText,
}; };
await this.stateService.setBrowserCipherComponentState(this.state); await this.stateService.setBrowserVaultItemsComponentState(this.state);
} }
} }

View File

@@ -27,11 +27,7 @@
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
> >
<button <button appStopClick (click)="selectAllVaults()">
appStopClick
(click)="selectAllVaults()"
[ngClass]="{ active: !myVaultOnly && !selectOrganizationId }"
>
<i class="bwi bwi-fw bwi-filter" aria-hidden="true"></i> <i class="bwi bwi-fw bwi-filter" aria-hidden="true"></i>
&nbsp;{{ "allVaults" | i18n }} &nbsp;{{ "allVaults" | i18n }}
</button> </button>
@@ -60,7 +56,7 @@
<i <i
*ngIf="!organization.enabled" *ngIf="!organization.enabled"
class="bwi bwi-fw bwi-exclamation-triangle text-danger" class="bwi bwi-fw bwi-exclamation-triangle text-danger"
aria-label="{{ 'organizationIsDisabled' | i18n }}" attr.aria-label="{{ 'organizationIsDisabled' | i18n }}"
appA11yTitle="{{ 'organizationIsDisabled' | i18n }}" appA11yTitle="{{ 'organizationIsDisabled' | i18n }}"
></i> ></i>
</button> </button>

View File

@@ -1,14 +1,14 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { ViewCustomFieldsComponent as BaseViewCustomFieldsComponent } from "@bitwarden/angular/components/view-custom-fields.component"; import { ViewCustomFieldsComponent as BaseViewCustomFieldsComponent } from "@bitwarden/angular/components/view-custom-fields.component";
import { EventService } from "@bitwarden/common/abstractions/event.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
@Component({ @Component({
selector: "app-vault-view-custom-fields", selector: "app-vault-view-custom-fields",
templateUrl: "view-custom-fields.component.html", templateUrl: "view-custom-fields.component.html",
}) })
export class ViewCustomFieldsComponent extends BaseViewCustomFieldsComponent { export class ViewCustomFieldsComponent extends BaseViewCustomFieldsComponent {
constructor(eventService: EventService) { constructor(eventCollectionService: EventCollectionService) {
super(eventService); super(eventCollectionService);
} }
} }

View File

@@ -88,17 +88,17 @@
appA11yTitle="{{ 'checkPassword' | i18n }}" appA11yTitle="{{ 'checkPassword' | i18n }}"
(click)="checkPassword()" (click)="checkPassword()"
[appApiAction]="checkPasswordPromise" [appApiAction]="checkPasswordPromise"
[disabled]="checkPasswordBtn.loading" [disabled]="$any(checkPasswordBtn).loading"
*ngIf="cipher.viewPassword" *ngIf="cipher.viewPassword"
> >
<i <i
class="bwi bwi-lg bwi-check-circle" class="bwi bwi-lg bwi-check-circle"
[hidden]="checkPasswordBtn.loading" [hidden]="$any(checkPasswordBtn).loading"
aria-hidden="true" aria-hidden="true"
></i> ></i>
<i <i
class="bwi bwi-lg bwi-spinner bwi-spin" class="bwi bwi-lg bwi-spinner bwi-spin"
[hidden]="!checkPasswordBtn.loading" [hidden]="!$any(checkPasswordBtn).loading"
aria-hidden="true" aria-hidden="true"
></i> ></i>
</button> </button>
@@ -537,12 +537,12 @@
<small class="row-sub-label">{{ attachment.sizeName }}</small> <small class="row-sub-label">{{ attachment.sizeName }}</small>
<i <i
class="bwi bwi-download bwi-fw row-sub-icon" class="bwi bwi-download bwi-fw row-sub-icon"
*ngIf="!attachment.downloading" *ngIf="!$any(attachment).downloading"
aria-hidden="true" aria-hidden="true"
></i> ></i>
<i <i
class="bwi bwi-spinner bwi-fw bwi-spin row-sub-icon" class="bwi bwi-spinner bwi-fw bwi-spin row-sub-icon"
*ngIf="attachment.downloading" *ngIf="$any(attachment).downloading"
aria-hidden="true" aria-hidden="true"
></i> ></i>
</button> </button>

View File

@@ -9,7 +9,7 @@ import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.service"; import { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { EventService } from "@bitwarden/common/abstractions/event.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service"; import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service";
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction"; import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@@ -58,7 +58,7 @@ export class ViewComponent extends BaseViewComponent {
ngZone: NgZone, ngZone: NgZone,
changeDetectorRef: ChangeDetectorRef, changeDetectorRef: ChangeDetectorRef,
stateService: StateService, stateService: StateService,
eventService: EventService, eventCollectionService: EventCollectionService,
private autofillService: AutofillService, private autofillService: AutofillService,
private messagingService: MessagingService, private messagingService: MessagingService,
private popupUtilsService: PopupUtilsService, private popupUtilsService: PopupUtilsService,
@@ -80,7 +80,7 @@ export class ViewComponent extends BaseViewComponent {
broadcasterService, broadcasterService,
ngZone, ngZone,
changeDetectorRef, changeDetectorRef,
eventService, eventCollectionService,
apiService, apiService,
passwordRepromptService, passwordRepromptService,
logService, logService,

View File

@@ -8,7 +8,8 @@ import { BrowserComponentState } from "../../models/browserComponentState";
import { BrowserGroupingsComponentState } from "../../models/browserGroupingsComponentState"; import { BrowserGroupingsComponentState } from "../../models/browserGroupingsComponentState";
import { BrowserSendComponentState } from "../../models/browserSendComponentState"; import { BrowserSendComponentState } from "../../models/browserSendComponentState";
export abstract class StateService extends BaseStateServiceAbstraction<Account> { export abstract class BrowserStateService extends BaseStateServiceAbstraction<Account> {
abstract hasInSessionMemory(key: string): Promise<boolean>;
abstract getFromSessionMemory<T>(key: string, deserializer?: (obj: Jsonify<T>) => T): Promise<T>; abstract getFromSessionMemory<T>(key: string, deserializer?: (obj: Jsonify<T>) => T): Promise<T>;
abstract setInSessionMemory(key: string, value: any): Promise<void>; abstract setInSessionMemory(key: string, value: any): Promise<void>;
getBrowserGroupingComponentState: ( getBrowserGroupingComponentState: (
@@ -18,8 +19,8 @@ export abstract class StateService extends BaseStateServiceAbstraction<Account>
value: BrowserGroupingsComponentState, value: BrowserGroupingsComponentState,
options?: StorageOptions options?: StorageOptions
) => Promise<void>; ) => Promise<void>;
getBrowserCipherComponentState: (options?: StorageOptions) => Promise<BrowserComponentState>; getBrowserVaultItemsComponentState: (options?: StorageOptions) => Promise<BrowserComponentState>;
setBrowserCipherComponentState: ( setBrowserVaultItemsComponentState: (
value: BrowserComponentState, value: BrowserComponentState,
options?: StorageOptions options?: StorageOptions
) => Promise<void>; ) => Promise<void>;

View File

@@ -1,5 +1,5 @@
import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { EventService } from "@bitwarden/common/abstractions/event.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { LogService } from "@bitwarden/common/abstractions/log.service"; import { LogService } from "@bitwarden/common/abstractions/log.service";
import { TotpService } from "@bitwarden/common/abstractions/totp.service"; import { TotpService } from "@bitwarden/common/abstractions/totp.service";
import { CipherRepromptType } from "@bitwarden/common/enums/cipherRepromptType"; import { CipherRepromptType } from "@bitwarden/common/enums/cipherRepromptType";
@@ -14,7 +14,6 @@ import { BrowserApi } from "../browser/browserApi";
import AutofillField from "../models/autofillField"; import AutofillField from "../models/autofillField";
import AutofillPageDetails from "../models/autofillPageDetails"; import AutofillPageDetails from "../models/autofillPageDetails";
import AutofillScript from "../models/autofillScript"; import AutofillScript from "../models/autofillScript";
import { StateService } from "../services/abstractions/state.service";
import { import {
AutoFillOptions, AutoFillOptions,
@@ -22,6 +21,7 @@ import {
PageDetail, PageDetail,
FormData, FormData,
} from "./abstractions/autofill.service"; } from "./abstractions/autofill.service";
import { BrowserStateService } from "./abstractions/browser-state.service";
import { import {
AutoFillConstants, AutoFillConstants,
CreditCardAutoFillConstants, CreditCardAutoFillConstants,
@@ -39,9 +39,9 @@ export interface GenerateFillScriptOptions {
export default class AutofillService implements AutofillServiceInterface { export default class AutofillService implements AutofillServiceInterface {
constructor( constructor(
private cipherService: CipherService, private cipherService: CipherService,
private stateService: StateService, private stateService: BrowserStateService,
private totpService: TotpService, private totpService: TotpService,
private eventService: EventService, private eventCollectionService: EventCollectionService,
private logService: LogService private logService: LogService
) {} ) {}
@@ -148,7 +148,7 @@ export default class AutofillService implements AutofillServiceInterface {
}); });
if (didAutofill) { if (didAutofill) {
this.eventService.collect(EventType.Cipher_ClientAutofilled, options.cipher.id); this.eventCollectionService.collect(EventType.Cipher_ClientAutofilled, options.cipher.id);
if (totpPromise != null) { if (totpPromise != null) {
return await totpPromise; return await totpPromise;
} else { } else {

View File

@@ -4,12 +4,12 @@ import { Folder } from "@bitwarden/common/models/domain/folder";
import { FolderView } from "@bitwarden/common/models/view/folder.view"; import { FolderView } from "@bitwarden/common/models/view/folder.view";
import { FolderService as BaseFolderService } from "@bitwarden/common/services/folder/folder.service"; import { FolderService as BaseFolderService } from "@bitwarden/common/services/folder/folder.service";
import { browserSession, sessionSync } from "../../decorators/session-sync-observable"; import { browserSession, sessionSync } from "../decorators/session-sync-observable";
@browserSession @browserSession
export class FolderService extends BaseFolderService { export class BrowserFolderService extends BaseFolderService {
@sessionSync({ initializer: Folder.fromJSON, initializeAsArray: true }) @sessionSync({ initializer: Folder.fromJSON, initializeAs: "array" })
protected _folders: BehaviorSubject<Folder[]>; protected _folders: BehaviorSubject<Folder[]>;
@sessionSync({ initializer: FolderView.fromJSON, initializeAsArray: true }) @sessionSync({ initializer: FolderView.fromJSON, initializeAs: "array" })
protected _folderViews: BehaviorSubject<FolderView[]>; protected _folderViews: BehaviorSubject<FolderView[]>;
} }

View File

@@ -0,0 +1,12 @@
import { BehaviorSubject } from "rxjs";
import { Organization } from "@bitwarden/common/models/domain/organization";
import { OrganizationService } from "@bitwarden/common/services/organization/organization.service";
import { browserSession, sessionSync } from "../decorators/session-sync-observable";
@browserSession
export class BrowserOrganizationService extends OrganizationService {
@sessionSync({ initializer: Organization.fromJSON, initializeAs: "array" })
protected _organizations: BehaviorSubject<Organization[]>;
}

View File

@@ -0,0 +1,12 @@
import { BehaviorSubject } from "rxjs";
import { Policy } from "@bitwarden/common/models/domain/policy";
import { PolicyService } from "@bitwarden/common/services/policy/policy.service";
import { browserSession, sessionSync } from "../decorators/session-sync-observable";
@browserSession
export class BrowserPolicyService extends PolicyService {
@sessionSync({ ctor: Policy, initializeAs: "array" })
protected _policies: BehaviorSubject<Policy[]>;
}

View File

@@ -0,0 +1,11 @@
import { BehaviorSubject } from "rxjs";
import { AccountSettingsSettings } from "@bitwarden/common/models/domain/account";
import { SettingsService } from "@bitwarden/common/services/settings.service";
import { sessionSync } from "../decorators/session-sync-observable";
export class BrowserSettingsService extends SettingsService {
@sessionSync({ initializer: (obj: string[][]) => obj })
protected _settings: BehaviorSubject<AccountSettingsSettings>;
}

Some files were not shown because too many files have changed in this diff Show More