mirror of
https://github.com/bitwarden/browser
synced 2025-12-13 14:53:33 +00:00
Merge branch 'feature/SG-771-org-domain-claiming-web' into feature/SG-680-create-domain-verification-comp
This commit is contained in:
@@ -86,7 +86,7 @@
|
||||
"error",
|
||||
{
|
||||
"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/**/*",
|
||||
"from": "./libs/angular/**/*"
|
||||
@@ -94,10 +94,6 @@
|
||||
{
|
||||
"target": "./libs/common/**/*",
|
||||
"from": "./libs/node/**/*"
|
||||
},
|
||||
{
|
||||
"target": "./libs/common/**/*",
|
||||
"from": "./libs/electron/**/*"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -131,12 +127,6 @@
|
||||
"rules": {
|
||||
"no-restricted-imports": ["error", { "patterns": ["@bitwarden/node/*", "src/**/*"] }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["libs/electron/src/**/*.ts"],
|
||||
"rules": {
|
||||
"no-restricted-imports": ["error", { "patterns": ["@bitwarden/electron/*", "src/**/*"] }]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
53
.github/whitelist-capital-letters.txt
vendored
53
.github/whitelist-capital-letters.txt
vendored
@@ -4,14 +4,10 @@
|
||||
./apps/browser/src/safari/desktop/Base.lproj
|
||||
./apps/browser/src/services/vaultTimeout
|
||||
./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/src/abstractions/fileDownload
|
||||
./libs/common/src/abstractions/userVerification
|
||||
./libs/common/src/abstractions/vaultTimeout
|
||||
./libs/common/src/emailForwarders
|
||||
./libs/common/src/misc/logInStrategies
|
||||
./libs/common/src/services/userVerification
|
||||
./libs/common/src/services/vaultTimeout
|
||||
@@ -112,12 +108,6 @@
|
||||
./libs/common/src/enums/nativeMessagingVersion.ts
|
||||
./libs/common/src/enums/cipherRepromptType.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/globalStateFactory.ts
|
||||
./libs/common/src/factories/stateFactory.ts
|
||||
@@ -162,16 +152,6 @@
|
||||
./libs/common/src/services/bitwardenFileUpload.service.ts
|
||||
./libs/common/src/services/webCryptoFunction.service.ts
|
||||
./libs/common/src/interfaces/IEncrypted.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
|
||||
./LICENSE_BITWARDEN.txt
|
||||
./CONTRIBUTING.md
|
||||
@@ -186,42 +166,9 @@
|
||||
./apps/desktop/resources/appx/StoreLogo.png
|
||||
./apps/desktop/resources/appx/Wide310x150Logo.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/desktop_native/Cargo.toml
|
||||
./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/README.md
|
||||
./apps/browser/README.md
|
||||
|
||||
6
.github/workflows/build-browser.yml
vendored
6
.github/workflows/build-browser.yml
vendored
@@ -63,8 +63,8 @@ jobs:
|
||||
repo_url=https://github.com/$GITHUB_REPOSITORY.git
|
||||
adj_build_num=${GITHUB_SHA:0:7}
|
||||
|
||||
echo "::set-output name=repo_url::$repo_url"
|
||||
echo "::set-output name=adj_build_number::$adj_build_num"
|
||||
echo "repo_url=$repo_url" >> $GITHUB_OUTPUT
|
||||
echo "adj_build_number=$adj_build_num" >> $GITHUB_OUTPUT
|
||||
|
||||
|
||||
locales-test:
|
||||
@@ -183,7 +183,7 @@ jobs:
|
||||
name: dist-opera-MV3-${{ env._BUILD_NUMBER }}.zip
|
||||
path: apps/browser/dist/dist-opera-mv3.zip
|
||||
if-no-files-found: error
|
||||
|
||||
|
||||
- name: Upload Chrome artifact
|
||||
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0
|
||||
with:
|
||||
|
||||
2
.github/workflows/build-cli.yml
vendored
2
.github/workflows/build-cli.yml
vendored
@@ -62,7 +62,7 @@ jobs:
|
||||
id: retrieve-version
|
||||
run: |
|
||||
PKG_VERSION=$(jq -r .version package.json)
|
||||
echo "::set-output name=package_version::$PKG_VERSION"
|
||||
echo "package_version=$PKG_VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
|
||||
cli:
|
||||
|
||||
18
.github/workflows/build-desktop.yml
vendored
18
.github/workflows/build-desktop.yml
vendored
@@ -87,29 +87,29 @@ jobs:
|
||||
id: retrieve-version
|
||||
run: |
|
||||
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
|
||||
id: increment-version
|
||||
run: |
|
||||
BUILD_NUMBER=$(expr 3000 + $GITHUB_RUN_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
|
||||
id: release-channel
|
||||
run: |
|
||||
case "${{ steps.retrieve-version.outputs.package_version }}" in
|
||||
*"alpha"*)
|
||||
echo "::set-output name=channel::alpha"
|
||||
echo "channel=alpha" >> $GITHUB_OUTPUT
|
||||
echo "[!] We do not yet support 'alpha'"
|
||||
exit 1
|
||||
;;
|
||||
*"beta"*)
|
||||
echo "::set-output name=channel::beta"
|
||||
echo "channel=beta" >> $GITHUB_OUTPUT
|
||||
;;
|
||||
*)
|
||||
echo "::set-output name=channel::latest"
|
||||
echo "channel=latest" >> $GITHUB_OUTPUT
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -117,15 +117,15 @@ jobs:
|
||||
id: branch-check
|
||||
run: |
|
||||
if [[ $(git ls-remote --heads origin rc) ]]; then
|
||||
echo "::set-output name=rc_branch_exists::1"
|
||||
echo "rc_branch_exists=1" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "::set-output name=rc_branch_exists::0"
|
||||
echo "rc_branch_exists=0" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
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
|
||||
echo "::set-output name=hotfix_branch_exists::0"
|
||||
echo "hotfix_branch_exists=0" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
|
||||
|
||||
4
.github/workflows/build-web.yml
vendored
4
.github/workflows/build-web.yml
vendored
@@ -60,7 +60,7 @@ jobs:
|
||||
|
||||
- name: Get GitHub sha as version
|
||||
id: version
|
||||
run: echo "::set-output name=value::${GITHUB_SHA:0:7}"
|
||||
run: echo "value=${GITHUB_SHA:0:7}" >> $GITHUB_OUTPUT
|
||||
|
||||
build-artifacts:
|
||||
name: Build artifacts
|
||||
@@ -303,7 +303,7 @@ jobs:
|
||||
IMAGE_TAG=$IMAGE_TAG-$TAG_EXTENSION
|
||||
fi
|
||||
|
||||
echo "::set-output name=value::$IMAGE_TAG"
|
||||
echo "value=$IMAGE_TAG" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Tag image
|
||||
env:
|
||||
|
||||
10
.github/workflows/release-desktop-beta.yml
vendored
10
.github/workflows/release-desktop-beta.yml
vendored
@@ -60,22 +60,22 @@ jobs:
|
||||
run: |
|
||||
BUILD_NUMBER=$(expr 3000 + $GITHUB_RUN_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
|
||||
id: release-channel
|
||||
run: |
|
||||
case "${{ steps.version.outputs.version }}" in
|
||||
*"alpha"*)
|
||||
echo "::set-output name=channel::alpha"
|
||||
echo "channel=alpha" >> $GITHUB_OUTPUT
|
||||
echo "[!] We do not yet support 'alpha'"
|
||||
exit 1
|
||||
;;
|
||||
*"beta"*)
|
||||
echo "::set-output name=channel::beta"
|
||||
echo "channel=beta" >> $GITHUB_OUTPUT
|
||||
;;
|
||||
*)
|
||||
echo "::set-output name=channel::latest"
|
||||
echo "channel=latest" >> $GITHUB_OUTPUT
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -102,7 +102,7 @@ jobs:
|
||||
|
||||
git push -u origin $branch_name
|
||||
|
||||
echo "::set-output name=branch-name::$branch_name"
|
||||
echo "branch-name=$branch_name" >> $GITHUB_OUTPUT
|
||||
|
||||
linux:
|
||||
name: Linux Build
|
||||
|
||||
23
.github/workflows/release-desktop.yml
vendored
23
.github/workflows/release-desktop.yml
vendored
@@ -29,6 +29,16 @@ on:
|
||||
required: true
|
||||
default: true
|
||||
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:
|
||||
run:
|
||||
@@ -70,15 +80,15 @@ jobs:
|
||||
run: |
|
||||
case "${{ steps.version.outputs.version }}" in
|
||||
*"alpha"*)
|
||||
echo "::set-output name=channel::alpha"
|
||||
echo "channel=alpha" >> $GITHUB_OUTPUT
|
||||
echo "[!] We do not yet support 'alpha'"
|
||||
exit 1
|
||||
;;
|
||||
*"beta"*)
|
||||
echo "::set-output name=channel::beta"
|
||||
echo "channel=beta" >> $GITHUB_OUTPUT
|
||||
;;
|
||||
*)
|
||||
echo "::set-output name=channel::latest"
|
||||
echo "channel=latest" >> $GITHUB_OUTPUT
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -136,6 +146,7 @@ jobs:
|
||||
run: mv Bitwarden-${{ env.PKG_VERSION }}-universal.pkg Bitwarden-${{ env.PKG_VERSION }}-universal.pkg.archive
|
||||
|
||||
- name: Set staged rollout percentage
|
||||
if: ${{ github.event.inputs.electron_publish }}
|
||||
env:
|
||||
RELEASE_CHANNEL: ${{ steps.release-channel.outputs.channel }}
|
||||
ROLLOUT_PCT: ${{ github.event.inputs.rollout_percentage }}
|
||||
@@ -145,7 +156,7 @@ jobs:
|
||||
echo "stagingPercentage: ${ROLLOUT_PCT}" >> apps/desktop/artifacts/${RELEASE_CHANNEL}-mac.yml
|
||||
|
||||
- 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:
|
||||
AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.aws-electron-access-id }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.aws-electron-access-key }}
|
||||
@@ -159,7 +170,7 @@ jobs:
|
||||
--quiet
|
||||
|
||||
- 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:
|
||||
AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.r2-electron-access-id }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.r2-electron-access-key }}
|
||||
@@ -175,7 +186,7 @@ jobs:
|
||||
|
||||
- name: Create Release
|
||||
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:
|
||||
PKG_VERSION: ${{ steps.version.outputs.version }}
|
||||
RELEASE_CHANNEL: ${{ steps.release-channel.outputs.channel }}
|
||||
|
||||
38
.github/workflows/staged-rollout-desktop.yml
vendored
38
.github/workflows/staged-rollout-desktop.yml
vendored
@@ -54,20 +54,6 @@ jobs:
|
||||
r2-electron-bucket-name,
|
||||
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
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.r2-electron-access-id }}
|
||||
@@ -99,7 +85,7 @@ jobs:
|
||||
echo
|
||||
echo "If you want to pull a staged release because it hasn’t 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, \
|
||||
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
|
||||
fi
|
||||
|
||||
@@ -118,10 +104,14 @@ jobs:
|
||||
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/ \
|
||||
--include "latest*.yml" \
|
||||
--acl "public-read" \
|
||||
--quiet
|
||||
aws s3 cp latest.yml $AWS_S3_BUCKET_NAME/desktop/ \
|
||||
--acl "public-read"
|
||||
|
||||
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
|
||||
env:
|
||||
@@ -131,7 +121,11 @@ jobs:
|
||||
AWS_S3_BUCKET_NAME: ${{ steps.retrieve-secrets.outputs.r2-electron-bucket-name }}
|
||||
CF_ACCOUNT: ${{ steps.retrieve-secrets.outputs.cf-prod-account }}
|
||||
run: |
|
||||
aws s3 cp ./ $AWS_S3_BUCKET_NAME/desktop/ \
|
||||
--include "latest*.yml" \
|
||||
--quiet \
|
||||
aws s3 cp latest.yml $AWS_S3_BUCKET_NAME/desktop/ \
|
||||
--endpoint-url https://${CF_ACCOUNT}.r2.cloudflarestorage.com
|
||||
|
||||
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
|
||||
|
||||
2
.github/workflows/version-auto-bump.yml
vendored
2
.github/workflows/version-auto-bump.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
|
||||
NEW_VER=$CURR_MAJOR.$NEW_PATCH
|
||||
echo "New Version: $NEW_VER"
|
||||
echo "::set-output name=new-version::$NEW_VER"
|
||||
echo "new-version=$NEW_VER" >> $GITHUB_OUTPUT
|
||||
|
||||
trigger_version_bump:
|
||||
name: "Trigger desktop version bump workflow"
|
||||
|
||||
9
.github/workflows/version-bump.yml
vendored
9
.github/workflows/version-bump.yml
vendored
@@ -56,7 +56,7 @@ jobs:
|
||||
VERSION: ${{ github.event.inputs.version_number }}
|
||||
run: |
|
||||
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}
|
||||
|
||||
@@ -131,9 +131,9 @@ jobs:
|
||||
id: version-changed
|
||||
run: |
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
echo "::set-output name=changes_to_commit::TRUE"
|
||||
echo "changes_to_commit=TRUE" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "::set-output name=changes_to_commit::FALSE"
|
||||
echo "changes_to_commit=FALSE" >> $GITHUB_OUTPUT
|
||||
echo "No changes to commit!";
|
||||
fi
|
||||
|
||||
@@ -142,8 +142,7 @@ jobs:
|
||||
env:
|
||||
CLIENT: ${{ steps.branch.outputs.client }}
|
||||
VERSION: ${{ github.event.inputs.version_number }}
|
||||
run: |
|
||||
git commit -m "Bumped ${CLIENT} version to ${VERSION}" -a
|
||||
run: git commit -m "Bumped ${CLIENT} version to ${VERSION}" -a
|
||||
|
||||
- name: Push changes
|
||||
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
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/docs/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:
|
||||
|
||||
|
||||
@@ -19,4 +19,4 @@ The Bitwarden browser extension is written using the Web Extension API and Angul
|
||||
|
||||
## 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/docs/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.
|
||||
|
||||
@@ -482,7 +482,7 @@
|
||||
"message": "الاسم مطلوب."
|
||||
},
|
||||
"addedFolder": {
|
||||
"message": "Folder added"
|
||||
"message": "أُضيف المجلد"
|
||||
},
|
||||
"changeMasterPass": {
|
||||
"message": "تغيير كلمة المرور الرئيسية"
|
||||
@@ -494,7 +494,7 @@
|
||||
"message": "تسجيل الدخول بخطوتين يجعل حسابك أكثر أمنا من خلال مطالبتك بالتحقق من تسجيل الدخول باستخدام جهاز آخر مثل مفتاح الأمان، تطبيق المصادقة، الرسائل القصيرة، المكالمة الهاتفية، أو البريد الإلكتروني. يمكن تمكين تسجيل الدخول بخطوتين على خزنة الويب bitwarden.com. هل تريد زيارة الموقع الآن؟"
|
||||
},
|
||||
"editedFolder": {
|
||||
"message": "Folder saved"
|
||||
"message": "حُفظ المجلد"
|
||||
},
|
||||
"deleteFolderConfirmation": {
|
||||
"message": "هل أنت متأكد من حذف هذا المجلّد؟"
|
||||
@@ -521,7 +521,7 @@
|
||||
"message": "عنوان الـ URI"
|
||||
},
|
||||
"uriPosition": {
|
||||
"message": "URI $POSITION$",
|
||||
"message": "رابط $POSITION$",
|
||||
"description": "A listing of URIs. Ex: URI 1, URI 2, URI 3, etc.",
|
||||
"placeholders": {
|
||||
"position": {
|
||||
@@ -571,13 +571,13 @@
|
||||
"description": "This is the folder for uncategorized items"
|
||||
},
|
||||
"enableAddLoginNotification": {
|
||||
"message": "Ask to add login"
|
||||
"message": "اطلب إضافة تسجيل الدخول"
|
||||
},
|
||||
"addLoginNotificationDesc": {
|
||||
"message": "Ask to add an item if one isn't found in your vault."
|
||||
"message": "اطلب إضافة عنصر إذا لم يُعثر عليه في خزنتك."
|
||||
},
|
||||
"showCardsCurrentTab": {
|
||||
"message": "Show cards on Tab page"
|
||||
"message": "أظهر البطاقات في صفحة التبويبات"
|
||||
},
|
||||
"showCardsCurrentTabDesc": {
|
||||
"message": "List card items on the Tab page for easy auto-fill."
|
||||
|
||||
@@ -149,7 +149,7 @@
|
||||
"message": "Změnit hlavní heslo"
|
||||
},
|
||||
"fingerprintPhrase": {
|
||||
"message": "Fráze otisku účtu",
|
||||
"message": "Unikátní přístupová fráze",
|
||||
"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": {
|
||||
@@ -606,7 +606,7 @@
|
||||
"message": "Zeptat se na aktualizaci existujícího přihlášení"
|
||||
},
|
||||
"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": {
|
||||
"message": "Chcete aktualizovat toto heslo v Bitwarden?"
|
||||
@@ -618,7 +618,7 @@
|
||||
"message": "Zobrazit v kontextovém menu"
|
||||
},
|
||||
"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": {
|
||||
"message": "Výchozí zjišťování shody URI",
|
||||
@@ -1043,10 +1043,10 @@
|
||||
"message": "Zobrazit rozeznatelný obrázek vedle každého přihlášení."
|
||||
},
|
||||
"enableBadgeCounter": {
|
||||
"message": "Show badge counter"
|
||||
"message": "Zobrazovat počet uložených přihlašovacích údajů na stránce"
|
||||
},
|
||||
"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": {
|
||||
"message": "Jméno držitele karty"
|
||||
@@ -1632,7 +1632,7 @@
|
||||
"message": "Odstraněné heslo"
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"exportingPersonalVaultTitle": {
|
||||
"message": "Exporting individual vault"
|
||||
"message": "Export mého trezoru"
|
||||
},
|
||||
"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": {
|
||||
"email": {
|
||||
"content": "$1",
|
||||
@@ -1970,16 +1970,16 @@
|
||||
"message": "API klíč"
|
||||
},
|
||||
"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": {
|
||||
"message": "Vyžadováno prémiové předplatné"
|
||||
},
|
||||
"organizationIsDisabled": {
|
||||
"message": "Organization suspended."
|
||||
"message": "Organizace je deaktivována."
|
||||
},
|
||||
"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": {
|
||||
"message": "Mir"
|
||||
@@ -2000,19 +2000,19 @@
|
||||
"message": "Klikněte zde"
|
||||
},
|
||||
"environmentEditedReset": {
|
||||
"message": "to reset to pre-configured settings"
|
||||
"message": "pro obnovení do přednastavených nastavení"
|
||||
},
|
||||
"serverVersion": {
|
||||
"message": "Verze serveru"
|
||||
},
|
||||
"selfHosted": {
|
||||
"message": "Self-hosted"
|
||||
"message": "Vlastní hosting"
|
||||
},
|
||||
"thirdParty": {
|
||||
"message": "Third-party"
|
||||
"message": "Tretí strana"
|
||||
},
|
||||
"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": {
|
||||
"servername": {
|
||||
"content": "$1",
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
"message": "My vault"
|
||||
},
|
||||
"allVaults": {
|
||||
"message": "All Vaults"
|
||||
"message": "All vaults"
|
||||
},
|
||||
"tools": {
|
||||
"message": "Tools"
|
||||
@@ -92,13 +92,13 @@
|
||||
"message": "Auto-fill"
|
||||
},
|
||||
"generatePasswordCopied": {
|
||||
"message": "Generate Password (and Copy)"
|
||||
"message": "Generate password (copied)"
|
||||
},
|
||||
"copyElementIdentifier": {
|
||||
"message": "Copy Custom Field Name"
|
||||
"message": "Copy custom field name"
|
||||
},
|
||||
"noMatchingLogins": {
|
||||
"message": "No matching logins."
|
||||
"message": "No matching logins"
|
||||
},
|
||||
"unlockVaultMenu": {
|
||||
"message": "Unlock your vault"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"message": "Bitwarden"
|
||||
},
|
||||
"extName": {
|
||||
"message": "Bitwarden – Ilmainen salasananhallinta",
|
||||
"message": "Bitwarden - Free Password Manager",
|
||||
"description": "Extension name, MUST be less than 40 characters (Safari restriction)"
|
||||
},
|
||||
"extDesc": {
|
||||
|
||||
@@ -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."
|
||||
},
|
||||
"showCardsCurrentTab": {
|
||||
"message": "Show cards on Tab page"
|
||||
"message": "Afficher les cartes sur la page de l'onglet"
|
||||
},
|
||||
"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": {
|
||||
"message": "Show identities on Tab page"
|
||||
"message": "Afficher les identités sur la page Onglet courant"
|
||||
},
|
||||
"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": {
|
||||
"message": "Effacer le presse-papiers",
|
||||
@@ -618,7 +618,7 @@
|
||||
"message": "Afficher les options du menu contextuel"
|
||||
},
|
||||
"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": {
|
||||
"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."
|
||||
},
|
||||
"enableAutoBiometricsPrompt": {
|
||||
"message": "Ask for biometrics on launch"
|
||||
"message": "Demander la biométrie au lancement"
|
||||
},
|
||||
"premiumRequired": {
|
||||
"message": "Version Premium requise"
|
||||
@@ -1043,10 +1043,10 @@
|
||||
"message": "Afficher une image reconnaissable à côté de chaque identifiant."
|
||||
},
|
||||
"enableBadgeCounter": {
|
||||
"message": "Show badge counter"
|
||||
"message": "Afficher le compteur de badge"
|
||||
},
|
||||
"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": {
|
||||
"message": "Nom du titulaire de la carte"
|
||||
@@ -2000,19 +2000,19 @@
|
||||
"message": "Cliquez ici"
|
||||
},
|
||||
"environmentEditedReset": {
|
||||
"message": "to reset to pre-configured settings"
|
||||
"message": "pour réinitialiser aux paramètres par défaut"
|
||||
},
|
||||
"serverVersion": {
|
||||
"message": "Server version"
|
||||
"message": "Version du serveur"
|
||||
},
|
||||
"selfHosted": {
|
||||
"message": "Auto-hébergé"
|
||||
},
|
||||
"thirdParty": {
|
||||
"message": "Third-party"
|
||||
"message": "Tierce partie"
|
||||
},
|
||||
"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": {
|
||||
"servername": {
|
||||
"content": "$1",
|
||||
@@ -2021,7 +2021,7 @@
|
||||
}
|
||||
},
|
||||
"lastSeenOn": {
|
||||
"message": "last seen on: $DATE$",
|
||||
"message": "vu pour la dernière fois le : $DATE$",
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"content": "$1",
|
||||
@@ -2030,10 +2030,10 @@
|
||||
}
|
||||
},
|
||||
"loginWithMasterPassword": {
|
||||
"message": "Log in with master password"
|
||||
"message": "Connectez-vous avec le mot de passe maître"
|
||||
},
|
||||
"loggingInAs": {
|
||||
"message": "Logging in as"
|
||||
"message": "Connexion en tant que"
|
||||
},
|
||||
"notYou": {
|
||||
"message": "Ce n'est pas vous ?"
|
||||
@@ -2042,6 +2042,6 @@
|
||||
"message": "Êtes-vous nouveau ici ?"
|
||||
},
|
||||
"rememberEmail": {
|
||||
"message": "Remember email"
|
||||
"message": "Se souvenir de l'e-mail"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2042,6 +2042,6 @@
|
||||
"message": "Jesteś tu nowy(a)?"
|
||||
},
|
||||
"rememberEmail": {
|
||||
"message": "Zapamiętaj email"
|
||||
"message": "Zapamiętaj adres e-mail"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,13 +53,13 @@
|
||||
"message": "Zavihek"
|
||||
},
|
||||
"vault": {
|
||||
"message": "Sef"
|
||||
"message": "Trezor"
|
||||
},
|
||||
"myVault": {
|
||||
"message": "Moj trezor"
|
||||
},
|
||||
"allVaults": {
|
||||
"message": "All vaults"
|
||||
"message": "Vsi trezorji"
|
||||
},
|
||||
"tools": {
|
||||
"message": "Orodja"
|
||||
@@ -101,7 +101,7 @@
|
||||
"message": "Nobenih ujemajočih prijav."
|
||||
},
|
||||
"unlockVaultMenu": {
|
||||
"message": "Unlock your vault"
|
||||
"message": "Odkleni svoj trezor"
|
||||
},
|
||||
"loginToVaultMenu": {
|
||||
"message": "Log in to your vault"
|
||||
@@ -131,10 +131,10 @@
|
||||
"message": "Send a verification code to your email"
|
||||
},
|
||||
"sendCode": {
|
||||
"message": "Send code"
|
||||
"message": "Pošlji kodo"
|
||||
},
|
||||
"codeSent": {
|
||||
"message": "Code sent"
|
||||
"message": "Koda poslana"
|
||||
},
|
||||
"verificationCode": {
|
||||
"message": "Verifikacijska koda"
|
||||
@@ -242,10 +242,10 @@
|
||||
"message": "Lowercase (a-z)"
|
||||
},
|
||||
"numbers": {
|
||||
"message": "Numbers (0-9)"
|
||||
"message": "Številke (0-9)"
|
||||
},
|
||||
"specialCharacters": {
|
||||
"message": "Special characters (!@#$%^&*)"
|
||||
"message": "Posebni znaki (!@#$%^&*)"
|
||||
},
|
||||
"numWords": {
|
||||
"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."
|
||||
},
|
||||
"webAuthnNewTabOpen": {
|
||||
"message": "Open new tab"
|
||||
"message": "Odpri nov zavihek"
|
||||
},
|
||||
"webAuthnAuthenticate": {
|
||||
"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)."
|
||||
},
|
||||
"twoStepOptions": {
|
||||
"message": "Two-step login options"
|
||||
"message": "Možnosti dvostopenjske prijave"
|
||||
},
|
||||
"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": {
|
||||
"message": "Koda za obnovitev"
|
||||
@@ -927,7 +927,7 @@
|
||||
"message": "Specify the base URL of your on-premises hosted Bitwarden installation."
|
||||
},
|
||||
"customEnvironment": {
|
||||
"message": "Custom environment"
|
||||
"message": "Okolje po meri"
|
||||
},
|
||||
"customEnvironmentFooter": {
|
||||
"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"
|
||||
},
|
||||
"commandLockVaultDesc": {
|
||||
"message": "Lock the vault"
|
||||
"message": "Zakleni trezor"
|
||||
},
|
||||
"privateModeWarning": {
|
||||
"message": "Private mode support is experimental and some features are limited."
|
||||
@@ -1002,10 +1002,10 @@
|
||||
"message": "Custom fields"
|
||||
},
|
||||
"copyValue": {
|
||||
"message": "Copy value"
|
||||
"message": "Kopiraj vrednost"
|
||||
},
|
||||
"value": {
|
||||
"message": "Value"
|
||||
"message": "Vrednost"
|
||||
},
|
||||
"newCustomField": {
|
||||
"message": "New custom field"
|
||||
@@ -1020,7 +1020,7 @@
|
||||
"message": "Skrito"
|
||||
},
|
||||
"cfTypeBoolean": {
|
||||
"message": "Boolean"
|
||||
"message": "Logična vrednost"
|
||||
},
|
||||
"cfTypeLinked": {
|
||||
"message": "Linked",
|
||||
@@ -1133,7 +1133,7 @@
|
||||
"message": "Priimek"
|
||||
},
|
||||
"fullName": {
|
||||
"message": "Full name"
|
||||
"message": "Polno ime"
|
||||
},
|
||||
"identityName": {
|
||||
"message": "Ime identitete"
|
||||
@@ -1190,22 +1190,22 @@
|
||||
"message": "Prijave"
|
||||
},
|
||||
"typeSecureNote": {
|
||||
"message": "Secure note"
|
||||
"message": "Varni zapisek"
|
||||
},
|
||||
"typeCard": {
|
||||
"message": "Card"
|
||||
"message": "Kartica"
|
||||
},
|
||||
"typeIdentity": {
|
||||
"message": "Identity"
|
||||
"message": "Identiteta"
|
||||
},
|
||||
"passwordHistory": {
|
||||
"message": "Password history"
|
||||
"message": "Zgodovina gesel"
|
||||
},
|
||||
"back": {
|
||||
"message": "Back"
|
||||
"message": "Nazaj"
|
||||
},
|
||||
"collections": {
|
||||
"message": "Collections"
|
||||
"message": "Zbirke"
|
||||
},
|
||||
"favorites": {
|
||||
"message": "Priljubljeno"
|
||||
@@ -1226,7 +1226,7 @@
|
||||
"message": "Prijave"
|
||||
},
|
||||
"secureNotes": {
|
||||
"message": "Secure notes"
|
||||
"message": "Varni zapiski"
|
||||
},
|
||||
"clear": {
|
||||
"message": "Počisti",
|
||||
@@ -1285,15 +1285,15 @@
|
||||
"description": "Toggle the display of the URIs of the currently open tabs in the browser."
|
||||
},
|
||||
"currentUri": {
|
||||
"message": "Current URI",
|
||||
"message": "Trenutni URI",
|
||||
"description": "The URI of one of the current open tabs in the browser."
|
||||
},
|
||||
"organization": {
|
||||
"message": "Organization",
|
||||
"message": "Organizacija",
|
||||
"description": "An entity of multiple related people (ex. a team or business organization)."
|
||||
},
|
||||
"types": {
|
||||
"message": "Types"
|
||||
"message": "Tipi"
|
||||
},
|
||||
"allItems": {
|
||||
"message": "All items"
|
||||
@@ -1302,13 +1302,13 @@
|
||||
"message": "There are no passwords to list."
|
||||
},
|
||||
"remove": {
|
||||
"message": "Remove"
|
||||
"message": "Odstrani"
|
||||
},
|
||||
"default": {
|
||||
"message": "Default"
|
||||
"message": "Privzeto"
|
||||
},
|
||||
"dateUpdated": {
|
||||
"message": "Updated",
|
||||
"message": "Posodobljeno",
|
||||
"description": "ex. Date this item was updated"
|
||||
},
|
||||
"dateCreated": {
|
||||
@@ -1329,25 +1329,25 @@
|
||||
"message": "There are no collections to list."
|
||||
},
|
||||
"ownership": {
|
||||
"message": "Ownership"
|
||||
"message": "Lastništvo"
|
||||
},
|
||||
"whoOwnsThisItem": {
|
||||
"message": "Who owns this item?"
|
||||
},
|
||||
"strong": {
|
||||
"message": "Strong",
|
||||
"message": "Močno",
|
||||
"description": "ex. A strong password. Scale: Weak -> Good -> Strong"
|
||||
},
|
||||
"good": {
|
||||
"message": "Good",
|
||||
"message": "Dobro",
|
||||
"description": "ex. A good password. Scale: Weak -> Good -> Strong"
|
||||
},
|
||||
"weak": {
|
||||
"message": "Weak",
|
||||
"message": "Šibko",
|
||||
"description": "ex. A weak password. Scale: Weak -> Good -> Strong"
|
||||
},
|
||||
"weakMasterPassword": {
|
||||
"message": "Weak master password"
|
||||
"message": "Šibko glavno geslo"
|
||||
},
|
||||
"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?"
|
||||
@@ -1357,19 +1357,19 @@
|
||||
"description": "PIN code. Ex. The short code (often numeric) that you use to unlock a device."
|
||||
},
|
||||
"unlockWithPin": {
|
||||
"message": "Unlock with PIN"
|
||||
"message": "Odkleni s PIN kodo"
|
||||
},
|
||||
"setYourPinCode": {
|
||||
"message": "Set your PIN code for unlocking Bitwarden. Your PIN settings will be reset if you ever fully log out of the application."
|
||||
},
|
||||
"pinRequired": {
|
||||
"message": "PIN code is required."
|
||||
"message": "Potrebna je PIN koda."
|
||||
},
|
||||
"invalidPin": {
|
||||
"message": "Invalid PIN code."
|
||||
"message": "Nepravilna PIN koda."
|
||||
},
|
||||
"unlockWithBiometrics": {
|
||||
"message": "Unlock with biometrics"
|
||||
"message": "Prijava z biometriko"
|
||||
},
|
||||
"awaitDesktop": {
|
||||
"message": "Awaiting confirmation from desktop"
|
||||
@@ -1387,7 +1387,7 @@
|
||||
"message": "Clone item"
|
||||
},
|
||||
"clone": {
|
||||
"message": "Clone"
|
||||
"message": "Kloniraj"
|
||||
},
|
||||
"passwordGeneratorPolicyInEffect": {
|
||||
"message": "One or more organization policies are affecting your generator settings."
|
||||
@@ -1396,15 +1396,15 @@
|
||||
"message": "Vault timeout action"
|
||||
},
|
||||
"lock": {
|
||||
"message": "Lock",
|
||||
"message": "Zakleni",
|
||||
"description": "Verb form: to make secure or inaccesible by"
|
||||
},
|
||||
"trash": {
|
||||
"message": "Trash",
|
||||
"message": "Koš",
|
||||
"description": "Noun: a special folder to hold deleted items"
|
||||
},
|
||||
"searchTrash": {
|
||||
"message": "Search trash"
|
||||
"message": "Preišči koš"
|
||||
},
|
||||
"permanentlyDeleteItem": {
|
||||
"message": "Permanently delete item"
|
||||
@@ -1440,7 +1440,7 @@
|
||||
"message": "Item auto-filled "
|
||||
},
|
||||
"setMasterPassword": {
|
||||
"message": "Set master password"
|
||||
"message": "Nastavi glavno geslo"
|
||||
},
|
||||
"masterPasswordPolicyInEffect": {
|
||||
"message": "One or more organization policies require your master password to meet the following requirements:"
|
||||
@@ -1494,13 +1494,13 @@
|
||||
"message": "Terms of Service"
|
||||
},
|
||||
"privacyPolicy": {
|
||||
"message": "Privacy Policy"
|
||||
"message": "Pravilnik o zasebnosti"
|
||||
},
|
||||
"hintEqualsPassword": {
|
||||
"message": "Your password hint cannot be the same as your password."
|
||||
},
|
||||
"ok": {
|
||||
"message": "Ok"
|
||||
"message": "V redu"
|
||||
},
|
||||
"desktopSyncVerificationTitle": {
|
||||
"message": "Desktop sync verification"
|
||||
@@ -1584,7 +1584,7 @@
|
||||
}
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"searchSends": {
|
||||
@@ -1596,10 +1596,10 @@
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"sendTypeText": {
|
||||
"message": "Text"
|
||||
"message": "Besedilo"
|
||||
},
|
||||
"sendTypeFile": {
|
||||
"message": "File"
|
||||
"message": "Datoteka"
|
||||
},
|
||||
"allSends": {
|
||||
"message": "All Sends",
|
||||
@@ -1610,7 +1610,7 @@
|
||||
"description": "This text will be displayed after a Send has been accessed the maximum amount of times."
|
||||
},
|
||||
"expired": {
|
||||
"message": "Expired"
|
||||
"message": "Poteklo"
|
||||
},
|
||||
"pendingDeletion": {
|
||||
"message": "Pending deletion"
|
||||
@@ -1626,7 +1626,7 @@
|
||||
"message": "Remove Password"
|
||||
},
|
||||
"delete": {
|
||||
"message": "Delete"
|
||||
"message": "Izbriši"
|
||||
},
|
||||
"removedPassword": {
|
||||
"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."
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"disabled": {
|
||||
"message": "Disabled"
|
||||
"message": "Onemogočeno"
|
||||
},
|
||||
"removePasswordConfirmation": {
|
||||
"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."
|
||||
},
|
||||
"oneDay": {
|
||||
"message": "1 day"
|
||||
"message": "1 dan"
|
||||
},
|
||||
"days": {
|
||||
"message": "$DAYS$ days",
|
||||
"message": "$DAYS$ dni",
|
||||
"placeholders": {
|
||||
"days": {
|
||||
"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."
|
||||
},
|
||||
"hours": {
|
||||
"message": "Hours"
|
||||
"message": "Ur"
|
||||
},
|
||||
"minutes": {
|
||||
"message": "Minutes"
|
||||
"message": "Minut"
|
||||
},
|
||||
"vaultTimeoutPolicyInEffect": {
|
||||
"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": {
|
||||
"message": "Error"
|
||||
"message": "Napaka"
|
||||
},
|
||||
"regenerateUsername": {
|
||||
"message": "Regenerate username"
|
||||
@@ -1936,13 +1936,13 @@
|
||||
"message": "Use your domain's configured catch-all inbox."
|
||||
},
|
||||
"random": {
|
||||
"message": "Random"
|
||||
"message": "Naključno"
|
||||
},
|
||||
"randomWord": {
|
||||
"message": "Random word"
|
||||
"message": "Naključna beseda"
|
||||
},
|
||||
"websiteName": {
|
||||
"message": "Website name"
|
||||
"message": "Ime spletne strani"
|
||||
},
|
||||
"whatWouldYouLikeToGenerate": {
|
||||
"message": "What would you like to generate?"
|
||||
@@ -1997,13 +1997,13 @@
|
||||
"message": "Settings have been edited"
|
||||
},
|
||||
"environmentEditedClick": {
|
||||
"message": "Click here"
|
||||
"message": "Kliknite tukaj"
|
||||
},
|
||||
"environmentEditedReset": {
|
||||
"message": "to reset to pre-configured settings"
|
||||
},
|
||||
"serverVersion": {
|
||||
"message": "Server version"
|
||||
"message": "Verzija strežnika"
|
||||
},
|
||||
"selfHosted": {
|
||||
"message": "Self-hosted"
|
||||
@@ -2036,12 +2036,12 @@
|
||||
"message": "Logging in as"
|
||||
},
|
||||
"notYou": {
|
||||
"message": "Not you?"
|
||||
"message": "Niste vi?"
|
||||
},
|
||||
"newAroundHere": {
|
||||
"message": "New around here?"
|
||||
},
|
||||
"rememberEmail": {
|
||||
"message": "Remember email"
|
||||
"message": "Zapomni si e-pošto"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2012,7 +2012,7 @@
|
||||
"message": "Трећа страна"
|
||||
},
|
||||
"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": {
|
||||
"servername": {
|
||||
"content": "$1",
|
||||
|
||||
66
apps/browser/src/alarms/alarm-state.ts
Normal file
66
apps/browser/src/alarms/alarm-state.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
26
apps/browser/src/alarms/on-alarm-listener.ts
Normal file
26
apps/browser/src/alarms/on-alarm-listener.ts
Normal 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:
|
||||
}
|
||||
});
|
||||
};
|
||||
29
apps/browser/src/alarms/register-alarms.ts
Normal file
29
apps/browser/src/alarms/register-alarms.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { onAlarmListener } from "./alarms/on-alarm-listener";
|
||||
import { registerAlarms } from "./alarms/register-alarms";
|
||||
import MainBackground from "./background/main.background";
|
||||
import { BrowserApi } from "./browser/browserApi";
|
||||
import { ClearClipboard } from "./clipboard";
|
||||
import { onCommandListener } from "./listeners/onCommandListener";
|
||||
import { onInstallListener } from "./listeners/onInstallListener";
|
||||
import { UpdateBadge } from "./listeners/update-badge";
|
||||
@@ -9,13 +10,12 @@ const manifestV3MessageListeners: ((
|
||||
serviceCache: Record<string, unknown>,
|
||||
message: { command: string }
|
||||
) => void | Promise<void>)[] = [UpdateBadge.messageListener];
|
||||
type AlarmAction = (executionTime: Date, serviceCache: Record<string, unknown>) => void;
|
||||
|
||||
const AlarmActions: AlarmAction[] = [ClearClipboard.run];
|
||||
|
||||
if (BrowserApi.manifestVersion === 3) {
|
||||
chrome.commands.onCommand.addListener(onCommandListener);
|
||||
chrome.runtime.onInstalled.addListener(onInstallListener);
|
||||
chrome.alarms.onAlarm.addListener(onAlarmListener);
|
||||
registerAlarms();
|
||||
chrome.tabs.onActivated.addListener(UpdateBadge.tabsOnActivatedListener);
|
||||
chrome.tabs.onReplaced.addListener(UpdateBadge.tabsOnReplacedListener);
|
||||
chrome.tabs.onUpdated.addListener(UpdateBadge.tabsOnUpdatedListener);
|
||||
@@ -26,14 +26,6 @@ if (BrowserApi.manifestVersion === 3) {
|
||||
listener(serviceCache, message);
|
||||
});
|
||||
});
|
||||
chrome.alarms.onAlarm.addListener((_alarm) => {
|
||||
const executionTime = new Date();
|
||||
const serviceCache = {};
|
||||
|
||||
for (const alarmAction of AlarmActions) {
|
||||
alarmAction(executionTime, serviceCache);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const bitwardenMain = ((window as any).bitwardenMain = new MainBackground());
|
||||
bitwardenMain.bootstrap().then(() => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.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
|
||||
|
||||
@@ -12,7 +12,7 @@ export default class IdleBackground {
|
||||
|
||||
constructor(
|
||||
private vaultTimeoutService: VaultTimeoutService,
|
||||
private stateService: StateService,
|
||||
private stateService: BrowserStateService,
|
||||
private notificationsService: NotificationsService
|
||||
) {
|
||||
this.idle = chrome.idle || (browser != null ? browser.idle : null);
|
||||
|
||||
@@ -61,14 +61,11 @@ import { FolderApiService } from "@bitwarden/common/services/folder/folder-api.s
|
||||
import { KeyConnectorService } from "@bitwarden/common/services/keyConnector.service";
|
||||
import { MemoryStorageService } from "@bitwarden/common/services/memoryStorage.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 { 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 { SearchService } from "@bitwarden/common/services/search.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 { SyncService } from "@bitwarden/common/services/sync/sync.service";
|
||||
import { SyncNotifierService } from "@bitwarden/common/services/sync/syncNotifier.service";
|
||||
@@ -89,19 +86,22 @@ import { UpdateBadge } from "../listeners/update-badge";
|
||||
import { Account } from "../models/account";
|
||||
import { PopupUtilsService } from "../popup/services/popup-utils.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 { 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 BrowserLocalStorageService from "../services/browserLocalStorage.service";
|
||||
import BrowserMessagingService from "../services/browserMessaging.service";
|
||||
import BrowserMessagingPrivateModeBackgroundService from "../services/browserMessagingPrivateModeBackground.service";
|
||||
import BrowserPlatformUtilsService from "../services/browserPlatformUtils.service";
|
||||
import { FolderService } from "../services/folders/folder.service";
|
||||
import I18nService from "../services/i18n.service";
|
||||
import { KeyGenerationService } from "../services/keyGeneration.service";
|
||||
import { LocalBackedSessionStorageService } from "../services/localBackedSessionStorage.service";
|
||||
import { StateService } from "../services/state.service";
|
||||
import { VaultFilterService } from "../services/vaultFilter.service";
|
||||
import VaultTimeoutService from "../services/vaultTimeout/vaultTimeout.service";
|
||||
|
||||
@@ -227,7 +227,7 @@ export default class MainBackground {
|
||||
this.secureStorageService,
|
||||
new StateFactory(GlobalState, Account)
|
||||
);
|
||||
this.stateService = new StateService(
|
||||
this.stateService = new BrowserStateService(
|
||||
this.storageService,
|
||||
this.secureStorageService,
|
||||
this.memoryStorageService,
|
||||
@@ -282,7 +282,7 @@ export default class MainBackground {
|
||||
this.appIdService,
|
||||
(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.cipherService = new CipherService(
|
||||
this.cryptoService,
|
||||
@@ -295,7 +295,7 @@ export default class MainBackground {
|
||||
this.stateService,
|
||||
this.encryptService
|
||||
);
|
||||
this.folderService = new FolderService(
|
||||
this.folderService = new BrowserFolderService(
|
||||
this.cryptoService,
|
||||
this.i18nService,
|
||||
this.cipherService,
|
||||
@@ -317,8 +317,8 @@ export default class MainBackground {
|
||||
this.stateService
|
||||
);
|
||||
this.syncNotifierService = new SyncNotifierService();
|
||||
this.organizationService = new OrganizationService(this.stateService);
|
||||
this.policyService = new PolicyService(this.stateService, this.organizationService);
|
||||
this.organizationService = new BrowserOrganizationService(this.stateService);
|
||||
this.policyService = new BrowserPolicyService(this.stateService, this.organizationService);
|
||||
this.policyApiService = new PolicyApiService(
|
||||
this.policyService,
|
||||
this.apiService,
|
||||
|
||||
@@ -15,7 +15,7 @@ import { LoginView } from "@bitwarden/common/models/view/login.view";
|
||||
|
||||
import { BrowserApi } from "../browser/browserApi";
|
||||
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 AddLoginQueueMessage from "./models/addLoginQueueMessage";
|
||||
@@ -33,7 +33,7 @@ export default class NotificationBackground {
|
||||
private authService: AuthService,
|
||||
private policyService: PolicyService,
|
||||
private folderService: FolderService,
|
||||
private stateService: StateService
|
||||
private stateService: BrowserStateService
|
||||
) {}
|
||||
|
||||
async init() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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 { cryptoServiceFactory, CryptoServiceInitOptions } from "./crypto-service.factory";
|
||||
@@ -28,7 +28,7 @@ export function folderServiceFactory(
|
||||
"folderService",
|
||||
opts,
|
||||
async () =>
|
||||
new FolderService(
|
||||
new BrowserFolderService(
|
||||
await cryptoServiceFactory(cache, opts),
|
||||
await i18nServiceFactory(cache, opts),
|
||||
await cipherServiceFactory(cache, opts),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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 { stateServiceFactory, StateServiceInitOptions } from "./state-service.factory";
|
||||
@@ -17,6 +18,6 @@ export function organizationServiceFactory(
|
||||
cache,
|
||||
"organizationService",
|
||||
opts,
|
||||
async () => new OrganizationService(await stateServiceFactory(cache, opts))
|
||||
async () => new BrowserOrganizationService(await stateServiceFactory(cache, opts))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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 {
|
||||
@@ -26,7 +27,7 @@ export function policyServiceFactory(
|
||||
"policyService",
|
||||
opts,
|
||||
async () =>
|
||||
new PolicyService(
|
||||
new BrowserPolicyService(
|
||||
await stateServiceFactory(cache, opts),
|
||||
await organizationServiceFactory(cache, opts)
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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 { stateServiceFactory, StateServiceInitOptions } from "./state-service.factory";
|
||||
@@ -16,6 +17,6 @@ export function settingsServiceFactory(
|
||||
cache,
|
||||
"settingsService",
|
||||
opts,
|
||||
async () => new SettingsService(await stateServiceFactory(cache, opts))
|
||||
async () => new BrowserSettingsService(await stateServiceFactory(cache, opts))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { StateFactory } from "@bitwarden/common/factories/stateFactory";
|
||||
import { GlobalState } from "@bitwarden/common/models/domain/global-state";
|
||||
|
||||
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 { logServiceFactory, LogServiceInitOptions } from "./log-service.factory";
|
||||
@@ -34,15 +34,15 @@ export type StateServiceInitOptions = StateServiceFactoryOptions &
|
||||
StateMigrationServiceInitOptions;
|
||||
|
||||
export async function stateServiceFactory(
|
||||
cache: { stateService?: StateService } & CachedServices,
|
||||
cache: { stateService?: BrowserStateService } & CachedServices,
|
||||
opts: StateServiceInitOptions
|
||||
): Promise<StateService> {
|
||||
): Promise<BrowserStateService> {
|
||||
const service = await factory(
|
||||
cache,
|
||||
"stateService",
|
||||
opts,
|
||||
async () =>
|
||||
await new StateService(
|
||||
await new BrowserStateService(
|
||||
await diskStorageServiceFactory(cache, opts),
|
||||
await secureStorageServiceFactory(cache, opts),
|
||||
await memoryStorageServiceFactory(cache, opts),
|
||||
|
||||
39
apps/browser/src/clipboard/clear-clipboard.spec.ts
Normal file
39
apps/browser/src/clipboard/clear-clipboard.spec.ts
Normal 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",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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 { Account } from "../models/account";
|
||||
|
||||
import { getClearClipboardTime } from "./clipboard-state";
|
||||
export const clearClipboardAlarmName = "clearClipboard";
|
||||
|
||||
export class ClearClipboard {
|
||||
static async run(executionTime: Date, serviceCache: Record<string, unknown>) {
|
||||
const stateFactory = new StateFactory(GlobalState, Account);
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
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)`
|
||||
|
||||
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();
|
||||
if (!activeTabs || activeTabs.length === 0) {
|
||||
return;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { StateService } from "../services/abstractions/state.service";
|
||||
import { BrowserStateService } from "../services/abstractions/browser-state.service";
|
||||
|
||||
const clearClipboardStorageKey = "clearClipboardTime";
|
||||
export const getClearClipboardTime = async (stateService: StateService) => {
|
||||
export const getClearClipboardTime = async (stateService: BrowserStateService) => {
|
||||
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);
|
||||
};
|
||||
|
||||
@@ -2,30 +2,30 @@ import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
|
||||
|
||||
import { setAlarmTime } from "../alarms/alarm-state";
|
||||
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";
|
||||
|
||||
jest.mock("./clipboard-state", () => {
|
||||
jest.mock("../alarms/alarm-state", () => {
|
||||
return {
|
||||
getClearClipboardTime: jest.fn(),
|
||||
setClearClipboardTime: jest.fn(),
|
||||
setAlarmTime: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const setClearClipboardTimeMock = setClearClipboardTime as jest.Mock;
|
||||
const setAlarmTimeMock = setAlarmTime as jest.Mock;
|
||||
|
||||
describe("GeneratePasswordToClipboardCommand", () => {
|
||||
let passwordGenerationService: MockProxy<PasswordGenerationService>;
|
||||
let stateService: MockProxy<StateService>;
|
||||
let stateService: MockProxy<BrowserStateService>;
|
||||
|
||||
let sut: GeneratePasswordToClipboardCommand;
|
||||
|
||||
beforeEach(() => {
|
||||
passwordGenerationService = mock<PasswordGenerationService>();
|
||||
stateService = mock<StateService>();
|
||||
stateService = mock<BrowserStateService>();
|
||||
|
||||
passwordGenerationService.getOptions.mockResolvedValue([{ length: 8 }, {} as any]);
|
||||
|
||||
@@ -53,9 +53,9 @@ describe("GeneratePasswordToClipboardCommand", () => {
|
||||
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 () => {
|
||||
@@ -70,7 +70,7 @@ describe("GeneratePasswordToClipboardCommand", () => {
|
||||
text: "PASSWORD",
|
||||
});
|
||||
|
||||
expect(setClearClipboardTimeMock).not.toHaveBeenCalled();
|
||||
expect(setAlarmTimeMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
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";
|
||||
|
||||
export class GeneratePasswordToClipboardCommand {
|
||||
constructor(
|
||||
private passwordGenerationService: PasswordGenerationService,
|
||||
private stateService: StateService
|
||||
private stateService: BrowserStateService
|
||||
) {}
|
||||
|
||||
async generatePasswordToClipboard(tab: chrome.tabs.Tab) {
|
||||
@@ -20,7 +21,7 @@ export class GeneratePasswordToClipboardCommand {
|
||||
const clearClipboard = await this.stateService.getClearClipboard();
|
||||
|
||||
if (clearClipboard != null) {
|
||||
await setClearClipboardTime(this.stateService, Date.now() + clearClipboard * 1000);
|
||||
await setAlarmTime(clearClipboardAlarmName, clearClipboard * 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
import { StateService } from "../../services/state.service";
|
||||
import { BrowserStateService } from "../../services/browser-state.service";
|
||||
|
||||
import { browserSession } from "./browser-session.decorator";
|
||||
import { SessionStorable } from "./session-storable";
|
||||
@@ -22,25 +22,25 @@ describe("browserSession decorator", () => {
|
||||
});
|
||||
|
||||
it("should create if StateService is a constructor argument", () => {
|
||||
const stateService = Object.create(StateService.prototype, {});
|
||||
const stateService = Object.create(BrowserStateService.prototype, {});
|
||||
|
||||
@browserSession
|
||||
class TestClass {
|
||||
constructor(private stateService: StateService) {}
|
||||
constructor(private stateService: BrowserStateService) {}
|
||||
}
|
||||
|
||||
expect(new TestClass(stateService)).toBeDefined();
|
||||
});
|
||||
|
||||
describe("interaction with @sessionSync decorator", () => {
|
||||
let stateService: StateService;
|
||||
let stateService: BrowserStateService;
|
||||
|
||||
@browserSession
|
||||
class TestClass {
|
||||
@sessionSync({ initializer: (s: string) => s })
|
||||
private behaviorSubject = new BehaviorSubject("");
|
||||
|
||||
constructor(private stateService: StateService) {}
|
||||
constructor(private stateService: BrowserStateService) {}
|
||||
|
||||
fromJSON(json: any) {
|
||||
this.behaviorSubject.next(json);
|
||||
@@ -48,7 +48,7 @@ describe("browserSession decorator", () => {
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
stateService = Object.create(StateService.prototype, {}) as StateService;
|
||||
stateService = Object.create(BrowserStateService.prototype, {}) as BrowserStateService;
|
||||
});
|
||||
|
||||
it("should create a session syncer", () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Constructor } from "type-fest";
|
||||
|
||||
import { StateService } from "../../services/state.service";
|
||||
import { BrowserStateService } from "../../services/browser-state.service";
|
||||
|
||||
import { SessionStorable } from "./session-storable";
|
||||
import { SessionSyncer } from "./session-syncer";
|
||||
@@ -22,7 +22,13 @@ export function browserSession<TCtor extends Constructor<any>>(constructor: TCto
|
||||
super(...args);
|
||||
|
||||
// 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) {
|
||||
throw new Error(
|
||||
`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);
|
||||
syncer.init();
|
||||
return syncer;
|
||||
|
||||
@@ -8,9 +8,12 @@ describe("sessionSync decorator", () => {
|
||||
class TestClass {
|
||||
@sessionSync({ ctor: ctor, initializer: initializer })
|
||||
private testProperty = new BehaviorSubject("");
|
||||
@sessionSync({ ctor: ctor, initializer: initializer, initializeAs: "array" })
|
||||
private secondTestProperty = new BehaviorSubject("");
|
||||
|
||||
complete() {
|
||||
this.testProperty.complete();
|
||||
this.secondTestProperty.complete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,11 +22,40 @@ describe("sessionSync decorator", () => {
|
||||
expect((testClass as any).__syncedItemMetadata).toEqual([
|
||||
expect.objectContaining({
|
||||
propertyKey: "testProperty",
|
||||
sessionKey: "TestClass_testProperty",
|
||||
sessionKey: "testProperty_0",
|
||||
ctor: ctor,
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { SessionStorable } from "./session-storable";
|
||||
import { InitializeOptions } from "./sync-item-metadata";
|
||||
|
||||
class BuildOptions<T> {
|
||||
class BuildOptions<T, TJson = Jsonify<T>> {
|
||||
ctor?: new () => T;
|
||||
initializer?: (keyValuePair: Jsonify<T>) => T;
|
||||
initializeAsArray? = false;
|
||||
initializer?: (keyValuePair: TJson) => T;
|
||||
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.
|
||||
*
|
||||
@@ -20,10 +24,10 @@ class BuildOptions<T> {
|
||||
* @param buildOptions
|
||||
* 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
|
||||
* 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,
|
||||
* ```ts
|
||||
* \@sessionSync({ initializer: Foo.fromJSON, initializeAsArray: true })
|
||||
* \@sessionSync({ initializer: Foo.fromJSON, initializeAs: 'array' })
|
||||
* ```
|
||||
* is equivalent to
|
||||
* ```
|
||||
@@ -43,10 +47,10 @@ export function sessionSync<T>(buildOptions: BuildOptions<T>) {
|
||||
|
||||
p.__syncedItemMetadata.push({
|
||||
propertyKey,
|
||||
sessionKey: `${prototype.constructor.name}_${propertyKey}`,
|
||||
sessionKey: `${propertyKey}_${index++}`,
|
||||
ctor: buildOptions.ctor,
|
||||
initializer: buildOptions.initializer,
|
||||
initializeAsArray: buildOptions.initializeAsArray,
|
||||
initializeAs: buildOptions.initializeAs ?? "object",
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { awaitAsync as flushAsyncObservables } from "@bitwarden/angular/../test-utils";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
import { BehaviorSubject, ReplaySubject } from "rxjs";
|
||||
|
||||
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 { SyncedItemMetadata } from "./sync-item-metadata";
|
||||
@@ -10,8 +11,13 @@ import { SyncedItemMetadata } from "./sync-item-metadata";
|
||||
describe("session syncer", () => {
|
||||
const propertyKey = "behaviorSubject";
|
||||
const sessionKey = "Test__" + propertyKey;
|
||||
const metaData = { propertyKey, sessionKey, initializer: (s: string) => s };
|
||||
let stateService: MockProxy<StateService>;
|
||||
const metaData: SyncedItemMetadata = {
|
||||
propertyKey,
|
||||
sessionKey,
|
||||
initializer: (s: string) => s,
|
||||
initializeAs: "object",
|
||||
};
|
||||
let stateService: MockProxy<BrowserStateService>;
|
||||
let sut: SessionSyncer;
|
||||
let behaviorSubject: BehaviorSubject<string>;
|
||||
|
||||
@@ -23,7 +29,7 @@ describe("session syncer", () => {
|
||||
manifest_version: 3,
|
||||
});
|
||||
|
||||
stateService = mock<StateService>();
|
||||
stateService = mock<BrowserStateService>();
|
||||
sut = new SessionSyncer(behaviorSubject, stateService, metaData);
|
||||
});
|
||||
|
||||
@@ -34,53 +40,85 @@ describe("session syncer", () => {
|
||||
});
|
||||
|
||||
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(() => {
|
||||
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", () => {
|
||||
expect(
|
||||
new SessionSyncer(behaviorSubject, stateService, { propertyKey, sessionKey, ctor: String })
|
||||
new SessionSyncer(behaviorSubject, stateService, {
|
||||
propertyKey,
|
||||
sessionKey,
|
||||
ctor: String,
|
||||
initializeAs: "object",
|
||||
})
|
||||
).toBeDefined();
|
||||
expect(
|
||||
new SessionSyncer(behaviorSubject, stateService, {
|
||||
propertyKey,
|
||||
sessionKey,
|
||||
initializer: (s: any) => s,
|
||||
initializeAs: "object",
|
||||
})
|
||||
).toBeDefined();
|
||||
});
|
||||
it("should throw if neither ctor or initializer is provided", () => {
|
||||
expect(() => {
|
||||
new SessionSyncer(behaviorSubject, stateService, { propertyKey, sessionKey });
|
||||
new SessionSyncer(behaviorSubject, stateService, {
|
||||
propertyKey,
|
||||
sessionKey,
|
||||
initializeAs: "object",
|
||||
});
|
||||
}).toThrowError("ctor or initializer must be provided");
|
||||
});
|
||||
});
|
||||
|
||||
describe("manifest v2 init", () => {
|
||||
let observeSpy: jest.SpyInstance;
|
||||
let listenForUpdatesSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
observeSpy = jest.spyOn(behaviorSubject, "subscribe").mockReturnThis();
|
||||
listenForUpdatesSpy = jest.spyOn(BrowserApi, "messageListener").mockReturnValue();
|
||||
jest.spyOn(chrome.runtime, "getManifest").mockReturnValue({
|
||||
name: "bitwarden-test",
|
||||
version: "0.0.0",
|
||||
manifest_version: 2,
|
||||
});
|
||||
describe("init", () => {
|
||||
it("should ignore all updates currently in a ReplaySubject's buffer", () => {
|
||||
const replaySubject = new ReplaySubject<string>(Infinity);
|
||||
replaySubject.next("1");
|
||||
replaySubject.next("2");
|
||||
replaySubject.next("3");
|
||||
sut = new SessionSyncer(replaySubject, stateService, metaData);
|
||||
// block observing the subject
|
||||
jest.spyOn(sut as any, "observe").mockImplementation();
|
||||
|
||||
sut.init();
|
||||
|
||||
expect(sut["ignoreNUpdates"]).toBe(3);
|
||||
});
|
||||
|
||||
it("should not start observing", () => {
|
||||
expect(observeSpy).not.toHaveBeenCalled();
|
||||
it("should ignore BehaviorSubject's initial value", () => {
|
||||
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", () => {
|
||||
expect(listenForUpdatesSpy).not.toHaveBeenCalled();
|
||||
it("should grab an initial value from storage if it exists", () => {
|
||||
stateService.hasInSessionMemory.mockResolvedValue(true);
|
||||
//Block a call to update
|
||||
const updateSpy = jest.spyOn(sut as any, "update").mockImplementation();
|
||||
|
||||
sut.init();
|
||||
|
||||
expect(updateSpy).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it("should not grab an initial value from storage if it does not exist", () => {
|
||||
stateService.hasInSessionMemory.mockResolvedValue(false);
|
||||
//Block a call to update
|
||||
const updateSpy = jest.spyOn(sut as any, "update").mockImplementation();
|
||||
|
||||
sut.init();
|
||||
|
||||
expect(updateSpy).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -146,6 +184,7 @@ describe("session syncer", () => {
|
||||
stateService.getFromSessionMemory.mockResolvedValue("test");
|
||||
|
||||
await sut.updateFromMessage({ command: `${sessionKey}_update`, id: "different_id" });
|
||||
await flushAsyncObservables();
|
||||
|
||||
expect(stateService.getFromSessionMemory).toHaveBeenCalledTimes(1);
|
||||
expect(stateService.getFromSessionMemory).toHaveBeenCalledWith(sessionKey, builder);
|
||||
|
||||
@@ -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 { 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";
|
||||
|
||||
@@ -11,16 +11,16 @@ export class SessionSyncer {
|
||||
subscription: Subscription;
|
||||
id = Utils.newGuid();
|
||||
|
||||
// everyone gets the same initial values
|
||||
private ignoreNextUpdate = true;
|
||||
// ignore initial values
|
||||
private ignoreNUpdates = 0;
|
||||
|
||||
constructor(
|
||||
private behaviorSubject: BehaviorSubject<any>,
|
||||
private stateService: StateService,
|
||||
private subject: Subject<any>,
|
||||
private stateService: BrowserStateService,
|
||||
private metaData: SyncedItemMetadata
|
||||
) {
|
||||
if (!(behaviorSubject instanceof BehaviorSubject)) {
|
||||
throw new Error("behaviorSubject must be an instance of BehaviorSubject");
|
||||
if (!(subject instanceof Subject)) {
|
||||
throw new Error("subject must inherit from Subject");
|
||||
}
|
||||
|
||||
if (metaData.ctor == null && metaData.initializer == null) {
|
||||
@@ -29,11 +29,23 @@ export class SessionSyncer {
|
||||
}
|
||||
|
||||
init() {
|
||||
if (BrowserApi.manifestVersion !== 3) {
|
||||
return;
|
||||
switch (this.subject.constructor) {
|
||||
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();
|
||||
if (this.stateService.hasInSessionMemory(this.metaData.sessionKey)) {
|
||||
this.update();
|
||||
}
|
||||
|
||||
this.listenForUpdates();
|
||||
}
|
||||
|
||||
@@ -41,11 +53,11 @@ export class SessionSyncer {
|
||||
// This may be a memory leak.
|
||||
// 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.
|
||||
this.subscription = this.behaviorSubject
|
||||
this.subscription = this.subject
|
||||
.pipe(
|
||||
concatMap(async (next) => {
|
||||
if (this.ignoreNextUpdate) {
|
||||
this.ignoreNextUpdate = false;
|
||||
if (this.ignoreNUpdates > 0) {
|
||||
this.ignoreNUpdates -= 1;
|
||||
return;
|
||||
}
|
||||
await this.updateSession(next);
|
||||
@@ -66,10 +78,14 @@ export class SessionSyncer {
|
||||
if (message.command != this.updateMessageCommand || message.id === this.id) {
|
||||
return;
|
||||
}
|
||||
this.update();
|
||||
}
|
||||
|
||||
async update() {
|
||||
const builder = SyncedItemMetadata.builder(this.metaData);
|
||||
const value = await this.stateService.getFromSessionMemory(this.metaData.sessionKey, builder);
|
||||
this.ignoreNextUpdate = true;
|
||||
this.behaviorSubject.next(value);
|
||||
this.ignoreNUpdates = 1;
|
||||
this.subject.next(value);
|
||||
}
|
||||
|
||||
private async updateSession(value: any) {
|
||||
|
||||
@@ -1,17 +1,27 @@
|
||||
export type InitializeOptions = "array" | "record" | "object";
|
||||
|
||||
export class SyncedItemMetadata {
|
||||
propertyKey: string;
|
||||
sessionKey: string;
|
||||
ctor?: new () => any;
|
||||
initializer?: (keyValuePair: any) => any;
|
||||
initializeAsArray?: boolean;
|
||||
initializeAs: InitializeOptions;
|
||||
|
||||
static builder(metadata: SyncedItemMetadata): (o: any) => any {
|
||||
const itemBuilder =
|
||||
metadata.initializer != null
|
||||
? metadata.initializer
|
||||
: (o: any) => Object.assign(new metadata.ctor(), o);
|
||||
if (metadata.initializeAsArray) {
|
||||
if (metadata.initializeAs === "array") {
|
||||
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 {
|
||||
return (keyValuePair: any) => itemBuilder(keyValuePair);
|
||||
}
|
||||
|
||||
@@ -8,32 +8,60 @@ describe("builder", () => {
|
||||
const ctor = TestClass;
|
||||
|
||||
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);
|
||||
expect(builder({})).toBe("used initializer");
|
||||
});
|
||||
|
||||
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);
|
||||
expect(builder({})).toBeInstanceOf(TestClass);
|
||||
});
|
||||
|
||||
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);
|
||||
expect(builder({})).toBe("used initializer");
|
||||
});
|
||||
|
||||
it("should honor initialize as array", () => {
|
||||
const metadata = {
|
||||
const metadata: SyncedItemMetadata = {
|
||||
propertyKey,
|
||||
sessionKey: key,
|
||||
initializer: initializer,
|
||||
initializeAsArray: true,
|
||||
initializeAs: "array",
|
||||
};
|
||||
const builder = SyncedItemMetadata.builder(metadata);
|
||||
expect(builder([{}])).toBeInstanceOf(Array);
|
||||
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" });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@ import { searchServiceFactory } from "../background/service_factories/search-ser
|
||||
import { stateServiceFactory } from "../background/service_factories/state-service.factory";
|
||||
import { BrowserApi } from "../browser/browserApi";
|
||||
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";
|
||||
|
||||
export type BadgeOptions = {
|
||||
@@ -25,7 +25,7 @@ export type BadgeOptions = {
|
||||
|
||||
export class UpdateBadge {
|
||||
private authService: AuthService;
|
||||
private stateService: StateService;
|
||||
private stateService: BrowserStateService;
|
||||
private cipherService: CipherService;
|
||||
private badgeAction: typeof chrome.action;
|
||||
private sidebarAction: OperaSidebarAction | FirefoxSidebarAction;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import {
|
||||
Account as BaseAccount,
|
||||
AccountSettings as BaseAccountSettings,
|
||||
@@ -9,6 +11,14 @@ import { BrowserSendComponentState } from "./browserSendComponentState";
|
||||
|
||||
export class AccountSettings extends BaseAccountSettings {
|
||||
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 {
|
||||
@@ -29,4 +39,18 @@ export class Account extends BaseAccount {
|
||||
this.ciphers = init?.ciphers ?? 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),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
export class BrowserComponentState {
|
||||
scrollY: number;
|
||||
searchText: string;
|
||||
|
||||
static fromJSON(json: Jsonify<BrowserComponentState>) {
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Object.assign(new BrowserComponentState(), json);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { CipherType } from "@bitwarden/common/enums/cipherType";
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
|
||||
import { CollectionView } from "@bitwarden/common/models/view/collection.view";
|
||||
import { FolderView } from "@bitwarden/common/models/view/folder.view";
|
||||
import { DeepJsonify } from "@bitwarden/common/types/deep-jsonify";
|
||||
|
||||
import { BrowserComponentState } from "./browserComponentState";
|
||||
|
||||
@@ -15,4 +17,28 @@ export class BrowserGroupingsComponentState extends BrowserComponentState {
|
||||
folders: FolderView[];
|
||||
collections: CollectionView[];
|
||||
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)),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,28 @@
|
||||
import { SendType } from "@bitwarden/common/enums/sendType";
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
import { SendView } from "@bitwarden/common/models/view/send.view";
|
||||
import { DeepJsonify } from "@bitwarden/common/types/deep-jsonify";
|
||||
|
||||
import { BrowserComponentState } from "./browserComponentState";
|
||||
|
||||
export class BrowserSendComponentState extends BrowserComponentState {
|
||||
sends: SendView[];
|
||||
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),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,13 +39,10 @@
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<div class="box-content-row-flex">
|
||||
<div class="row-main">
|
||||
<label for="masterPassword"
|
||||
>{{ "masterPass" | i18n }}
|
||||
<strong
|
||||
class="sub-label text-{{ passwordStrengthComponent?.masterPasswordScoreColor }}"
|
||||
*ngIf="passwordStrengthComponent?.masterPasswordScoreText"
|
||||
>
|
||||
{{ passwordStrengthComponent?.masterPasswordScoreText }}
|
||||
<label for="masterPassword">
|
||||
{{ "masterPass" | i18n }}
|
||||
<strong class="sub-label text-{{ color }}" *ngIf="text">
|
||||
{{ text }}
|
||||
</strong>
|
||||
</label>
|
||||
<input
|
||||
|
||||
@@ -86,7 +86,9 @@
|
||||
</div>
|
||||
</ng-container>
|
||||
<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-content">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
|
||||
@@ -19,7 +19,7 @@ import { MessagingService } from "@bitwarden/common/abstractions/messaging.servi
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
|
||||
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";
|
||||
|
||||
@@ -43,7 +43,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
private authService: AuthService,
|
||||
private i18nService: I18nService,
|
||||
private router: Router,
|
||||
private stateService: StateService,
|
||||
private stateService: BrowserStateService,
|
||||
private messagingService: MessagingService,
|
||||
private changeDetectorRef: ChangeDetectorRef,
|
||||
private ngZone: NgZone,
|
||||
|
||||
@@ -57,11 +57,11 @@
|
||||
appStopClick
|
||||
appA11yTitle="{{ 'regenerateUsername' | i18n }}"
|
||||
(click)="regenerate()"
|
||||
[disabled]="form.loading"
|
||||
[disabled]="$any(form).loading"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-lg bwi-generate"
|
||||
[ngClass]="form.loading ? 'bwi-spin' : ''"
|
||||
[ngClass]="$any(form).loading ? 'bwi-spin' : ''"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
@@ -351,7 +351,7 @@
|
||||
<input
|
||||
id="duckduckgo-apikey"
|
||||
type="password"
|
||||
name="DuckDudkGoApiKey"
|
||||
name="DuckDuckGoApiKey"
|
||||
[(ngModel)]="usernameOptions.forwardedDuckDuckGoToken"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
|
||||
@@ -432,6 +432,12 @@ app-vault-view .box-footer {
|
||||
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
|
||||
// See: https://bugs.chromium.org/p/chromium/issues/detail?id=971701#c64
|
||||
@keyframes redraw {
|
||||
|
||||
@@ -305,8 +305,11 @@
|
||||
>
|
||||
<div class="row-main text-danger">
|
||||
<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-spinner bwi-spin 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]="!$any(deleteBtn).loading"
|
||||
></i>
|
||||
</div>
|
||||
<span>{{ "deleteSend" | i18n }}</span>
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,7 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti
|
||||
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
|
||||
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";
|
||||
|
||||
@Component({
|
||||
@@ -33,7 +33,7 @@ export class SendAddEditComponent extends BaseAddEditComponent {
|
||||
constructor(
|
||||
i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
stateService: StateService,
|
||||
stateService: BrowserStateService,
|
||||
messagingService: MessagingService,
|
||||
policyService: PolicyService,
|
||||
environmentService: EnvironmentService,
|
||||
|
||||
@@ -15,7 +15,7 @@ import { SendType } from "@bitwarden/common/enums/sendType";
|
||||
import { SendView } from "@bitwarden/common/models/view/send.view";
|
||||
|
||||
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";
|
||||
|
||||
const ComponentId = "SendComponent";
|
||||
@@ -42,7 +42,7 @@ export class SendGroupingsComponent extends BaseSendComponent {
|
||||
policyService: PolicyService,
|
||||
searchService: SearchService,
|
||||
private popupUtils: PopupUtilsService,
|
||||
private stateService: StateService,
|
||||
private stateService: BrowserStateService,
|
||||
private router: Router,
|
||||
private syncService: SyncService,
|
||||
private changeDetectorRef: ChangeDetectorRef,
|
||||
@@ -165,12 +165,12 @@ export class SendGroupingsComponent extends BaseSendComponent {
|
||||
}
|
||||
|
||||
private async saveState() {
|
||||
this.state = {
|
||||
this.state = Object.assign(new BrowserSendComponentState(), {
|
||||
scrollY: this.popupUtils.getContentScrollY(window),
|
||||
searchText: this.searchText,
|
||||
sends: this.sends,
|
||||
typeCounts: this.typeCounts,
|
||||
};
|
||||
});
|
||||
await this.stateService.setBrowserSendComponentState(this.state);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import { SendType } from "@bitwarden/common/enums/sendType";
|
||||
import { SendView } from "@bitwarden/common/models/view/send.view";
|
||||
|
||||
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";
|
||||
|
||||
const ComponentId = "SendTypeComponent";
|
||||
@@ -41,7 +41,7 @@ export class SendTypeComponent extends BaseSendComponent {
|
||||
policyService: PolicyService,
|
||||
searchService: SearchService,
|
||||
private popupUtils: PopupUtilsService,
|
||||
private stateService: StateService,
|
||||
private stateService: BrowserStateService,
|
||||
private route: ActivatedRoute,
|
||||
private location: Location,
|
||||
private changeDetectorRef: ChangeDetectorRef,
|
||||
|
||||
@@ -5,7 +5,7 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService as LogServiceAbstraction } from "@bitwarden/common/abstractions/log.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";
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abs
|
||||
import { SendService } from "@bitwarden/common/abstractions/send.service";
|
||||
import { SettingsService } from "@bitwarden/common/abstractions/settings.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 { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
|
||||
import { TokenService } from "@bitwarden/common/abstractions/token.service";
|
||||
@@ -47,6 +48,8 @@ import { UserVerificationService } from "@bitwarden/common/abstractions/userVeri
|
||||
import { UsernameGenerationService } from "@bitwarden/common/abstractions/usernameGeneration.service";
|
||||
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.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 { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service";
|
||||
import { LoginService } from "@bitwarden/common/services/login.service";
|
||||
@@ -54,9 +57,14 @@ import { SearchService } from "@bitwarden/common/services/search.service";
|
||||
|
||||
import MainBackground from "../../background/main.background";
|
||||
import { BrowserApi } from "../../browser/browserApi";
|
||||
import { Account } from "../../models/account";
|
||||
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 { 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 BrowserMessagingService from "../../services/browserMessaging.service";
|
||||
import BrowserMessagingPrivateModePopupService from "../../services/browserMessagingPrivateModePopup.service";
|
||||
@@ -190,8 +198,13 @@ function getBgService<T>(service: keyof MainBackground) {
|
||||
{ provide: EventService, useFactory: getBgService<EventService>("eventService"), deps: [] },
|
||||
{
|
||||
provide: PolicyService,
|
||||
useFactory: getBgService<PolicyService>("policyService"),
|
||||
deps: [],
|
||||
useFactory: (
|
||||
stateService: StateServiceAbstraction,
|
||||
organizationService: OrganizationService
|
||||
) => {
|
||||
return new BrowserPolicyService(stateService, organizationService);
|
||||
},
|
||||
deps: [StateServiceAbstraction, OrganizationService],
|
||||
},
|
||||
{
|
||||
provide: PolicyApiServiceAbstraction,
|
||||
@@ -212,8 +225,10 @@ function getBgService<T>(service: keyof MainBackground) {
|
||||
{ provide: SyncService, useFactory: getBgService<SyncService>("syncService"), deps: [] },
|
||||
{
|
||||
provide: SettingsService,
|
||||
useFactory: getBgService<SettingsService>("settingsService"),
|
||||
deps: [],
|
||||
useFactory: (stateService: StateServiceAbstraction) => {
|
||||
return new BrowserSettingsService(stateService);
|
||||
},
|
||||
deps: [StateServiceAbstraction],
|
||||
},
|
||||
{
|
||||
provide: AbstractStorageService,
|
||||
@@ -261,8 +276,10 @@ function getBgService<T>(service: keyof MainBackground) {
|
||||
{ provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService },
|
||||
{
|
||||
provide: OrganizationService,
|
||||
useFactory: getBgService<OrganizationService>("organizationService"),
|
||||
deps: [],
|
||||
useFactory: (stateService: StateServiceAbstraction) => {
|
||||
return new BrowserOrganizationService(stateService);
|
||||
},
|
||||
deps: [StateServiceAbstraction],
|
||||
},
|
||||
{
|
||||
provide: VaultFilterService,
|
||||
@@ -293,10 +310,36 @@ function getBgService<T>(service: keyof MainBackground) {
|
||||
useFactory: getBgService<AbstractStorageService>("memoryStorageService"),
|
||||
},
|
||||
{
|
||||
provide: StateServiceAbstraction,
|
||||
useFactory: getBgService<StateServiceAbstraction>("stateService"),
|
||||
provide: StateMigrationService,
|
||||
useFactory: getBgService<StateMigrationService>("stateMigrationService"),
|
||||
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,
|
||||
useFactory: getBgService<UsernameGenerationService>("usernameGenerationService"),
|
||||
@@ -317,17 +360,19 @@ function getBgService<T>(service: keyof MainBackground) {
|
||||
},
|
||||
{
|
||||
provide: AbstractThemingService,
|
||||
useFactory: () => {
|
||||
useFactory: (
|
||||
stateService: StateServiceAbstraction,
|
||||
platformUtilsService: PlatformUtilsService
|
||||
) => {
|
||||
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.
|
||||
// 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()
|
||||
? getBgService<Window>("backgroundWindow")()
|
||||
: window,
|
||||
platformUtilsService.isSafari() ? getBgService<Window>("backgroundWindow")() : window,
|
||||
document
|
||||
);
|
||||
},
|
||||
deps: [StateServiceAbstraction, PlatformUtilsService],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
@@ -40,8 +40,11 @@
|
||||
>
|
||||
<div class="row-main text-danger">
|
||||
<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-spinner bwi-spin 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]="!$any(deleteBtn).loading"
|
||||
></i>
|
||||
</div>
|
||||
<span>{{ "deleteFolder" | i18n }}</span>
|
||||
</div>
|
||||
|
||||
@@ -49,14 +49,14 @@
|
||||
#refreshBtn
|
||||
type="button"
|
||||
(click)="refresh()"
|
||||
[disabled]="refreshBtn.loading"
|
||||
[disabled]="$any(refreshBtn).loading"
|
||||
[appApiAction]="refreshPromise"
|
||||
class="btn link block"
|
||||
>
|
||||
<span [hidden]="refreshBtn.loading">{{ "premiumRefresh" | i18n }}</span>
|
||||
<span [hidden]="$any(refreshBtn).loading">{{ "premiumRefresh" | i18n }}</span>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw"
|
||||
[hidden]="!refreshBtn.loading"
|
||||
[hidden]="!$any(refreshBtn).loading"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
|
||||
@@ -17,11 +17,15 @@
|
||||
class="btn block primary"
|
||||
(click)="sync()"
|
||||
#syncBtn
|
||||
[disabled]="syncBtn.loading"
|
||||
[disabled]="$any(syncBtn).loading"
|
||||
[appApiAction]="syncPromise"
|
||||
>
|
||||
<span [hidden]="syncBtn.loading">{{ "syncVaultNow" | i18n }}</span>
|
||||
<i class="bwi bwi-spinner bwi-lg bwi-spin" [hidden]="!syncBtn.loading" aria-hidden="true"></i>
|
||||
<span [hidden]="$any(syncBtn).loading">{{ "syncVaultNow" | i18n }}</span>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-lg bwi-spin"
|
||||
[hidden]="!$any(syncBtn).loading"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
<p class="text-center text-muted small">{{ "lastSync" | i18n }} {{ lastSync }}</p>
|
||||
</div>
|
||||
|
||||
@@ -81,8 +81,8 @@
|
||||
[(ngModel)]="f.value"
|
||||
*ngIf="f.type === fieldType.Boolean"
|
||||
appTrueFalseValue
|
||||
trueValue="true"
|
||||
falseValue="false"
|
||||
[trueValue]="true"
|
||||
[falseValue]="false"
|
||||
attr.aria-describedby="fieldName{{ i }}"
|
||||
[readonly]="!cipher.edit && editMode"
|
||||
/>
|
||||
|
||||
@@ -88,17 +88,17 @@
|
||||
appA11yTitle="{{ 'checkPassword' | i18n }}"
|
||||
(click)="checkPassword()"
|
||||
[appApiAction]="checkPasswordPromise"
|
||||
[disabled]="checkPasswordBtn.loading"
|
||||
[disabled]="$any(checkPasswordBtn).loading"
|
||||
*ngIf="cipher.viewPassword"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-lg bwi-check-circle"
|
||||
[hidden]="checkPasswordBtn.loading"
|
||||
[hidden]="$any(checkPasswordBtn).loading"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<i
|
||||
class="bwi bwi-lg bwi-spinner bwi-spin"
|
||||
[hidden]="!checkPasswordBtn.loading"
|
||||
[hidden]="!$any(checkPasswordBtn).loading"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
@@ -487,7 +487,7 @@
|
||||
type="text"
|
||||
name="Login.Uris[{{ i }}].Uri"
|
||||
[(ngModel)]="u.uri"
|
||||
[hidden]="u.showUriOptionsInput === true"
|
||||
[hidden]="$any(u).showUriOptionsInput === true"
|
||||
placeholder="{{ 'ex' | i18n }} https://google.com"
|
||||
inputmode="url"
|
||||
appInputVerbatim
|
||||
@@ -500,7 +500,7 @@
|
||||
id="currentUris{{ i }}"
|
||||
name="Login.Uris[{{ i }}].CurrentUris"
|
||||
[(ngModel)]="u.uri"
|
||||
[hidden]="!u.showCurrentUris"
|
||||
[hidden]="!$any(u).showCurrentUris"
|
||||
>
|
||||
<option [ngValue]="null">-- {{ "select" | i18n }} --</option>
|
||||
<option *ngFor="let u of currentUris" [ngValue]="u">{{ u }}</option>
|
||||
@@ -512,7 +512,9 @@
|
||||
id="loginUriMatch{{ i }}"
|
||||
name="Login.Uris[{{ i }}].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)"
|
||||
>
|
||||
<option *ngFor="let o of uriMatchOptions" [ngValue]="o.value">{{ o.name }}</option>
|
||||
@@ -526,7 +528,7 @@
|
||||
appStopClick
|
||||
appA11yTitle="{{ 'toggleCurrentUris' | i18n }}"
|
||||
(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>
|
||||
</button>
|
||||
@@ -536,7 +538,7 @@
|
||||
appStopClick
|
||||
appA11yTitle="{{ 'toggleOptions' | i18n }}"
|
||||
(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>
|
||||
</button>
|
||||
@@ -695,7 +697,7 @@
|
||||
<input
|
||||
id="collection_{{ i }}"
|
||||
type="checkbox"
|
||||
[(ngModel)]="c.checked"
|
||||
[(ngModel)]="$any(c).checked"
|
||||
name="Collection[{{ i }}].Checked"
|
||||
/>
|
||||
</div>
|
||||
@@ -713,8 +715,11 @@
|
||||
>
|
||||
<div class="row-main text-danger">
|
||||
<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-spinner bwi-spin 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]="!$any(deleteBtn).loading"
|
||||
></i>
|
||||
</div>
|
||||
<span>{{ "deleteItem" | i18n }}</span>
|
||||
</div>
|
||||
|
||||
@@ -37,16 +37,16 @@
|
||||
(click)="delete(a)"
|
||||
#deleteBtn
|
||||
[appApiAction]="deletePromises[a.id]"
|
||||
[disabled]="deleteBtn.loading"
|
||||
[disabled]="$any(deleteBtn).loading"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-trash bwi-lg bwi-fw"
|
||||
[hidden]="deleteBtn.loading"
|
||||
[hidden]="$any(deleteBtn).loading"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw"
|
||||
[hidden]="!deleteBtn.loading"
|
||||
[hidden]="!$any(deleteBtn).loading"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<input
|
||||
id="collection_{{ i }}"
|
||||
type="checkbox"
|
||||
[(ngModel)]="c.checked"
|
||||
[(ngModel)]="$any(c).checked"
|
||||
name="Collection[{{ i }}].Checked"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -200,7 +200,7 @@ export class CurrentTabComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
private async load() {
|
||||
protected async load() {
|
||||
this.isLoading = false;
|
||||
this.tab = await BrowserApi.getTabFromCurrentWindow();
|
||||
if (this.tab != null) {
|
||||
|
||||
@@ -18,7 +18,7 @@ import { FolderView } from "@bitwarden/common/models/view/folder.view";
|
||||
|
||||
import { BrowserApi } from "../../browser/browserApi";
|
||||
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 { PopupUtilsService } from "../services/popup-utils.service";
|
||||
|
||||
@@ -83,7 +83,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private searchService: SearchService,
|
||||
private location: Location,
|
||||
private browserStateService: StateService,
|
||||
private browserStateService: BrowserStateService,
|
||||
private vaultFilterService: VaultFilterService
|
||||
) {
|
||||
this.noFolderListSize = 100;
|
||||
@@ -373,7 +373,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
private async saveState() {
|
||||
this.state = {
|
||||
this.state = Object.assign(new BrowserGroupingsComponentState(), {
|
||||
scrollY: this.popupUtils.getContentScrollY(window),
|
||||
searchText: this.searchText,
|
||||
favoriteCiphers: this.favoriteCiphers,
|
||||
@@ -385,7 +385,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
|
||||
folders: this.folders,
|
||||
collections: this.collections,
|
||||
deletedCount: this.deletedCount,
|
||||
};
|
||||
});
|
||||
await this.browserStateService.setBrowserGroupingComponentState(this.state);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import { FolderView } from "@bitwarden/common/models/view/folder.view";
|
||||
|
||||
import { BrowserApi } from "../../browser/browserApi";
|
||||
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 { PopupUtilsService } from "../services/popup-utils.service";
|
||||
|
||||
@@ -60,7 +60,7 @@ export class VaultItemsComponent extends BaseVaultItemsComponent implements OnIn
|
||||
private ngZone: NgZone,
|
||||
private broadcasterService: BroadcasterService,
|
||||
private changeDetectorRef: ChangeDetectorRef,
|
||||
private stateService: StateService,
|
||||
private stateService: BrowserStateService,
|
||||
private popupUtils: PopupUtilsService,
|
||||
private i18nService: I18nService,
|
||||
private folderService: FolderService,
|
||||
|
||||
@@ -27,11 +27,7 @@
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
>
|
||||
<button
|
||||
appStopClick
|
||||
(click)="selectAllVaults()"
|
||||
[ngClass]="{ active: !myVaultOnly && !selectOrganizationId }"
|
||||
>
|
||||
<button appStopClick (click)="selectAllVaults()">
|
||||
<i class="bwi bwi-fw bwi-filter" aria-hidden="true"></i>
|
||||
{{ "allVaults" | i18n }}
|
||||
</button>
|
||||
@@ -60,7 +56,7 @@
|
||||
<i
|
||||
*ngIf="!organization.enabled"
|
||||
class="bwi bwi-fw bwi-exclamation-triangle text-danger"
|
||||
aria-label="{{ 'organizationIsDisabled' | i18n }}"
|
||||
attr.aria-label="{{ 'organizationIsDisabled' | i18n }}"
|
||||
appA11yTitle="{{ 'organizationIsDisabled' | i18n }}"
|
||||
></i>
|
||||
</button>
|
||||
|
||||
@@ -88,17 +88,17 @@
|
||||
appA11yTitle="{{ 'checkPassword' | i18n }}"
|
||||
(click)="checkPassword()"
|
||||
[appApiAction]="checkPasswordPromise"
|
||||
[disabled]="checkPasswordBtn.loading"
|
||||
[disabled]="$any(checkPasswordBtn).loading"
|
||||
*ngIf="cipher.viewPassword"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-lg bwi-check-circle"
|
||||
[hidden]="checkPasswordBtn.loading"
|
||||
[hidden]="$any(checkPasswordBtn).loading"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<i
|
||||
class="bwi bwi-lg bwi-spinner bwi-spin"
|
||||
[hidden]="!checkPasswordBtn.loading"
|
||||
[hidden]="!$any(checkPasswordBtn).loading"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
@@ -537,12 +537,12 @@
|
||||
<small class="row-sub-label">{{ attachment.sizeName }}</small>
|
||||
<i
|
||||
class="bwi bwi-download bwi-fw row-sub-icon"
|
||||
*ngIf="!attachment.downloading"
|
||||
*ngIf="!$any(attachment).downloading"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-fw bwi-spin row-sub-icon"
|
||||
*ngIf="attachment.downloading"
|
||||
*ngIf="$any(attachment).downloading"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
|
||||
@@ -8,7 +8,8 @@ import { BrowserComponentState } from "../../models/browserComponentState";
|
||||
import { BrowserGroupingsComponentState } from "../../models/browserGroupingsComponentState";
|
||||
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 setInSessionMemory(key: string, value: any): Promise<void>;
|
||||
getBrowserGroupingComponentState: (
|
||||
@@ -14,7 +14,6 @@ import { BrowserApi } from "../browser/browserApi";
|
||||
import AutofillField from "../models/autofillField";
|
||||
import AutofillPageDetails from "../models/autofillPageDetails";
|
||||
import AutofillScript from "../models/autofillScript";
|
||||
import { StateService } from "../services/abstractions/state.service";
|
||||
|
||||
import {
|
||||
AutoFillOptions,
|
||||
@@ -22,6 +21,7 @@ import {
|
||||
PageDetail,
|
||||
FormData,
|
||||
} from "./abstractions/autofill.service";
|
||||
import { BrowserStateService } from "./abstractions/browser-state.service";
|
||||
import {
|
||||
AutoFillConstants,
|
||||
CreditCardAutoFillConstants,
|
||||
@@ -39,7 +39,7 @@ export interface GenerateFillScriptOptions {
|
||||
export default class AutofillService implements AutofillServiceInterface {
|
||||
constructor(
|
||||
private cipherService: CipherService,
|
||||
private stateService: StateService,
|
||||
private stateService: BrowserStateService,
|
||||
private totpService: TotpService,
|
||||
private eventService: EventService,
|
||||
private logService: LogService
|
||||
|
||||
@@ -4,12 +4,12 @@ import { Folder } from "@bitwarden/common/models/domain/folder";
|
||||
import { FolderView } from "@bitwarden/common/models/view/folder.view";
|
||||
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
|
||||
export class FolderService extends BaseFolderService {
|
||||
@sessionSync({ initializer: Folder.fromJSON, initializeAsArray: true })
|
||||
export class BrowserFolderService extends BaseFolderService {
|
||||
@sessionSync({ initializer: Folder.fromJSON, initializeAs: "array" })
|
||||
protected _folders: BehaviorSubject<Folder[]>;
|
||||
@sessionSync({ initializer: FolderView.fromJSON, initializeAsArray: true })
|
||||
@sessionSync({ initializer: FolderView.fromJSON, initializeAs: "array" })
|
||||
protected _folderViews: BehaviorSubject<FolderView[]>;
|
||||
}
|
||||
12
apps/browser/src/services/browser-organization.service.ts
Normal file
12
apps/browser/src/services/browser-organization.service.ts
Normal 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[]>;
|
||||
}
|
||||
12
apps/browser/src/services/browser-policy.service.ts
Normal file
12
apps/browser/src/services/browser-policy.service.ts
Normal 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[]>;
|
||||
}
|
||||
11
apps/browser/src/services/browser-settings.service.ts
Normal file
11
apps/browser/src/services/browser-settings.service.ts
Normal 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>;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import {
|
||||
MemoryStorageServiceInterface,
|
||||
@@ -18,28 +18,29 @@ import { BrowserComponentState } from "../models/browserComponentState";
|
||||
import { BrowserGroupingsComponentState } from "../models/browserGroupingsComponentState";
|
||||
import { BrowserSendComponentState } from "../models/browserSendComponentState";
|
||||
|
||||
import { AbstractKeyGenerationService } from "./abstractions/abstractKeyGeneration.service";
|
||||
import { BrowserStateService } from "./browser-state.service";
|
||||
import { LocalBackedSessionStorageService } from "./localBackedSessionStorage.service";
|
||||
import { StateService } from "./state.service";
|
||||
|
||||
describe("Browser State Service", () => {
|
||||
let secureStorageService: SubstituteOf<AbstractStorageService>;
|
||||
let diskStorageService: SubstituteOf<AbstractStorageService>;
|
||||
let logService: SubstituteOf<LogService>;
|
||||
let stateMigrationService: SubstituteOf<StateMigrationService>;
|
||||
let stateFactory: SubstituteOf<StateFactory<GlobalState, Account>>;
|
||||
let secureStorageService: MockProxy<AbstractStorageService>;
|
||||
let diskStorageService: MockProxy<AbstractStorageService>;
|
||||
let logService: MockProxy<LogService>;
|
||||
let stateMigrationService: MockProxy<StateMigrationService>;
|
||||
let stateFactory: MockProxy<StateFactory<GlobalState, Account>>;
|
||||
let useAccountCache: boolean;
|
||||
|
||||
let state: State<GlobalState, Account>;
|
||||
const userId = "userId";
|
||||
|
||||
let sut: StateService;
|
||||
let sut: BrowserStateService;
|
||||
|
||||
beforeEach(() => {
|
||||
secureStorageService = Substitute.for();
|
||||
diskStorageService = Substitute.for();
|
||||
logService = Substitute.for();
|
||||
stateMigrationService = Substitute.for();
|
||||
stateFactory = Substitute.for();
|
||||
secureStorageService = mock();
|
||||
diskStorageService = mock();
|
||||
logService = mock();
|
||||
stateMigrationService = mock();
|
||||
stateFactory = mock();
|
||||
useAccountCache = true;
|
||||
|
||||
state = new State(new GlobalState());
|
||||
@@ -54,9 +55,12 @@ describe("Browser State Service", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
// We need `AbstractCachedStorageService` in the prototype chain to correctly test cache bypass.
|
||||
memoryStorageService = Object.create(LocalBackedSessionStorageService.prototype);
|
||||
memoryStorageService = new LocalBackedSessionStorageService(
|
||||
mock<EncryptService>(),
|
||||
mock<AbstractKeyGenerationService>()
|
||||
);
|
||||
|
||||
sut = new StateService(
|
||||
sut = new BrowserStateService(
|
||||
diskStorageService,
|
||||
secureStorageService,
|
||||
memoryStorageService,
|
||||
@@ -80,14 +84,14 @@ describe("Browser State Service", () => {
|
||||
});
|
||||
|
||||
describe("state methods", () => {
|
||||
let memoryStorageService: SubstituteOf<AbstractStorageService & MemoryStorageServiceInterface>;
|
||||
let memoryStorageService: MockProxy<AbstractStorageService & MemoryStorageServiceInterface>;
|
||||
|
||||
beforeEach(() => {
|
||||
memoryStorageService = Substitute.for();
|
||||
const stateGetter = (key: string) => Promise.resolve(JSON.parse(JSON.stringify(state)));
|
||||
memoryStorageService.get("state", Arg.any()).mimicks(stateGetter);
|
||||
memoryStorageService = mock();
|
||||
const stateGetter = (key: string) => Promise.resolve(state);
|
||||
memoryStorageService.get.mockImplementation(stateGetter);
|
||||
|
||||
sut = new StateService(
|
||||
sut = new BrowserStateService(
|
||||
diskStorageService,
|
||||
secureStorageService,
|
||||
memoryStorageService,
|
||||
@@ -128,6 +132,7 @@ describe("Browser State Service", () => {
|
||||
[SendType.Text, 5],
|
||||
]);
|
||||
state.accounts[userId].send = sendState;
|
||||
(global as any)["watch"] = state;
|
||||
|
||||
const actual = await sut.getBrowserSendComponentState();
|
||||
expect(actual).toBeInstanceOf(BrowserSendComponentState);
|
||||
@@ -1,24 +1,40 @@
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { AbstractCachedStorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||
import { GlobalState } from "@bitwarden/common/models/domain/global-state";
|
||||
import { StorageOptions } from "@bitwarden/common/models/domain/storage-options";
|
||||
import {
|
||||
StateService as BaseStateService,
|
||||
withPrototype,
|
||||
} from "@bitwarden/common/services/state.service";
|
||||
import { StateService as BaseStateService } from "@bitwarden/common/services/state.service";
|
||||
|
||||
import { browserSession, sessionSync } from "../decorators/session-sync-observable";
|
||||
import { Account } from "../models/account";
|
||||
import { BrowserComponentState } from "../models/browserComponentState";
|
||||
import { BrowserGroupingsComponentState } from "../models/browserGroupingsComponentState";
|
||||
import { BrowserSendComponentState } from "../models/browserSendComponentState";
|
||||
|
||||
import { StateService as StateServiceAbstraction } from "./abstractions/state.service";
|
||||
import { BrowserStateService as StateServiceAbstraction } from "./abstractions/browser-state.service";
|
||||
|
||||
export class StateService
|
||||
@browserSession
|
||||
export class BrowserStateService
|
||||
extends BaseStateService<GlobalState, Account>
|
||||
implements StateServiceAbstraction
|
||||
{
|
||||
@sessionSync({
|
||||
initializer: Account.fromJSON as any, // TODO: Remove this any when all any types are removed from Account
|
||||
initializeAs: "record",
|
||||
})
|
||||
protected accountsSubject: BehaviorSubject<{ [userId: string]: Account }>;
|
||||
@sessionSync({ ctor: String })
|
||||
protected activeAccountSubject: BehaviorSubject<string>;
|
||||
@sessionSync({ ctor: Boolean })
|
||||
protected activeAccountUnlockedSubject: BehaviorSubject<boolean>;
|
||||
|
||||
protected accountDeserializer = Account.fromJSON;
|
||||
|
||||
async hasInSessionMemory(key: string): Promise<boolean> {
|
||||
return await this.memoryStorageService.has(key);
|
||||
}
|
||||
|
||||
async getFromSessionMemory<T>(key: string, deserializer?: (obj: Jsonify<T>) => T): Promise<T> {
|
||||
return this.memoryStorageService instanceof AbstractCachedStorageService
|
||||
? await this.memoryStorageService.getBypassCache<T>(key, { deserializer: deserializer })
|
||||
@@ -44,7 +60,6 @@ export class StateService
|
||||
);
|
||||
}
|
||||
|
||||
@withPrototype(BrowserGroupingsComponentState)
|
||||
async getBrowserGroupingComponentState(
|
||||
options?: StorageOptions
|
||||
): Promise<BrowserGroupingsComponentState> {
|
||||
@@ -67,7 +82,6 @@ export class StateService
|
||||
);
|
||||
}
|
||||
|
||||
@withPrototype(BrowserComponentState)
|
||||
async getBrowserVaultItemsComponentState(
|
||||
options?: StorageOptions
|
||||
): Promise<BrowserComponentState> {
|
||||
@@ -90,7 +104,6 @@ export class StateService
|
||||
);
|
||||
}
|
||||
|
||||
@withPrototype(BrowserSendComponentState)
|
||||
async getBrowserSendComponentState(options?: StorageOptions): Promise<BrowserSendComponentState> {
|
||||
return (
|
||||
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
|
||||
@@ -111,7 +124,6 @@ export class StateService
|
||||
);
|
||||
}
|
||||
|
||||
@withPrototype(BrowserComponentState)
|
||||
async getBrowserSendTypeComponentState(options?: StorageOptions): Promise<BrowserComponentState> {
|
||||
return (
|
||||
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
|
||||
@@ -150,6 +150,10 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
|
||||
return Promise.resolve(BrowserApi.getApplicationVersion());
|
||||
}
|
||||
|
||||
async getApplicationVersionNumber(): Promise<string> {
|
||||
return (await this.getApplicationVersion()).split(RegExp("[+|-]"))[0].trim();
|
||||
}
|
||||
|
||||
supportsWebAuthn(win: Window): boolean {
|
||||
return typeof PublicKeyCredential !== "undefined";
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ Bitwarden offers Teams and Enterprise plans for companies so you can securely sh
|
||||
Why Choose Bitwarden:
|
||||
|
||||
World-Class Encryption
|
||||
Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private.
|
||||
Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashing, and PBKDF2 SHA-256) so your data stays secure and private.
|
||||
|
||||
Built-in Password Generator
|
||||
Generate strong, unique, and random passwords based on security requirements for every website you frequent.
|
||||
|
||||
@@ -118,7 +118,7 @@
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Name" xml:space="preserve">
|
||||
<value>Bitwarden – Ilmainen salasanojen hallinta</value>
|
||||
<value>Bitwarden – Free Password Manager</value>
|
||||
</data>
|
||||
<data name="Summary" xml:space="preserve">
|
||||
<value>Turvallinen ja ilmainen salasanojen hallinta kaikille laitteillesi</value>
|
||||
|
||||
@@ -148,7 +148,8 @@ Traductions mondiales
|
||||
Les traductions de Bitwarden existent dans 40 langues et ne cessent de croître, grâce à notre communauté mondiale.
|
||||
|
||||
Applications multiplateformes
|
||||
Sécurisez et partagez des données sensibles dans votre coffre-fort Bitwarden à partir de n'importe quel navigateur, appareil mobile ou système d'exploitation de bureau, et plus encore.</value>
|
||||
Sécurisez et partagez des données sensibles dans votre coffre-fort Bitwarden à partir de n'importe quel navigateur, appareil mobile ou système d'exploitation de bureau, et plus encore.
|
||||
</value>
|
||||
</data>
|
||||
<data name="AssetTitle" xml:space="preserve">
|
||||
<value>Un gestionnaire de mots de passe sécurisé et gratuit pour tous vos appareils</value>
|
||||
|
||||
@@ -124,31 +124,31 @@
|
||||
<value>Сигурни и бесплатни менаџер лозинке за сва Ваша уређаја</value>
|
||||
</data>
|
||||
<data name="Description" xml:space="preserve">
|
||||
<value>Bitwarden, Inc. is the parent company of 8bit Solutions LLC.
|
||||
<value>Bitwarden, Inc. је матична компанија фирме 8bit Solutions LLC.
|
||||
|
||||
NAMED BEST PASSWORD MANAGER BY THE VERGE, U.S. NEWS & WORLD REPORT, CNET, AND MORE.
|
||||
Именован као најбољи управљач лозинкама од стране новинских сајтова као што су THE VERGE, U.S. NEWS & WORLD REPORT, CNET, и других.
|
||||
|
||||
Manage, store, secure, and share unlimited passwords across unlimited devices from anywhere. Bitwarden delivers open source password management solutions to everyone, whether at home, at work, or on the go.
|
||||
Управљајте, чувајте, обезбедите, и поделите неограничен број лозинки са неограниченог броја уређаја где год да се налазите. Bitwarden свима доноси решења за управљање лозинкама која су отвореног кода, било да сте код куће, на послу, или на путу.
|
||||
|
||||
Generate strong, unique, and random passwords based on security requirements for every website you frequent.
|
||||
Генеришите јаке, јединствене, и насумичне лозинке у зависности од безбедносних захтева за сваки сајт који често посећујете.
|
||||
|
||||
Bitwarden Send quickly transmits encrypted information --- files and plaintext -- directly to anyone.
|
||||
Bitwarden Send брзо преноси шифроване информације--- датотеке и обичан текст-- директно и свима.
|
||||
|
||||
Bitwarden offers Teams and Enterprise plans for companies so you can securely share passwords with colleagues.
|
||||
Bitwarden нуди планове за компаније и предузећа како бисте могли безбедно да делите лозинке са вашим колегама.
|
||||
|
||||
Why Choose Bitwarden:
|
||||
Зашто изабрати Bitwarden:
|
||||
|
||||
World-Class Encryption
|
||||
Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashing, and PBKDF2 SHA-256) so your data stays secure and private.
|
||||
Шифровање светске класе
|
||||
Лозинке су заштићене напредним шифровањем од једног до другог краја (AES-256 bit, salted hashing, и PBKDF2 SHA-256) како би ваши подаци остали безбедни и приватни.
|
||||
|
||||
Built-in Password Generator
|
||||
Generate strong, unique, and random passwords based on security requirements for every website you frequent.
|
||||
Уграђен генератор лозинки
|
||||
Генеришите јаке, јединствене, и насумичне лозинке у зависности од безбедносних захтева за сваки сајт који често посећујете.
|
||||
|
||||
Global Translations
|
||||
Bitwarden translations exist in 40 languages and are growing, thanks to our global community.
|
||||
Глобално преведен
|
||||
Bitwarden преводи постоје за 40 језика и стално се унапређују, захваљујући нашој глобалној заједници.
|
||||
|
||||
Cross-Platform Applications
|
||||
Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more.
|
||||
Вишеплатформне апликације
|
||||
Обезбедите и поделите осетљиве податке у вашем Bitwarden сефу из било ког претраживача, мобилног уређаја, или desktop оперативног система, и других.
|
||||
</value>
|
||||
</data>
|
||||
<data name="AssetTitle" xml:space="preserve">
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
}
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"strictTemplates": true,
|
||||
"preserveWhitespaces": true
|
||||
},
|
||||
"include": ["src", "../../libs/common/src/services/**/*.worker.ts"]
|
||||
|
||||
@@ -11,7 +11,7 @@ The Bitwarden CLI is a powerful, full-featured command-line interface (CLI) tool
|
||||
|
||||
## Developer Documentation
|
||||
|
||||
Please refer to the [CLI section](https://contributing.bitwarden.com/clients/cli/) 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 [CLI section](https://contributing.bitwarden.com/docs/clients/cli/) 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.
|
||||
|
||||
## User Documentation
|
||||
|
||||
|
||||
4
apps/cli/package-lock.json
generated
4
apps/cli/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@bitwarden/cli",
|
||||
"version": "2022.10.0",
|
||||
"version": "2022.11.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@bitwarden/cli",
|
||||
"version": "2022.10.0",
|
||||
"version": "2022.11.0",
|
||||
"license": "GPL-3.0-only",
|
||||
"dependencies": {
|
||||
"@koa/multer": "^3.0.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@bitwarden/cli",
|
||||
"description": "A secure and free password manager for all of your devices.",
|
||||
"version": "2022.10.0",
|
||||
"version": "2022.11.0",
|
||||
"keywords": [
|
||||
"bitwarden",
|
||||
"password",
|
||||
|
||||
@@ -1,104 +1,109 @@
|
||||
import * as fetch from "node-fetch";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
|
||||
import { Response } from "../models/response";
|
||||
import { MessageResponse } from "../models/response/message.response";
|
||||
|
||||
const CLIENTS_RELEASE_LIST_ENDPOINT = "https://api.github.com/repos/bitwarden/clients/releases";
|
||||
const DEFAULT_DOWNLOAD_URL = "https://github.com/bitwarden/clients/releases";
|
||||
const UPDATE_COMMAND = "npm install -g @bitwarden/cli";
|
||||
|
||||
export class UpdateCommand {
|
||||
inPkg = false;
|
||||
|
||||
constructor(
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService,
|
||||
private repoName: string,
|
||||
private executableName: string,
|
||||
private showExtendedMessage: boolean
|
||||
) {
|
||||
constructor(private platformUtilsService: PlatformUtilsService) {
|
||||
this.inPkg = !!(process as any).pkg;
|
||||
}
|
||||
|
||||
async run(): Promise<Response> {
|
||||
const currentVersion = await this.platformUtilsService.getApplicationVersion();
|
||||
|
||||
const response = await fetch.default(
|
||||
"https://api.github.com/repos/bitwarden/" + this.repoName + "/releases/latest"
|
||||
);
|
||||
if (response.status === 200) {
|
||||
const responseJson = await response.json();
|
||||
const res = new MessageResponse(null, null);
|
||||
|
||||
const tagName: string = responseJson.tag_name;
|
||||
if (tagName === "v" + currentVersion) {
|
||||
res.title = "No update available.";
|
||||
res.noColor = true;
|
||||
return Response.success(res);
|
||||
}
|
||||
|
||||
let downloadUrl: string = null;
|
||||
if (responseJson.assets != null) {
|
||||
for (const a of responseJson.assets) {
|
||||
const download: string = a.browser_download_url;
|
||||
if (download == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (download.indexOf(".zip") === -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
process.platform === "win32" &&
|
||||
download.indexOf(this.executableName + "-windows") > -1
|
||||
) {
|
||||
downloadUrl = download;
|
||||
break;
|
||||
} else if (
|
||||
process.platform === "darwin" &&
|
||||
download.indexOf(this.executableName + "-macos") > -1
|
||||
) {
|
||||
downloadUrl = download;
|
||||
break;
|
||||
} else if (
|
||||
process.platform === "linux" &&
|
||||
download.indexOf(this.executableName + "-linux") > -1
|
||||
) {
|
||||
downloadUrl = download;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.title = "A new version is available: " + tagName;
|
||||
if (downloadUrl == null) {
|
||||
downloadUrl = "https://github.com/bitwarden/" + this.repoName + "/releases";
|
||||
} else {
|
||||
res.raw = downloadUrl;
|
||||
}
|
||||
res.message = "";
|
||||
if (responseJson.body != null && responseJson.body !== "") {
|
||||
res.message = responseJson.body + "\n\n";
|
||||
}
|
||||
|
||||
res.message += "You can download this update at " + downloadUrl;
|
||||
|
||||
if (this.showExtendedMessage) {
|
||||
if (this.inPkg) {
|
||||
res.message +=
|
||||
"\n\nIf you installed this CLI through a package manager " +
|
||||
"you should probably update using its update command instead.";
|
||||
} else {
|
||||
res.message +=
|
||||
"\n\nIf you installed this CLI through NPM " +
|
||||
"you should update using `npm install -g @bitwarden/" +
|
||||
this.repoName +
|
||||
"`";
|
||||
}
|
||||
}
|
||||
return Response.success(res);
|
||||
} else {
|
||||
const response = await fetch.default(CLIENTS_RELEASE_LIST_ENDPOINT);
|
||||
if (response.status !== 200) {
|
||||
return Response.error("Error contacting update API: " + response.status);
|
||||
}
|
||||
|
||||
const responseJson = await response.json();
|
||||
const cliRelease = responseJson.find((r: any) => r.tag_name.includes("cli"));
|
||||
if (cliRelease === undefined || cliRelease === null) {
|
||||
return Response.error("Could not find latest CLI version.");
|
||||
}
|
||||
|
||||
const currentVersion = await this.platformUtilsService.getApplicationVersion();
|
||||
if (cliRelease.tag_name === "cli-v" + currentVersion) {
|
||||
const response = new MessageResponse(null, null);
|
||||
response.title = "No update available.";
|
||||
response.noColor = true;
|
||||
return Response.success(response);
|
||||
}
|
||||
|
||||
const res = this.getFoundUpdateResponse(cliRelease);
|
||||
return Response.success(res);
|
||||
}
|
||||
|
||||
private getFoundUpdateResponse(release: any) {
|
||||
const downloadUrl = this.getDownloadUrl(release.assets);
|
||||
|
||||
const response = new MessageResponse(null, null);
|
||||
response.title = "A new version is available: " + release.tag_name;
|
||||
response.raw = downloadUrl;
|
||||
response.message = this.getMessage(release, downloadUrl);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private getMessage(release: any, downloadUrl: string) {
|
||||
let message = "";
|
||||
|
||||
if (release.body != null && release.body !== "") {
|
||||
message = release.body + "\n\n";
|
||||
}
|
||||
|
||||
message += "You can download this update at " + downloadUrl;
|
||||
|
||||
if (this.inPkg) {
|
||||
message +=
|
||||
"\n\nIf you installed this CLI through a package manager " +
|
||||
"you should probably update using its update command instead.";
|
||||
} else {
|
||||
message +=
|
||||
"\n\nIf you installed this CLI through NPM " +
|
||||
"you should update using `" +
|
||||
UPDATE_COMMAND +
|
||||
"`";
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
private getDownloadUrl(assets: any) {
|
||||
if (assets == null) {
|
||||
return DEFAULT_DOWNLOAD_URL;
|
||||
}
|
||||
|
||||
let downloadUrl: string = DEFAULT_DOWNLOAD_URL;
|
||||
|
||||
for (const a of assets) {
|
||||
const download: string = a.browser_download_url;
|
||||
if (download == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (download.indexOf(".zip") === -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (process.platform === "win32" && download.indexOf("bw-windows") > -1) {
|
||||
downloadUrl = download;
|
||||
break;
|
||||
} else if (process.platform === "darwin" && download.indexOf("bw-macos") > -1) {
|
||||
downloadUrl = download;
|
||||
break;
|
||||
} else if (process.platform === "linux" && download.indexOf("bw-linux") > -1) {
|
||||
downloadUrl = download;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return downloadUrl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -403,13 +403,7 @@ export class Program {
|
||||
writeLn("", true);
|
||||
})
|
||||
.action(async () => {
|
||||
const command = new UpdateCommand(
|
||||
this.main.platformUtilsService,
|
||||
this.main.i18nService,
|
||||
"cli",
|
||||
"bw",
|
||||
true
|
||||
);
|
||||
const command = new UpdateCommand(this.main.platformUtilsService);
|
||||
const response = await command.run();
|
||||
this.processResponse(response);
|
||||
});
|
||||
|
||||
@@ -88,6 +88,10 @@ export class CliPlatformUtilsService implements PlatformUtilsService {
|
||||
return Promise.resolve(this.packageJson.version);
|
||||
}
|
||||
|
||||
async getApplicationVersionNumber(): Promise<string> {
|
||||
return (await this.getApplicationVersion()).split(RegExp("[+|-]"))[0].trim();
|
||||
}
|
||||
|
||||
getApplicationVersionSync(): string {
|
||||
return this.packageJson.version;
|
||||
}
|
||||
|
||||
@@ -12,4 +12,4 @@ The Bitwarden desktop app is written using Electron and Angular. The application
|
||||
|
||||
## Documentation
|
||||
|
||||
Please refer to the [Desktop section](https://contributing.bitwarden.com/clients/desktop/) 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 [Desktop section](https://contributing.bitwarden.com/docs/clients/desktop/) 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.
|
||||
|
||||
@@ -5,9 +5,9 @@ import { hideBin } from "yargs/helpers";
|
||||
|
||||
import { NativeMessagingVersion } from "@bitwarden/common/enums/nativeMessagingVersion";
|
||||
|
||||
import { CredentialCreatePayload } from "../../../src/models/nativeMessaging/encryptedMessagePayloads/credentialCreatePayload";
|
||||
import { LogUtils } from "../logUtils";
|
||||
import NativeMessageService from "../nativeMessageService";
|
||||
import { CredentialCreatePayload } from "../../../src/models/native-messaging/encrypted-message-payloads/credential-create-payload";
|
||||
import { LogUtils } from "../log-utils";
|
||||
import NativeMessageService from "../native-message.service";
|
||||
import * as config from "../variables";
|
||||
|
||||
const argv: any = yargs(hideBin(process.argv)).option("name", {
|
||||
|
||||
@@ -5,8 +5,8 @@ import { hideBin } from "yargs/helpers";
|
||||
|
||||
import { NativeMessagingVersion } from "@bitwarden/common/enums/nativeMessagingVersion";
|
||||
|
||||
import { LogUtils } from "../logUtils";
|
||||
import NativeMessageService from "../nativeMessageService";
|
||||
import { LogUtils } from "../log-utils";
|
||||
import NativeMessageService from "../native-message.service";
|
||||
import * as config from "../variables";
|
||||
|
||||
const argv: any = yargs(hideBin(process.argv)).option("uri", {
|
||||
|
||||
@@ -5,9 +5,9 @@ import { hideBin } from "yargs/helpers";
|
||||
|
||||
import { NativeMessagingVersion } from "@bitwarden/common/enums/nativeMessagingVersion";
|
||||
|
||||
import { CredentialUpdatePayload } from "../../../src/models/nativeMessaging/encryptedMessagePayloads/credentialUpdatePayload";
|
||||
import { LogUtils } from "../logUtils";
|
||||
import NativeMessageService from "../nativeMessageService";
|
||||
import { CredentialUpdatePayload } from "../../../src/models/native-messaging/encrypted-message-payloads/credential-update-payload";
|
||||
import { LogUtils } from "../log-utils";
|
||||
import NativeMessageService from "../native-message.service";
|
||||
import * as config from "../variables";
|
||||
|
||||
// Command line arguments
|
||||
|
||||
@@ -5,8 +5,8 @@ import { hideBin } from "yargs/helpers";
|
||||
|
||||
import { NativeMessagingVersion } from "@bitwarden/common/enums/nativeMessagingVersion";
|
||||
|
||||
import { LogUtils } from "../logUtils";
|
||||
import NativeMessageService from "../nativeMessageService";
|
||||
import { LogUtils } from "../log-utils";
|
||||
import NativeMessageService from "../native-message.service";
|
||||
import * as config from "../variables";
|
||||
|
||||
const argv: any = yargs(hideBin(process.argv)).option("userId", {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user