mirror of
https://github.com/bitwarden/browser
synced 2026-02-09 13:10:17 +00:00
Merge remote-tracking branch 'origin/main' into PM-19741
This commit is contained in:
5
.github/CODEOWNERS
vendored
5
.github/CODEOWNERS
vendored
@@ -185,5 +185,8 @@ apps/web/src/locales/en/messages.json
|
||||
**/entrypoint.sh
|
||||
|
||||
## Overrides
|
||||
# tsconfig files are potentially dangerous and will be reviewed by platform to prevent misconfigurations
|
||||
# For the time being platform owns tsconfig and jest config
|
||||
# These overrides will be removed after Nx is implemented
|
||||
# To track that effort please see https://bitwarden.atlassian.net/browse/PM-21636
|
||||
**/tsconfig.json @bitwarden/team-platform-dev
|
||||
**/jest.config.js @bitwarden/team-platform-dev
|
||||
|
||||
3
.github/renovate.json5
vendored
3
.github/renovate.json5
vendored
@@ -222,7 +222,6 @@
|
||||
"@types/chrome",
|
||||
"@types/firefox-webext-browser",
|
||||
"@types/glob",
|
||||
"@types/jquery",
|
||||
"@types/lowdb",
|
||||
"@types/node",
|
||||
"@types/node-forge",
|
||||
@@ -330,9 +329,7 @@
|
||||
"autoprefixer",
|
||||
"bootstrap",
|
||||
"chromatic",
|
||||
"jquery",
|
||||
"ngx-toastr",
|
||||
"popper.js",
|
||||
"react",
|
||||
"react-dom",
|
||||
"remark-gfm",
|
||||
|
||||
7
.github/workflows/build-browser-target.yml
vendored
7
.github/workflows/build-browser-target.yml
vendored
@@ -8,10 +8,9 @@ name: Build Browser on PR Target
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize]
|
||||
branches-ignore:
|
||||
- 'l10n_master'
|
||||
- 'cf-pages'
|
||||
types: [opened, synchronize, reopened]
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'apps/browser/**'
|
||||
- 'libs/**'
|
||||
|
||||
7
.github/workflows/build-cli-target.yml
vendored
7
.github/workflows/build-cli-target.yml
vendored
@@ -8,10 +8,9 @@ name: Build CLI on PR Target
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize]
|
||||
branches-ignore:
|
||||
- 'l10n_master'
|
||||
- 'cf-pages'
|
||||
types: [opened, synchronize, reopened]
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'apps/cli/**'
|
||||
- 'libs/**'
|
||||
|
||||
7
.github/workflows/build-desktop-target.yml
vendored
7
.github/workflows/build-desktop-target.yml
vendored
@@ -9,10 +9,9 @@ name: Build Desktop on PR Target
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize]
|
||||
branches-ignore:
|
||||
- 'l10n_master'
|
||||
- 'cf-pages'
|
||||
types: [opened, synchronize, reopened]
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'apps/desktop/**'
|
||||
- 'libs/**'
|
||||
|
||||
7
.github/workflows/build-web-target.yml
vendored
7
.github/workflows/build-web-target.yml
vendored
@@ -8,10 +8,9 @@ name: Build Web on PR Target
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize]
|
||||
branches-ignore:
|
||||
- 'l10n_master'
|
||||
- 'cf-pages'
|
||||
types: [opened, synchronize, reopened]
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'apps/web/**'
|
||||
- 'libs/**'
|
||||
|
||||
2
.github/workflows/chromatic.yml
vendored
2
.github/workflows/chromatic.yml
vendored
@@ -73,7 +73,7 @@ jobs:
|
||||
run: npm run build-storybook:ci
|
||||
|
||||
- name: Publish to Chromatic
|
||||
uses: chromaui/action@8a12962215a66cd05b1ac5b0f1c08768d1aab155 # v11.25.0
|
||||
uses: chromaui/action@e8cc4c31775280b175a3c440076c00d19a9014d7 # v11.28.2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||
|
||||
8
.github/workflows/scan.yml
vendored
8
.github/workflows/scan.yml
vendored
@@ -7,8 +7,14 @@ on:
|
||||
- "main"
|
||||
- "rc"
|
||||
- "hotfix-rc"
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
branches-ignore:
|
||||
- main
|
||||
pull_request_target:
|
||||
types: [opened, synchronize]
|
||||
types: [opened, synchronize, reopened]
|
||||
branches:
|
||||
- "main"
|
||||
|
||||
jobs:
|
||||
check-run:
|
||||
|
||||
45
.github/workflows/test.yml
vendored
45
.github/workflows/test.yml
vendored
@@ -8,7 +8,7 @@ on:
|
||||
- "rc"
|
||||
- "hotfix-rc-*"
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
types: [ opened, synchronize ]
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -66,12 +66,15 @@ jobs:
|
||||
reporter: jest-junit
|
||||
fail-on-error: true
|
||||
|
||||
- name: Upload coverage to codecov.io
|
||||
uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2
|
||||
|
||||
- name: Upload results to codecov.io
|
||||
uses: codecov/test-results-action@f2dba722c67b86c6caa034178c6e4d35335f6706 # v1.1.0
|
||||
|
||||
- name: Upload test coverage
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: jest-coverage
|
||||
path: ./coverage/lcov.info
|
||||
|
||||
rust:
|
||||
name: Run Rust tests on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os || 'ubuntu-22.04' }}
|
||||
@@ -148,7 +151,37 @@ jobs:
|
||||
working-directory: ./apps/desktop/desktop_native
|
||||
run: cargo llvm-cov --all-features --lcov --output-path lcov.info --workspace --no-cfg-coverage
|
||||
|
||||
- name: Upload to codecov.io
|
||||
- name: Upload test coverage
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: rust-coverage
|
||||
path: ./apps/desktop/desktop_native/lcov.info
|
||||
|
||||
upload-codecov:
|
||||
name: Upload to Codecov
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- testing
|
||||
- rust-coverage
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Download jest coverage
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||
with:
|
||||
name: jest-coverage
|
||||
path: ./
|
||||
|
||||
- name: Download rust coverage
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||
with:
|
||||
name: rust-coverage
|
||||
path: ./apps/desktop/desktop_native
|
||||
|
||||
- name: Upload coverage to codecov.io
|
||||
uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2
|
||||
with:
|
||||
files: ./apps/desktop/desktop_native/lcov.info
|
||||
files: |
|
||||
./lcov.info
|
||||
./apps/desktop/desktop_native/lcov.info
|
||||
|
||||
30
angular.json
30
angular.json
@@ -6,6 +6,32 @@
|
||||
"analytics": false
|
||||
},
|
||||
"projects": {
|
||||
"bit-web": {
|
||||
"projectType": "application",
|
||||
"schematics": {
|
||||
"@schematics/angular:application": {
|
||||
"strict": true
|
||||
}
|
||||
},
|
||||
"root": "bitwarden_license/bit-web",
|
||||
"sourceRoot": "bitwarden_license/bit-web/src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/web",
|
||||
"index": "apps/web/src/index.html",
|
||||
"main": "bitwarden_license/bit-web/src/app/main.ts",
|
||||
"polyfills": "apps/web/src/polyfills.ts",
|
||||
"tsConfig": "bitwarden_license/bit-web/tsconfig.json",
|
||||
"assets": ["apps/web/src/favicon.ico"],
|
||||
"styles": [],
|
||||
"scripts": []
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"web": {
|
||||
"projectType": "application",
|
||||
"schematics": {
|
||||
@@ -22,8 +48,8 @@
|
||||
"options": {
|
||||
"outputPath": "dist/web",
|
||||
"index": "apps/web/src/index.html",
|
||||
"main": "apps/web/src/app/main.ts",
|
||||
"polyfills": "apps/web/src/app/polyfills.ts",
|
||||
"main": "apps/web/src/main.ts",
|
||||
"polyfills": "apps/web/src/polyfills.ts",
|
||||
"tsConfig": "apps/web/tsconfig.json",
|
||||
"assets": ["apps/web/src/favicon.ico"],
|
||||
"styles": [],
|
||||
|
||||
@@ -1071,6 +1071,10 @@
|
||||
},
|
||||
"description": "Aria label for the view button in notification bar confirmation message"
|
||||
},
|
||||
"notificationNewItemAria": {
|
||||
"message": "New Item, opens in new window",
|
||||
"description": "Aria label for the new item button in notification bar confirmation message when error is prompted"
|
||||
},
|
||||
"notificationEditTooltip": {
|
||||
"message": "Edit before saving",
|
||||
"description": "Tooltip and Aria label for edit button on cipher item"
|
||||
@@ -1090,13 +1094,24 @@
|
||||
},
|
||||
"notificationLoginSaveConfirmation": {
|
||||
"message": "saved to Bitwarden.",
|
||||
|
||||
"description": "Shown to user after item is saved."
|
||||
},
|
||||
"notificationLoginUpdatedConfirmation": {
|
||||
"message": "updated in Bitwarden.",
|
||||
"description": "Shown to user after item is updated."
|
||||
},
|
||||
"selectItemAriaLabel": {
|
||||
"message": "Select $ITEMTYPE$, $ITEMNAME$",
|
||||
"description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.",
|
||||
"placeholders": {
|
||||
"itemType": {
|
||||
"content": "$1"
|
||||
},
|
||||
"itemName": {
|
||||
"content": "$2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"saveAsNewLoginAction": {
|
||||
"message": "Save as new login",
|
||||
"description": "Button text for saving login details as a new entry."
|
||||
@@ -1105,6 +1120,10 @@
|
||||
"message": "Update login",
|
||||
"description": "Button text for updating an existing login entry."
|
||||
},
|
||||
"unlockToSave": {
|
||||
"message": "Unlock to save this login",
|
||||
"description": "User prompt to take action in order to save the login they just entered."
|
||||
},
|
||||
"saveLogin": {
|
||||
"message": "Save login",
|
||||
"description": "Prompt asking the user if they want to save their login details."
|
||||
@@ -2208,15 +2227,6 @@
|
||||
"vaultTimeoutAction1": {
|
||||
"message": "Timeout action"
|
||||
},
|
||||
"newCustomizationOptionsCalloutTitle": {
|
||||
"message": "New customization options"
|
||||
},
|
||||
"newCustomizationOptionsCalloutContent": {
|
||||
"message": "Customize your vault experience with quick copy actions, compact mode, and more!"
|
||||
},
|
||||
"newCustomizationOptionsCalloutLink": {
|
||||
"message": "View all Appearance settings"
|
||||
},
|
||||
"lock": {
|
||||
"message": "Lock",
|
||||
"description": "Verb form: to make secure or inaccessible by"
|
||||
@@ -3621,7 +3631,7 @@
|
||||
"orgTrustWarning1": {
|
||||
"message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint."
|
||||
},
|
||||
"trustUser":{
|
||||
"trustUser": {
|
||||
"message": "Trust user"
|
||||
},
|
||||
"sendsNoItemsTitle": {
|
||||
@@ -5285,6 +5295,9 @@
|
||||
"secureDevicesBody": {
|
||||
"message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps."
|
||||
},
|
||||
"nudgeBadgeAria": {
|
||||
"message": "1 notification"
|
||||
},
|
||||
"emptyVaultNudgeTitle": {
|
||||
"message": "Import existing passwords"
|
||||
},
|
||||
@@ -5297,8 +5310,14 @@
|
||||
"hasItemsVaultNudgeTitle": {
|
||||
"message": "Welcome to your vault!"
|
||||
},
|
||||
"hasItemsVaultNudgeBody": {
|
||||
"message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else"
|
||||
"hasItemsVaultNudgeBodyOne": {
|
||||
"message": "Autofill items for the current page"
|
||||
},
|
||||
"hasItemsVaultNudgeBodyTwo": {
|
||||
"message": "Favorite items for easy access"
|
||||
},
|
||||
"hasItemsVaultNudgeBodyThree": {
|
||||
"message": "Search your vault for something else"
|
||||
},
|
||||
"newLoginNudgeTitle": {
|
||||
"message": "Save time with autofill"
|
||||
@@ -5348,5 +5367,8 @@
|
||||
"message": "Learn more about SSH agent",
|
||||
"description": "Two part message",
|
||||
"example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent"
|
||||
},
|
||||
"noPermissionsViewPage": {
|
||||
"message": "You do not have permissions to view this page. Try logging in with a different account."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,18 +168,21 @@ type Story = StoryObj<ExtensionAnonLayoutWrapperComponent>;
|
||||
@Component({
|
||||
selector: "bit-default-primary-outlet-example-component",
|
||||
template: "<p>Primary Outlet Example: <br> your primary component goes here</p>",
|
||||
standalone: false,
|
||||
})
|
||||
class DefaultPrimaryOutletExampleComponent {}
|
||||
|
||||
@Component({
|
||||
selector: "bit-default-secondary-outlet-example-component",
|
||||
template: "<p>Secondary Outlet Example: <br> your secondary component goes here</p>",
|
||||
standalone: false,
|
||||
})
|
||||
class DefaultSecondaryOutletExampleComponent {}
|
||||
|
||||
@Component({
|
||||
selector: "bit-default-env-selector-outlet-example-component",
|
||||
template: "<p>Env Selector Outlet Example: <br> your env selector component goes here</p>",
|
||||
standalone: false,
|
||||
})
|
||||
class DefaultEnvSelectorOutletExampleComponent {}
|
||||
|
||||
@@ -264,6 +267,7 @@ const changedData: ExtensionAnonLayoutWrapperData = {
|
||||
template: `
|
||||
<button type="button" bitButton buttonType="primary" (click)="toggleData()">Toggle Data</button>
|
||||
`,
|
||||
standalone: false,
|
||||
})
|
||||
export class DynamicContentExampleComponent {
|
||||
initialData = true;
|
||||
|
||||
@@ -5,5 +5,6 @@ import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/ang
|
||||
@Component({
|
||||
selector: "app-set-password",
|
||||
templateUrl: "set-password.component.html",
|
||||
standalone: false,
|
||||
})
|
||||
export class SetPasswordComponent extends BaseSetPasswordComponent {}
|
||||
|
||||
@@ -18,5 +18,6 @@ import { VaultTimeoutInputComponent as VaultTimeoutInputComponentBase } from "@b
|
||||
useExisting: VaultTimeoutInputComponent,
|
||||
},
|
||||
],
|
||||
standalone: false,
|
||||
})
|
||||
export class VaultTimeoutInputComponent extends VaultTimeoutInputComponentBase {}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { postLogoutMessageListener$ } from "./utils/post-logout-message-listener
|
||||
@Component({
|
||||
selector: "app-update-temp-password",
|
||||
templateUrl: "update-temp-password.component.html",
|
||||
standalone: false,
|
||||
})
|
||||
export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {
|
||||
onSuccessfulChangePassword: () => Promise<void> = this.doOnSuccessfulChangePassword.bind(this);
|
||||
|
||||
@@ -946,9 +946,7 @@ export default class NotificationBackground {
|
||||
private async getDecryptedCipherById(cipherId: string, userId: UserId) {
|
||||
const cipher = await this.cipherService.get(cipherId, userId);
|
||||
if (cipher != null && cipher.type === CipherType.Login) {
|
||||
return await cipher.decrypt(
|
||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, userId),
|
||||
);
|
||||
return await this.cipherService.decrypt(cipher, userId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -74,6 +74,10 @@ const actionButtonStyles = ({
|
||||
background-color: ${themes[theme].primary["700"]};
|
||||
color: ${themes[theme].text.contrast};
|
||||
}
|
||||
:focus {
|
||||
outline: 2px solid ${themes[theme].primary["600"]};
|
||||
outline-offset: 1px;
|
||||
}
|
||||
`}
|
||||
|
||||
svg {
|
||||
|
||||
@@ -8,15 +8,19 @@ import { border, themes, typography, spacing } from "../constants/styles";
|
||||
export type BadgeButtonProps = {
|
||||
buttonAction: (e: Event) => void;
|
||||
buttonText: string;
|
||||
itemName: string;
|
||||
disabled?: boolean;
|
||||
theme: Theme;
|
||||
username?: string;
|
||||
};
|
||||
|
||||
export function BadgeButton({
|
||||
buttonAction,
|
||||
buttonText,
|
||||
disabled = false,
|
||||
itemName,
|
||||
theme,
|
||||
username,
|
||||
}: BadgeButtonProps) {
|
||||
const handleButtonClick = (event: Event) => {
|
||||
if (!disabled) {
|
||||
@@ -28,6 +32,7 @@ export function BadgeButton({
|
||||
<button
|
||||
type="button"
|
||||
title=${buttonText}
|
||||
aria-label=${[buttonText, [itemName, username].filter(Boolean).join(" ")]}
|
||||
class=${badgeButtonStyles({ disabled, theme })}
|
||||
@click=${handleButtonClick}
|
||||
>
|
||||
@@ -65,5 +70,9 @@ const badgeButtonStyles = ({ disabled, theme }: { disabled: boolean; theme: Them
|
||||
background-color: ${themes[theme].primary["600"]};
|
||||
color: ${themes[theme].text.contrast};
|
||||
}
|
||||
:focus {
|
||||
outline: 2px solid ${themes[theme].primary["600"]};
|
||||
outline-offset: 2px;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
@@ -3,17 +3,24 @@ import { html } from "lit";
|
||||
|
||||
import { Theme } from "@bitwarden/common/platform/enums";
|
||||
|
||||
import { I18n } from "../common-types";
|
||||
import { spacing, themes } from "../constants/styles";
|
||||
import { Close as CloseIcon } from "../icons";
|
||||
|
||||
export type CloseButtonProps = {
|
||||
i18n: I18n;
|
||||
handleCloseNotification: (e: Event) => void;
|
||||
theme: Theme;
|
||||
};
|
||||
|
||||
export function CloseButton({ handleCloseNotification, theme }: CloseButtonProps) {
|
||||
export function CloseButton({ handleCloseNotification, i18n, theme }: CloseButtonProps) {
|
||||
return html`
|
||||
<button type="button" class=${closeButtonStyles(theme)} @click=${handleCloseNotification}>
|
||||
<button
|
||||
type="button"
|
||||
aria-label=${i18n.close}
|
||||
class=${closeButtonStyles(theme)}
|
||||
@click=${handleCloseNotification}
|
||||
>
|
||||
${CloseIcon({ theme })}
|
||||
</button>
|
||||
`;
|
||||
|
||||
@@ -33,6 +33,9 @@ export function OptionSelectionButton({
|
||||
class=${selectionButtonStyles({ disabled, toggledOn, theme })}
|
||||
title=${text}
|
||||
type="button"
|
||||
aria-haspopup="menu"
|
||||
aria-expanded=${toggledOn}
|
||||
aria-controls="option-menu"
|
||||
@click=${handleButtonClick}
|
||||
>
|
||||
${buttonIcon ?? nothing}
|
||||
|
||||
@@ -8,8 +8,10 @@ import { I18n } from "../common-types";
|
||||
export type CipherActionProps = {
|
||||
handleAction?: (e: Event) => void;
|
||||
i18n: I18n;
|
||||
itemName: string;
|
||||
notificationType: typeof NotificationTypes.Change | typeof NotificationTypes.Add;
|
||||
theme: Theme;
|
||||
username?: string;
|
||||
};
|
||||
|
||||
export function CipherAction({
|
||||
@@ -17,14 +19,18 @@ export function CipherAction({
|
||||
/* no-op */
|
||||
},
|
||||
i18n,
|
||||
itemName,
|
||||
notificationType,
|
||||
theme,
|
||||
username,
|
||||
}: CipherActionProps) {
|
||||
return notificationType === NotificationTypes.Change
|
||||
? BadgeButton({
|
||||
buttonAction: handleAction,
|
||||
buttonText: i18n.notificationUpdate,
|
||||
itemName,
|
||||
theme,
|
||||
username,
|
||||
})
|
||||
: EditButton({
|
||||
buttonAction: handleAction,
|
||||
|
||||
@@ -32,14 +32,21 @@ export function CipherItem({
|
||||
notificationType,
|
||||
theme = ThemeTypes.Light,
|
||||
}: CipherItemProps) {
|
||||
const { icon } = cipher;
|
||||
const { icon, name, login } = cipher;
|
||||
const uri = (icon.imageEnabled && icon.image) || undefined;
|
||||
|
||||
let cipherActionButton = null;
|
||||
|
||||
if (notificationType === NotificationTypes.Change || notificationType === NotificationTypes.Add) {
|
||||
cipherActionButton = html`<div>
|
||||
${CipherAction({ handleAction, i18n, notificationType, theme })}
|
||||
${CipherAction({
|
||||
handleAction,
|
||||
i18n,
|
||||
itemName: name,
|
||||
notificationType,
|
||||
theme,
|
||||
username: login?.username,
|
||||
})}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ export type IconProps = {
|
||||
color?: string;
|
||||
disabled?: boolean;
|
||||
theme: Theme;
|
||||
ariaHidden?: boolean;
|
||||
};
|
||||
|
||||
export type Option = {
|
||||
|
||||
@@ -4,11 +4,16 @@ import { html } from "lit";
|
||||
import { IconProps } from "../common-types";
|
||||
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
|
||||
|
||||
export function AngleDown({ color, disabled, theme }: IconProps) {
|
||||
export function AngleDown({ ariaHidden = true, color, disabled, theme }: IconProps) {
|
||||
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
|
||||
|
||||
return html`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 8" fill="none">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 14 8"
|
||||
fill="none"
|
||||
aria-hidden="${ariaHidden}"
|
||||
>
|
||||
<path
|
||||
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
|
||||
d="M13.53.47a.75.75 0 0 0-1.06 0L7 5.94 1.53.47A.75.75 0 1 0 .47 1.53l6 6a.75.75 0 0 0 1.06 0l6-6a.75.75 0 0 0 0-1.06Z"
|
||||
|
||||
@@ -4,11 +4,16 @@ import { html } from "lit";
|
||||
import { IconProps } from "../common-types";
|
||||
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
|
||||
|
||||
export function AngleUp({ color, disabled, theme }: IconProps) {
|
||||
export function AngleUp({ ariaHidden = true, color, disabled, theme }: IconProps) {
|
||||
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
|
||||
|
||||
return html`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 8" fill="none">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 14 8"
|
||||
fill="none"
|
||||
aria-hidden="${ariaHidden}"
|
||||
>
|
||||
<path
|
||||
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
|
||||
d="M.47 7.53a.75.75 0 0 0 1.06 0L7 2.06l5.47 5.47a.75.75 0 1 0 1.06-1.06l-6-6a.75.75 0 0 0-1.06 0l-6 6a.75.75 0 0 0 0 1.06Z"
|
||||
|
||||
@@ -4,11 +4,16 @@ import { html } from "lit";
|
||||
import { IconProps } from "../common-types";
|
||||
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
|
||||
|
||||
export function Business({ color, disabled, theme }: IconProps) {
|
||||
export function Business({ ariaHidden = true, color, disabled, theme }: IconProps) {
|
||||
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
|
||||
|
||||
return html`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 16" fill="none">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 12 16"
|
||||
fill="none"
|
||||
aria-hidden="${ariaHidden}"
|
||||
>
|
||||
<path
|
||||
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
|
||||
d="M3.25 3a.75.75 0 0 0 0 1.5h1.5a.75.75 0 0 0 0-1.5h-1.5ZM7.25 3a.75.75 0 0 0 0 1.5h1.5a.75.75 0 0 0 0-1.5h-1.5ZM7.25 6a.75.75 0 0 0 0 1.5h1.5a.75.75 0 0 0 0-1.5h-1.5ZM6.5 9.75A.75.75 0 0 1 7.25 9h1.5a.75.75 0 0 1 0 1.5h-1.5a.75.75 0 0 1-.75-.75ZM2.5 6.75A.75.75 0 0 1 3.25 6h1.5a.75.75 0 0 1 0 1.5h-1.5a.75.75 0 0 1-.75-.75ZM3.25 9a.75.75 0 0 0 0 1.5h1.5a.75.75 0 0 0 0-1.5h-1.5Z"
|
||||
|
||||
@@ -4,11 +4,16 @@ import { html } from "lit";
|
||||
import { IconProps } from "../common-types";
|
||||
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
|
||||
|
||||
export function Close({ color, disabled, theme }: IconProps) {
|
||||
export function Close({ ariaHidden = true, color, disabled, theme }: IconProps) {
|
||||
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
|
||||
|
||||
return html`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14" fill="none">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 14 14"
|
||||
fill="none"
|
||||
aria-hidden="${ariaHidden}"
|
||||
>
|
||||
<path
|
||||
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
|
||||
d="M.22.22a.75.75 0 0 1 1.06 0L7 5.94 12.72.22a.75.75 0 1 1 1.06 1.06L8.06 7l5.72 5.72a.75.75 0 1 1-1.06 1.06L7 8.06l-5.72 5.72a.75.75 0 0 1-1.06-1.06L5.94 7 .22 1.28a.75.75 0 0 1 0-1.06Z"
|
||||
|
||||
@@ -4,11 +4,16 @@ import { html } from "lit";
|
||||
import { IconProps } from "../common-types";
|
||||
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
|
||||
|
||||
export function CollectionShared({ color, disabled, theme }: IconProps) {
|
||||
export function CollectionShared({ ariaHidden = true, color, disabled, theme }: IconProps) {
|
||||
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
|
||||
|
||||
return html`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14" fill="none">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 14 14"
|
||||
fill="none"
|
||||
aria-hidden="${ariaHidden}"
|
||||
>
|
||||
<path
|
||||
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
|
||||
d="M3.5.75A.75.75 0 0 1 4.25 0h5.5a.75.75 0 0 1 0 1.5h-5.5A.75.75 0 0 1 3.5.75ZM2.25 2a.75.75 0 0 0 0 1.5h9.5a.75.75 0 0 0 0-1.5h-9.5ZM6 8a1 1 0 1 1-2 0 1 1 0 0 1 2 0ZM10 8a1 1 0 1 1-2 0 1 1 0 0 1 2 0ZM7 11.46a1.928 1.928 0 0 0-.586-1.386 2.035 2.035 0 0 0-2.828 0A1.928 1.928 0 0 0 3 11.461c0 .298.241.539.54.539h2.92a.54.54 0 0 0 .54-.54ZM8 11.46a2.928 2.928 0 0 0-.371-1.426A2.005 2.005 0 0 1 9 9.5a2.035 2.035 0 0 1 1.414.574A1.928 1.928 0 0 1 11 11.461a.54.54 0 0 1-.54.539H7.904c.063-.168.097-.35.097-.54Z"
|
||||
|
||||
@@ -4,11 +4,16 @@ import { html } from "lit";
|
||||
import { IconProps } from "../common-types";
|
||||
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
|
||||
|
||||
export function ExclamationTriangle({ color, disabled, theme }: IconProps) {
|
||||
export function ExclamationTriangle({ ariaHidden = true, color, disabled, theme }: IconProps) {
|
||||
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
|
||||
|
||||
return html`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 15" fill="none">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 15"
|
||||
fill="none"
|
||||
aria-hidden="${ariaHidden}"
|
||||
>
|
||||
<path
|
||||
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
|
||||
d="M9 11C9 11.5523 8.55229 12 8 12C7.44772 12 7 11.5523 7 11C7 10.4477 7.44772 10 8 10C8.55229 10 9 10.4477 9 11Z"
|
||||
|
||||
@@ -4,11 +4,16 @@ import { html } from "lit";
|
||||
import { IconProps } from "../common-types";
|
||||
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
|
||||
|
||||
export function ExternalLink({ color, disabled, theme }: IconProps) {
|
||||
export function ExternalLink({ ariaHidden = true, color, disabled, theme }: IconProps) {
|
||||
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
|
||||
|
||||
return html`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14" fill="none">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 14 14"
|
||||
fill="none"
|
||||
aria-hidden="${ariaHidden}"
|
||||
>
|
||||
<path
|
||||
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
|
||||
d="M1.5 2.75c0-.69.56-1.25 1.25-1.25h3.5a.75.75 0 0 0 0-1.5h-3.5A2.75 2.75 0 0 0 0 2.75v8.5A2.75 2.75 0 0 0 2.75 14h8.5A2.75 2.75 0 0 0 14 11.25v-3.5a.75.75 0 0 0-1.5 0v3.5c0 .69-.56 1.25-1.25 1.25h-8.5c-.69 0-1.25-.56-1.25-1.25v-8.5Z"
|
||||
|
||||
@@ -4,11 +4,16 @@ import { html } from "lit";
|
||||
import { IconProps } from "../common-types";
|
||||
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
|
||||
|
||||
export function Family({ color, disabled, theme }: IconProps) {
|
||||
export function Family({ ariaHidden = true, color, disabled, theme }: IconProps) {
|
||||
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
|
||||
|
||||
return html`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
aria-hidden="${ariaHidden}"
|
||||
>
|
||||
<path
|
||||
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
|
||||
fill-rule="evenodd"
|
||||
|
||||
@@ -4,11 +4,16 @@ import { html } from "lit";
|
||||
import { IconProps } from "../common-types";
|
||||
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
|
||||
|
||||
export function Folder({ color, disabled, theme }: IconProps) {
|
||||
export function Folder({ ariaHidden = true, color, disabled, theme }: IconProps) {
|
||||
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
|
||||
|
||||
return html`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 13" fill="none">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 13"
|
||||
fill="none"
|
||||
aria-hidden="${ariaHidden}"
|
||||
>
|
||||
<path
|
||||
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
|
||||
d="M2 0a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8L6.586.586A2 2 0 0 0 5.172 0H2Zm5.379 3.5L5.525 1.646a.5.5 0 0 0-.353-.146H2a.5.5 0 0 0-.5.5v9a.5.5 0 0 0 .5.5h12a.5.5 0 0 0 .5-.5V4a.5.5 0 0 0-.5-.5H7.379Z"
|
||||
|
||||
@@ -4,11 +4,16 @@ import { html } from "lit";
|
||||
import { IconProps } from "../common-types";
|
||||
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
|
||||
|
||||
export function Globe({ color, disabled, theme }: IconProps) {
|
||||
export function Globe({ ariaHidden = true, color, disabled, theme }: IconProps) {
|
||||
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
|
||||
|
||||
return html`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
aria-hidden="${ariaHidden}"
|
||||
>
|
||||
<path
|
||||
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
|
||||
d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0Zm0 14.5c.23 0 .843-.226 1.487-1.514.524-1.048.906-2.526.994-4.236H5.519c.088 1.71.47 3.188.994 4.236C7.157 14.274 7.77 14.5 8 14.5ZM5.52 7.25h4.96c-.087-1.71-.47-3.188-.993-4.236C8.843 1.726 8.23 1.5 8 1.5c-.23 0-.843.226-1.487 1.514C5.99 4.062 5.607 5.54 5.52 7.25Zm6.463 0h2.474a6.506 6.506 0 0 0-3.766-5.168c.718 1.305 1.197 3.125 1.292 5.168Zm-7.966 0c.095-2.043.574-3.863 1.292-5.168A6.506 6.506 0 0 0 1.543 7.25h2.474Zm7.966 1.5c-.095 2.043-.574 3.863-1.292 5.168a6.506 6.506 0 0 0 3.766-5.168h-2.474Zm-6.677 5.185c-.718-1.305-1.197-3.125-1.292-5.168H1.54a6.506 6.506 0 0 0 3.766 5.168Z"
|
||||
|
||||
@@ -4,11 +4,16 @@ import { html } from "lit";
|
||||
import { IconProps } from "../common-types";
|
||||
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
|
||||
|
||||
export function PencilSquare({ color, disabled, theme }: IconProps) {
|
||||
export function PencilSquare({ ariaHidden = true, color, disabled, theme }: IconProps) {
|
||||
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
|
||||
|
||||
return html`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15" fill="none" aria-hidden="true">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 15 15"
|
||||
fill="none"
|
||||
aria-hidden="${ariaHidden}"
|
||||
>
|
||||
<path
|
||||
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
|
||||
d="M11.013.677a1.75 1.75 0 0 1 2.474 0l.836.836a1.75 1.75 0 0 1 0 2.475L9.03 9.28a.75.75 0 0 1-.348.197l-3 .75a.75.75 0 0 1-.91-.91l.75-3a.75.75 0 0 1 .198-.348L11.013.677Zm1.414 1.06a.25.25 0 0 0-.354 0l-.646.647a.75.75 0 0 1 .103.086l1 1a.751.751 0 0 1 .087.103l.646-.646a.25.25 0 0 0 0-.353l-.836-.836Zm-.854 2.88a.752.752 0 0 1-.103-.087l-1-1a.756.756 0 0 1-.087-.103L6.928 6.884 6.531 8.47l1.586-.397 3.456-3.456Z"
|
||||
|
||||
@@ -4,11 +4,16 @@ import { html } from "lit";
|
||||
import { IconProps } from "../common-types";
|
||||
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
|
||||
|
||||
export function Shield({ color, theme }: IconProps) {
|
||||
export function Shield({ ariaHidden = true, color, theme }: IconProps) {
|
||||
const shapeColor = color || themes[theme].brandLogo;
|
||||
|
||||
return html`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 16" fill="none">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 14 16"
|
||||
fill="none"
|
||||
aria-hidden="${ariaHidden}"
|
||||
>
|
||||
<path
|
||||
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
|
||||
d="M13.469.2A.647.647 0 0 0 13 0H1a.639.639 0 0 0-.468.2.641.641 0 0 0-.2.468v8a4.81 4.81 0 0 0 .348 1.777c.216.557.507 1.083.865 1.563.367.48.779.925 1.229 1.329.417.383.857.741 1.317 1.073.4.284.82.553 1.26.807.44.254.75.425.932.515.183.09.33.16.44.208.087.041.181.062.277.06a.58.58 0 0 0 .27-.063c.113-.05.259-.118.444-.208s.5-.262.932-.515c.432-.253.857-.523 1.26-.807.46-.332.9-.69 1.319-1.073.45-.404.861-.849 1.228-1.33.357-.48.648-1.005.865-1.562a4.79 4.79 0 0 0 .348-1.777v-8A.63.63 0 0 0 13.47.2Zm-1.547 8.54c0 2.9-4.921 5.392-4.921 5.392V1.714h4.92v7.027Z"
|
||||
|
||||
@@ -4,11 +4,16 @@ import { html } from "lit";
|
||||
import { IconProps } from "../common-types";
|
||||
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
|
||||
|
||||
export function User({ color, disabled, theme }: IconProps) {
|
||||
export function User({ ariaHidden = true, color, disabled, theme }: IconProps) {
|
||||
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
|
||||
|
||||
return html`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 15" fill="none">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 14 15"
|
||||
fill="none"
|
||||
aria-hidden="${ariaHidden}"
|
||||
>
|
||||
<path
|
||||
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
|
||||
d="M9.203 7.339a4 4 0 1 0-4.407 0A7.033 7.033 0 0 0 2.05 8.953a6.655 6.655 0 0 0-1.517 2.162A6.393 6.393 0 0 0 0 13.667C0 14.403.597 15 1.333 15h11.334c.736 0 1.333-.597 1.333-1.333 0-.876-.181-1.743-.533-2.552a6.654 6.654 0 0 0-1.517-2.162 7.032 7.032 0 0 0-2.747-1.614ZM9.5 4a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0Zm2.592 7.714c.247.57.384 1.175.405 1.786H1.503a4.897 4.897 0 0 1 .405-1.786 5.156 5.156 0 0 1 1.177-1.675 5.534 5.534 0 0 1 1.787-1.136A5.805 5.805 0 0 1 7 8.5c.732 0 1.456.137 2.128.403.673.265 1.28.652 1.787 1.136a5.156 5.156 0 0 1 1.177 1.675Z"
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Meta, StoryObj } from "@storybook/web-components";
|
||||
import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum";
|
||||
|
||||
import { CloseButton, CloseButtonProps } from "../../buttons/close-button";
|
||||
import { mockI18n } from "../mock-data";
|
||||
|
||||
export default {
|
||||
title: "Components/Buttons/Close Button",
|
||||
@@ -15,6 +16,7 @@ export default {
|
||||
handleCloseNotification: () => {
|
||||
alert("Close button clicked!");
|
||||
},
|
||||
i18n: mockI18n,
|
||||
},
|
||||
parameters: {
|
||||
design: {
|
||||
|
||||
@@ -131,12 +131,15 @@ export const mockI18n = {
|
||||
notificationUnlock: "Unlock",
|
||||
notificationUnlockDesc: "Unlock your Bitwarden vault to complete the autofill request.",
|
||||
notificationViewAria: `View $ITEMNAME$, opens in new window`,
|
||||
notificationNewItemAria: "New Item, opens in new window",
|
||||
saveAction: "Save",
|
||||
saveAsNewLoginAction: "Save as new login",
|
||||
saveFailure: "Error saving",
|
||||
saveFailureDetails: "Oh no! We couldn't save this. Try entering the details manually.",
|
||||
saveLogin: "Save login",
|
||||
selectItemAriaLabel: "Select $ITEMTYPE$, $ITEMNAME$",
|
||||
typeLogin: "Login",
|
||||
unlockToSave: "Unlock to save this login",
|
||||
updateLoginAction: "Update login",
|
||||
updateLogin: "Update existing login",
|
||||
vault: "Vault",
|
||||
|
||||
@@ -48,6 +48,7 @@ export function NotificationConfirmationBody({
|
||||
? NotificationConfirmationMessage({
|
||||
buttonAria,
|
||||
buttonText,
|
||||
error,
|
||||
itemName,
|
||||
message: confirmationMessage,
|
||||
messageDetails,
|
||||
@@ -62,7 +63,7 @@ export function NotificationConfirmationBody({
|
||||
export const iconContainerStyles = (error?: string | boolean) => css`
|
||||
> svg {
|
||||
width: ${!error ? "50px" : "40px"};
|
||||
height: fit-content;
|
||||
height: auto;
|
||||
}
|
||||
`;
|
||||
export const notificationConfirmationBodyStyles = ({ theme }: { theme: Theme }) => css`
|
||||
|
||||
@@ -45,7 +45,9 @@ export function NotificationConfirmationContainer({
|
||||
const headerMessage = getHeaderMessage(i18n, type, error);
|
||||
const confirmationMessage = getConfirmationMessage(i18n, type, error);
|
||||
const buttonText = error ? i18n.newItem : i18n.view;
|
||||
const buttonAria = chrome.i18n.getMessage("notificationViewAria", [itemName]);
|
||||
const buttonAria = error
|
||||
? i18n.notificationNewItemAria
|
||||
: chrome.i18n.getMessage("notificationViewAria", [itemName]);
|
||||
|
||||
let messageDetails: string | undefined;
|
||||
let remainingTasksCount: number | undefined;
|
||||
@@ -68,6 +70,7 @@ export function NotificationConfirmationContainer({
|
||||
<div class=${notificationContainerStyles(theme)}>
|
||||
${NotificationHeader({
|
||||
handleCloseNotification,
|
||||
i18n,
|
||||
message: headerMessage,
|
||||
theme,
|
||||
})}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { spacing, themes, typography } from "../../constants/styles";
|
||||
export type NotificationConfirmationMessageProps = {
|
||||
buttonAria?: string;
|
||||
buttonText?: string;
|
||||
error?: string;
|
||||
itemName?: string;
|
||||
message?: string;
|
||||
messageDetails?: string;
|
||||
@@ -18,6 +19,7 @@ export type NotificationConfirmationMessageProps = {
|
||||
export function NotificationConfirmationMessage({
|
||||
buttonAria,
|
||||
buttonText,
|
||||
error,
|
||||
itemName,
|
||||
message,
|
||||
messageDetails,
|
||||
@@ -29,7 +31,11 @@ export function NotificationConfirmationMessage({
|
||||
${message || buttonText
|
||||
? html`
|
||||
<div class=${singleLineWrapperStyles}>
|
||||
<span class=${itemNameStyles(theme)} title=${itemName}> ${itemName} </span>
|
||||
${!error && itemName
|
||||
? html`
|
||||
<span class=${itemNameStyles(theme)} title=${itemName}> ${itemName} </span>
|
||||
`
|
||||
: nothing}
|
||||
<span
|
||||
title=${message || buttonText}
|
||||
class=${notificationConfirmationMessageStyles(theme)}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { css } from "@emotion/css";
|
||||
import { html } from "lit";
|
||||
import { html, nothing } from "lit";
|
||||
|
||||
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums";
|
||||
|
||||
@@ -47,14 +47,14 @@ export function NotificationContainer({
|
||||
type,
|
||||
}: NotificationContainerProps) {
|
||||
const headerMessage = getHeaderMessage(i18n, type);
|
||||
const showBody = true;
|
||||
const showBody = type !== NotificationTypes.Unlock;
|
||||
|
||||
return html`
|
||||
<div class=${notificationContainerStyles(theme)}>
|
||||
${NotificationHeader({
|
||||
handleCloseNotification,
|
||||
i18n,
|
||||
message: headerMessage,
|
||||
standalone: showBody,
|
||||
theme,
|
||||
})}
|
||||
${showBody
|
||||
@@ -65,7 +65,7 @@ export function NotificationContainer({
|
||||
theme,
|
||||
i18n,
|
||||
})
|
||||
: null}
|
||||
: nothing}
|
||||
${NotificationFooter({
|
||||
handleSaveAction,
|
||||
collections,
|
||||
@@ -106,7 +106,7 @@ function getHeaderMessage(i18n: I18n, type?: NotificationType) {
|
||||
case NotificationTypes.Change:
|
||||
return i18n.updateLogin;
|
||||
case NotificationTypes.Unlock:
|
||||
return "";
|
||||
return i18n.unlockToSave;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,13 @@ export function NotificationFooter({
|
||||
handleSaveAction,
|
||||
}: NotificationFooterProps) {
|
||||
const isChangeNotification = notificationType === NotificationTypes.Change;
|
||||
const primaryButtonText = i18n.saveAction;
|
||||
const isUnlockNotification = notificationType === NotificationTypes.Unlock;
|
||||
|
||||
let primaryButtonText = i18n.saveAction;
|
||||
|
||||
if (isUnlockNotification) {
|
||||
primaryButtonText = i18n.notificationUnlock;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class=${[displayFlex, notificationFooterStyles({ isChangeNotification })]}>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { html } from "lit";
|
||||
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums";
|
||||
|
||||
import { CloseButton } from "../buttons/close-button";
|
||||
import { I18n } from "../common-types";
|
||||
import { spacing, themes } from "../constants/styles";
|
||||
import { BrandIconContainer } from "../icons/brand-icon-container";
|
||||
|
||||
@@ -16,6 +17,7 @@ const { css } = createEmotion({
|
||||
});
|
||||
|
||||
export type NotificationHeaderProps = {
|
||||
i18n: I18n;
|
||||
message?: string;
|
||||
standalone?: boolean;
|
||||
theme: Theme;
|
||||
@@ -23,6 +25,7 @@ export type NotificationHeaderProps = {
|
||||
};
|
||||
|
||||
export function NotificationHeader({
|
||||
i18n,
|
||||
message,
|
||||
standalone = false,
|
||||
theme = ThemeTypes.Light,
|
||||
@@ -35,7 +38,7 @@ export function NotificationHeader({
|
||||
<div class=${notificationHeaderStyles({ standalone, theme })}>
|
||||
${showIcon ? BrandIconContainer({ theme }) : null}
|
||||
${message ? NotificationHeaderMessage({ message, theme }) : null}
|
||||
${isDismissable ? CloseButton({ handleCloseNotification, theme }) : null}
|
||||
${isDismissable ? CloseButton({ handleCloseNotification, i18n, theme }) : null}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -56,8 +59,8 @@ const notificationHeaderStyles = ({
|
||||
white-space: nowrap;
|
||||
|
||||
${standalone
|
||||
? css`
|
||||
? css``
|
||||
: css`
|
||||
border-bottom: 0.5px solid ${themes[theme].secondary["300"]};
|
||||
`
|
||||
: css``}
|
||||
`}
|
||||
`;
|
||||
|
||||
@@ -13,11 +13,19 @@ const { css } = createEmotion({
|
||||
});
|
||||
|
||||
export type OptionItemProps = Option & {
|
||||
contextLabel?: string;
|
||||
theme: Theme;
|
||||
handleSelection: () => void;
|
||||
};
|
||||
|
||||
export function OptionItem({ icon, text, value, theme, handleSelection }: OptionItemProps) {
|
||||
export function OptionItem({
|
||||
contextLabel,
|
||||
icon,
|
||||
text,
|
||||
theme,
|
||||
value,
|
||||
handleSelection,
|
||||
}: OptionItemProps) {
|
||||
const handleSelectionKeyUpProxy = (event: KeyboardEvent) => {
|
||||
const listenedForKeys = new Set(["Enter", "Space"]);
|
||||
if (listenedForKeys.has(event.code) && event.target instanceof Element) {
|
||||
@@ -29,12 +37,18 @@ export function OptionItem({ icon, text, value, theme, handleSelection }: Option
|
||||
|
||||
const iconProps: IconProps = { color: themes[theme].text.main, theme };
|
||||
const itemIcon = icon?.(iconProps);
|
||||
const ariaLabel =
|
||||
contextLabel && text
|
||||
? chrome.i18n.getMessage("selectItemAriaLabel", [contextLabel, text])
|
||||
: text;
|
||||
|
||||
return html`<div
|
||||
class=${optionItemStyles}
|
||||
key=${value}
|
||||
tabindex="0"
|
||||
title=${text}
|
||||
role="option"
|
||||
aria-label=${ariaLabel}
|
||||
@click=${handleSelection}
|
||||
@keyup=${handleSelectionKeyUpProxy}
|
||||
>
|
||||
|
||||
@@ -33,17 +33,41 @@ export function OptionItems({
|
||||
const isSafari = false;
|
||||
|
||||
return html`
|
||||
<div class=${optionsStyles({ theme, topOffset })} key="container">
|
||||
<div
|
||||
class=${optionsStyles({ theme, topOffset })}
|
||||
key="container"
|
||||
@keyup=${(e: KeyboardEvent) => handleMenuKeyUp(e)}
|
||||
>
|
||||
${label ? html`<div class=${optionsLabelStyles({ theme })}>${label}</div>` : nothing}
|
||||
<div class=${optionsWrapper({ isSafari, theme })}>
|
||||
${options.map((option) =>
|
||||
OptionItem({ ...option, theme, handleSelection: () => handleOptionSelection(option) }),
|
||||
OptionItem({
|
||||
...option,
|
||||
theme,
|
||||
contextLabel: label,
|
||||
handleSelection: () => handleOptionSelection(option),
|
||||
}),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function handleMenuKeyUp(event: KeyboardEvent) {
|
||||
const items = [
|
||||
...(event.currentTarget as HTMLElement).querySelectorAll<HTMLElement>('[tabindex="0"]'),
|
||||
];
|
||||
const index = items.indexOf(document.activeElement as HTMLElement);
|
||||
const direction = event.key === "ArrowDown" ? 1 : event.key === "ArrowUp" ? -1 : 0;
|
||||
|
||||
if (index === -1 || direction === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
items[(index + direction + items.length) % items.length]?.focus();
|
||||
}
|
||||
|
||||
const optionsStyles = ({ theme, topOffset }: { theme: Theme; topOffset: number }) => css`
|
||||
${typography.body1}
|
||||
|
||||
|
||||
@@ -48,10 +48,18 @@ export class OptionSelection extends LitElement {
|
||||
@state()
|
||||
private selection?: Option;
|
||||
|
||||
private handleButtonClick = (event: Event) => {
|
||||
private static currentOpenInstance: OptionSelection | null = null;
|
||||
|
||||
private handleButtonClick = async (event: Event) => {
|
||||
if (!this.disabled) {
|
||||
// Menu is about to be shown
|
||||
if (!this.showMenu) {
|
||||
const isOpening = !this.showMenu;
|
||||
|
||||
if (isOpening) {
|
||||
if (OptionSelection.currentOpenInstance && OptionSelection.currentOpenInstance !== this) {
|
||||
OptionSelection.currentOpenInstance.showMenu = false;
|
||||
}
|
||||
OptionSelection.currentOpenInstance = this;
|
||||
|
||||
this.menuTopOffset = this.offsetTop;
|
||||
|
||||
// Distance from right edge of button to left edge of the viewport
|
||||
@@ -71,9 +79,29 @@ export class OptionSelection extends LitElement {
|
||||
optionsMenuItemMaxWidth + optionItemIconWidth + 2 + 8 + 12 * 2;
|
||||
|
||||
this.menuIsEndJustified = distanceFromViewportRightEdge < maxDifferenceThreshold;
|
||||
} else {
|
||||
if (OptionSelection.currentOpenInstance === this) {
|
||||
OptionSelection.currentOpenInstance = null;
|
||||
}
|
||||
}
|
||||
|
||||
this.showMenu = !this.showMenu;
|
||||
this.showMenu = isOpening;
|
||||
|
||||
if (this.showMenu) {
|
||||
await this.updateComplete;
|
||||
const firstItem = this.querySelector('#option-menu [tabindex="0"]') as HTMLElement;
|
||||
firstItem?.focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private handleFocusOut = (event: FocusEvent) => {
|
||||
const relatedTarget = event.relatedTarget;
|
||||
if (!(relatedTarget instanceof Node) || !this.contains(relatedTarget)) {
|
||||
this.showMenu = false;
|
||||
if (OptionSelection.currentOpenInstance === this) {
|
||||
OptionSelection.currentOpenInstance = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -95,7 +123,10 @@ export class OptionSelection extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class=${optionSelectionStyles({ menuIsEndJustified: this.menuIsEndJustified })}>
|
||||
<div
|
||||
class=${optionSelectionStyles({ menuIsEndJustified: this.menuIsEndJustified })}
|
||||
@focusout=${this.handleFocusOut}
|
||||
>
|
||||
${OptionSelectionButton({
|
||||
disabled: this.disabled,
|
||||
icon: this.selection?.icon,
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
NotificationBarWindowMessage,
|
||||
NotificationBarIframeInitData,
|
||||
NotificationType,
|
||||
NotificationTypes,
|
||||
} from "./abstractions/notification-bar";
|
||||
|
||||
const logService = new ConsoleLogService(false);
|
||||
@@ -89,6 +90,7 @@ function getI18n() {
|
||||
saveFailureDetails: chrome.i18n.getMessage("saveFailureDetails"),
|
||||
saveLogin: chrome.i18n.getMessage("saveLogin"),
|
||||
typeLogin: chrome.i18n.getMessage("typeLogin"),
|
||||
unlockToSave: chrome.i18n.getMessage("unlockToSave"),
|
||||
updateLogin: chrome.i18n.getMessage("updateLogin"),
|
||||
updateLoginAction: chrome.i18n.getMessage("updateLoginAction"),
|
||||
vault: chrome.i18n.getMessage("vault"),
|
||||
@@ -154,6 +156,26 @@ async function initNotificationBar(message: NotificationBarWindowMessage) {
|
||||
// Current implementations utilize a require for scss files which creates the need to remove the node.
|
||||
document.head.querySelectorAll('link[rel="stylesheet"]').forEach((node) => node.remove());
|
||||
|
||||
if (isVaultLocked) {
|
||||
return render(
|
||||
NotificationContainer({
|
||||
...notificationBarIframeInitData,
|
||||
type: NotificationTypes.Unlock,
|
||||
theme: resolvedTheme,
|
||||
personalVaultIsAllowed: !personalVaultDisallowed,
|
||||
handleCloseNotification,
|
||||
handleSaveAction: (e) => {
|
||||
sendSaveCipherMessage(true);
|
||||
|
||||
// @TODO can't close before vault has finished decrypting, but can't leave open during long decrypt because it looks like the experience has failed
|
||||
},
|
||||
handleEditOrUpdateAction,
|
||||
i18n,
|
||||
}),
|
||||
document.body,
|
||||
);
|
||||
}
|
||||
|
||||
const orgId = selectedVaultSignal.get();
|
||||
|
||||
await Promise.all([
|
||||
|
||||
@@ -20,7 +20,7 @@ export class OverlayNotificationsContentService
|
||||
private notificationBarIframeElement: HTMLIFrameElement | null = null;
|
||||
private currentNotificationBarType: string | null = null;
|
||||
private removeTabFromNotificationQueueTypes = new Set(["add", "change"]);
|
||||
private notificationRefreshFlag: boolean;
|
||||
private notificationRefreshFlag: boolean = false;
|
||||
private notificationBarElementStyles: Partial<CSSStyleDeclaration> = {
|
||||
height: "82px",
|
||||
width: "430px",
|
||||
@@ -60,6 +60,7 @@ export class OverlayNotificationsContentService
|
||||
void sendExtensionMessage("checkNotificationQueue");
|
||||
void sendExtensionMessage("notificationRefreshFlagValue").then((notificationRefreshFlag) => {
|
||||
this.notificationRefreshFlag = !!notificationRefreshFlag;
|
||||
this.setNotificationRefreshBarHeight();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -228,15 +229,31 @@ export class OverlayNotificationsContentService
|
||||
this.notificationBarElement.id = "bit-notification-bar";
|
||||
|
||||
setElementStyles(this.notificationBarElement, this.notificationBarElementStyles, true);
|
||||
|
||||
if (this.notificationRefreshFlag) {
|
||||
setElementStyles(this.notificationBarElement, { height: "400px", right: "0" }, true);
|
||||
}
|
||||
this.setNotificationRefreshBarHeight();
|
||||
|
||||
this.notificationBarElement.appendChild(this.notificationBarIframeElement);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the height of the notification bar based on the value of `notificationRefreshFlag`.
|
||||
* If the flag is `true`, the bar is expanded to 400px and aligned right.
|
||||
* If the flag is `false`, `null`, or `undefined`, it defaults to height of 82px.
|
||||
* Skips if the notification bar element has not yet been created.
|
||||
*
|
||||
*/
|
||||
private setNotificationRefreshBarHeight() {
|
||||
const isNotificationV3 = !!this.notificationRefreshFlag;
|
||||
|
||||
if (!this.notificationBarElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNotificationV3) {
|
||||
setElementStyles(this.notificationBarElement, { height: "400px", right: "0" }, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the message listener for the initialization of the notification bar.
|
||||
* This will send the initialization data to the notification bar iframe.
|
||||
|
||||
@@ -216,9 +216,7 @@ export class Fido2Component implements OnInit, OnDestroy {
|
||||
this.ciphers = await Promise.all(
|
||||
message.cipherIds.map(async (cipherId) => {
|
||||
const cipher = await this.cipherService.get(cipherId, activeUserId);
|
||||
return cipher.decrypt(
|
||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
||||
);
|
||||
return this.cipherService.decrypt(cipher, activeUserId);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -237,9 +235,7 @@ export class Fido2Component implements OnInit, OnDestroy {
|
||||
this.ciphers = await Promise.all(
|
||||
message.existingCipherIds.map(async (cipherId) => {
|
||||
const cipher = await this.cipherService.get(cipherId, activeUserId);
|
||||
return cipher.decrypt(
|
||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
||||
);
|
||||
return this.cipherService.decrypt(cipher, activeUserId);
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -4,14 +4,14 @@ import { CommonModule } from "@angular/common";
|
||||
import { Component, DestroyRef, OnInit } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import {
|
||||
FormBuilder,
|
||||
FormControl,
|
||||
FormGroup,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
FormBuilder,
|
||||
FormGroup,
|
||||
FormControl,
|
||||
} from "@angular/forms";
|
||||
import { RouterModule } from "@angular/router";
|
||||
import { Observable, filter, firstValueFrom, map, switchMap } from "rxjs";
|
||||
import { filter, firstValueFrom, Observable, switchMap } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
@@ -55,7 +55,7 @@ import {
|
||||
SelectModule,
|
||||
TypographyModule,
|
||||
} from "@bitwarden/components";
|
||||
import { SpotlightComponent, VaultNudgesService, VaultNudgeType } from "@bitwarden/vault";
|
||||
import { NudgesService, NudgeType, SpotlightComponent } from "@bitwarden/vault";
|
||||
|
||||
import { AutofillBrowserSettingsService } from "../../../autofill/services/autofill-browser-settings.service";
|
||||
import { BrowserApi } from "../../../platform/browser/browser-api";
|
||||
@@ -108,9 +108,7 @@ export class AutofillComponent implements OnInit {
|
||||
protected showSpotlightNudge$: Observable<boolean> = this.accountService.activeAccount$.pipe(
|
||||
filter((account): account is Account => account !== null),
|
||||
switchMap((account) =>
|
||||
this.vaultNudgesService
|
||||
.showNudge$(VaultNudgeType.AutofillNudge, account.id)
|
||||
.pipe(map((nudgeStatus) => !nudgeStatus.hasSpotlightDismissed)),
|
||||
this.nudgesService.showNudgeSpotlight$(NudgeType.AutofillNudge, account.id),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -155,7 +153,7 @@ export class AutofillComponent implements OnInit {
|
||||
private configService: ConfigService,
|
||||
private formBuilder: FormBuilder,
|
||||
private destroyRef: DestroyRef,
|
||||
private vaultNudgesService: VaultNudgesService,
|
||||
private nudgesService: NudgesService,
|
||||
private accountService: AccountService,
|
||||
private autofillBrowserSettingsService: AutofillBrowserSettingsService,
|
||||
) {
|
||||
@@ -343,8 +341,8 @@ export class AutofillComponent implements OnInit {
|
||||
}
|
||||
|
||||
async dismissSpotlight() {
|
||||
await this.vaultNudgesService.dismissNudge(
|
||||
VaultNudgeType.AutofillNudge,
|
||||
await this.nudgesService.dismissNudge(
|
||||
NudgeType.AutofillNudge,
|
||||
await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -183,6 +183,7 @@ import { SendStateProvider } from "@bitwarden/common/tools/send/services/send-st
|
||||
import { SendService } from "@bitwarden/common/tools/send/services/send.service";
|
||||
import { InternalSendService as InternalSendServiceAbstraction } from "@bitwarden/common/tools/send/services/send.service.abstraction";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherEncryptionService } from "@bitwarden/common/vault/abstractions/cipher-encryption.service";
|
||||
import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherFileUploadService as CipherFileUploadServiceAbstraction } from "@bitwarden/common/vault/abstractions/file-upload/cipher-file-upload.service";
|
||||
import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction";
|
||||
@@ -199,6 +200,7 @@ import {
|
||||
DefaultCipherAuthorizationService,
|
||||
} from "@bitwarden/common/vault/services/cipher-authorization.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/services/cipher.service";
|
||||
import { DefaultCipherEncryptionService } from "@bitwarden/common/vault/services/default-cipher-encryption.service";
|
||||
import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service";
|
||||
import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service";
|
||||
import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service";
|
||||
@@ -408,6 +410,7 @@ export default class MainBackground {
|
||||
endUserNotificationService: EndUserNotificationService;
|
||||
inlineMenuFieldQualificationService: InlineMenuFieldQualificationService;
|
||||
taskService: TaskService;
|
||||
cipherEncryptionService: CipherEncryptionService;
|
||||
|
||||
ipcContentScriptManagerService: IpcContentScriptManagerService;
|
||||
ipcService: IpcService;
|
||||
@@ -856,6 +859,11 @@ export default class MainBackground {
|
||||
|
||||
this.bulkEncryptService = new FallbackBulkEncryptService(this.encryptService);
|
||||
|
||||
this.cipherEncryptionService = new DefaultCipherEncryptionService(
|
||||
this.sdkService,
|
||||
this.logService,
|
||||
);
|
||||
|
||||
this.cipherService = new CipherService(
|
||||
this.keyService,
|
||||
this.domainSettingsService,
|
||||
@@ -871,6 +879,7 @@ export default class MainBackground {
|
||||
this.stateProvider,
|
||||
this.accountService,
|
||||
this.logService,
|
||||
this.cipherEncryptionService,
|
||||
);
|
||||
this.folderService = new FolderService(
|
||||
this.keyService,
|
||||
|
||||
@@ -5,5 +5,6 @@ import { RemovePasswordComponent as BaseRemovePasswordComponent } from "@bitward
|
||||
@Component({
|
||||
selector: "app-remove-password",
|
||||
templateUrl: "remove-password.component.html",
|
||||
standalone: false,
|
||||
})
|
||||
export class RemovePasswordComponent extends BaseRemovePasswordComponent {}
|
||||
|
||||
@@ -50,17 +50,40 @@ export class PopupSizeService {
|
||||
PopupSizeService.setStyle(width);
|
||||
localStorage.setItem(PopupSizeService.LocalStorageKey, width);
|
||||
});
|
||||
}
|
||||
|
||||
async setHeight() {
|
||||
const isInChromeTab = await BrowserPopupUtils.isInTab();
|
||||
|
||||
/**
|
||||
* To support both browser default zoom and system default zoom, we need to take into account
|
||||
* the full screen height. When system default zoom is >100%, window.innerHeight still outputs
|
||||
* a height equivalent to what it would be at 100%, which can cause the extension window to
|
||||
* render as too tall. So if the screen height is smaller than the max possible extension height,
|
||||
* we should use that to set our extension height. Otherwise, we want to use the window.innerHeight
|
||||
* to support browser zoom.
|
||||
*
|
||||
* This is basically a workaround for what we consider a bug with browsers reporting the wrong
|
||||
* available innerHeight when system zoom is turned on. If that gets fixed, we can remove the code
|
||||
* checking the screen height.
|
||||
*/
|
||||
const MAX_EXT_HEIGHT = 600;
|
||||
const extensionInnerHeight = window.innerHeight;
|
||||
// Use a 100px offset when calculating screen height to account for browser container elements
|
||||
const screenAvailHeight = window.screen.availHeight - 100;
|
||||
const availHeight =
|
||||
screenAvailHeight < MAX_EXT_HEIGHT ? screenAvailHeight : extensionInnerHeight;
|
||||
|
||||
if (!BrowserPopupUtils.inPopup(window) || isInChromeTab) {
|
||||
window.document.body.classList.add("body-full");
|
||||
} else if (window.innerHeight < 400) {
|
||||
window.document.body.classList.add("body-xxs");
|
||||
} else if (window.innerHeight < 500) {
|
||||
window.document.body.classList.add("body-xs");
|
||||
} else if (window.innerHeight < 600) {
|
||||
window.document.body.classList.add("body-sm");
|
||||
window.document.documentElement.classList.add("body-full");
|
||||
} else if (availHeight < 300) {
|
||||
window.document.documentElement.classList.add("body-3xs");
|
||||
} else if (availHeight < 400) {
|
||||
window.document.documentElement.classList.add("body-xxs");
|
||||
} else if (availHeight < 500) {
|
||||
window.document.documentElement.classList.add("body-xs");
|
||||
} else if (availHeight < 600) {
|
||||
window.document.documentElement.classList.add("body-sm");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,10 @@ import { PopupRouterCacheService, popupRouterCacheGuard } from "./popup-router-c
|
||||
|
||||
const flushPromises = async () => await new Promise(process.nextTick);
|
||||
|
||||
@Component({ template: "" })
|
||||
@Component({
|
||||
template: "",
|
||||
standalone: false,
|
||||
})
|
||||
export class EmptyComponent {}
|
||||
|
||||
describe("Popup router cache guard", () => {
|
||||
|
||||
@@ -19,10 +19,16 @@ import {
|
||||
|
||||
import { PopupViewCacheService } from "./popup-view-cache.service";
|
||||
|
||||
@Component({ template: "" })
|
||||
@Component({
|
||||
template: "",
|
||||
standalone: false,
|
||||
})
|
||||
export class EmptyComponent {}
|
||||
|
||||
@Component({ template: "" })
|
||||
@Component({
|
||||
template: "",
|
||||
standalone: false,
|
||||
})
|
||||
export class TestComponent {
|
||||
private viewCacheService = inject(PopupViewCacheService);
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
import { BiometricsService, BiometricStateService } from "@bitwarden/key-management";
|
||||
|
||||
import { PopupCompactModeService } from "../platform/popup/layout/popup-compact-mode.service";
|
||||
import { PopupSizeService } from "../platform/popup/layout/popup-size.service";
|
||||
import { initPopupClosedListener } from "../platform/services/popup-view-cache-background.service";
|
||||
import { VaultBrowserStateService } from "../vault/services/vault-browser-state.service";
|
||||
|
||||
@@ -42,6 +43,7 @@ import { DesktopSyncVerificationDialogComponent } from "./components/desktop-syn
|
||||
</div>
|
||||
<bit-toast-container></bit-toast-container>
|
||||
`,
|
||||
standalone: false,
|
||||
})
|
||||
export class AppComponent implements OnInit, OnDestroy {
|
||||
private compactModeService = inject(PopupCompactModeService);
|
||||
@@ -71,6 +73,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
private biometricStateService: BiometricStateService,
|
||||
private biometricsService: BiometricsService,
|
||||
private deviceTrustToastService: DeviceTrustToastService,
|
||||
private popupSizeService: PopupSizeService,
|
||||
) {
|
||||
this.deviceTrustToastService.setupListeners$.pipe(takeUntilDestroyed()).subscribe();
|
||||
}
|
||||
@@ -79,6 +82,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
initPopupClosedListener();
|
||||
|
||||
this.compactModeService.init();
|
||||
await this.popupSizeService.setHeight();
|
||||
|
||||
// Component states must not persist between closing and reopening the popup, otherwise they become dead objects
|
||||
// Clear them aggressively to make sure this doesn't occur
|
||||
|
||||
@@ -22,5 +22,6 @@ import { UserVerificationComponent as BaseComponent } from "@bitwarden/angular/a
|
||||
transition(":enter", [style({ opacity: 0 }), animate("100ms", style({ opacity: 1 }))]),
|
||||
]),
|
||||
],
|
||||
standalone: false,
|
||||
})
|
||||
export class UserVerificationComponent extends BaseComponent {}
|
||||
|
||||
@@ -8,6 +8,34 @@
|
||||
|
||||
html {
|
||||
overflow: hidden;
|
||||
min-height: 600px;
|
||||
height: 100%;
|
||||
|
||||
&.body-sm {
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
&.body-xs {
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
&.body-xxs {
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
&.body-3xs {
|
||||
min-height: 240px;
|
||||
}
|
||||
|
||||
&.body-full {
|
||||
min-height: unset;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
& body {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
@@ -20,9 +48,9 @@ body {
|
||||
|
||||
body {
|
||||
width: 380px;
|
||||
height: 600px;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
min-height: inherit;
|
||||
overflow: hidden;
|
||||
color: $text-color;
|
||||
background-color: $background-color;
|
||||
@@ -31,23 +59,6 @@ body {
|
||||
color: themed("textColor");
|
||||
background-color: themed("backgroundColor");
|
||||
}
|
||||
|
||||
&.body-sm {
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
&.body-xs {
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
&.body-xxs {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
&.body-full {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
h1,
|
||||
|
||||
@@ -6,18 +6,19 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { Icons } from "@bitwarden/components";
|
||||
import { VaultNudgesService } from "@bitwarden/vault";
|
||||
import { NudgesService } from "@bitwarden/vault";
|
||||
|
||||
import { NavButton } from "../platform/popup/layout/popup-tab-navigation.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-tabs-v2",
|
||||
templateUrl: "./tabs-v2.component.html",
|
||||
standalone: false,
|
||||
})
|
||||
export class TabsV2Component {
|
||||
private hasActiveBadges$ = this.accountService.activeAccount$
|
||||
.pipe(getUserId)
|
||||
.pipe(switchMap((userId) => this.vaultNudgesService.hasActiveBadges$(userId)));
|
||||
.pipe(switchMap((userId) => this.nudgesService.hasActiveBadges$(userId)));
|
||||
protected navButtons$: Observable<NavButton[]> = combineLatest([
|
||||
this.configService.getFeatureFlag$(FeatureFlag.PM8851_BrowserOnboardingNudge),
|
||||
this.hasActiveBadges$,
|
||||
@@ -53,7 +54,7 @@ export class TabsV2Component {
|
||||
}),
|
||||
);
|
||||
constructor(
|
||||
private vaultNudgesService: VaultNudgesService,
|
||||
private nudgesService: NudgesService,
|
||||
private accountService: AccountService,
|
||||
private readonly configService: ConfigService,
|
||||
) {}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
*ngIf="!isBrowserAutofillSettingOverridden && (showAutofillBadge$ | async)"
|
||||
bitBadge
|
||||
variant="notification"
|
||||
[attr.aria-label]="'nudgeBadgeAria' | i18n"
|
||||
>1</span
|
||||
>
|
||||
</div>
|
||||
@@ -40,7 +41,7 @@
|
||||
<a
|
||||
bit-item-content
|
||||
routerLink="/vault-settings"
|
||||
(click)="dismissBadge(VaultNudgeType.EmptyVaultNudge)"
|
||||
(click)="dismissBadge(NudgeType.EmptyVaultNudge)"
|
||||
>
|
||||
<i slot="start" class="bwi bwi-vault" aria-hidden="true"></i>
|
||||
<div class="tw-flex tw-items-center tw-justify-center">
|
||||
@@ -50,9 +51,10 @@
|
||||
Will make this dynamic when more nudges are added
|
||||
-->
|
||||
<span
|
||||
*ngIf="!(showVaultBadge$ | async)?.hasBadgeDismissed"
|
||||
*ngIf="showVaultBadge$ | async"
|
||||
bitBadge
|
||||
variant="notification"
|
||||
[attr.aria-label]="'nudgeBadgeAria' | i18n"
|
||||
>1</span
|
||||
>
|
||||
</div>
|
||||
@@ -80,9 +82,10 @@
|
||||
<div class="tw-flex tw-items-center tw-justify-center">
|
||||
<p class="tw-pr-2">{{ "downloadBitwardenOnAllDevices" | i18n }}</p>
|
||||
<span
|
||||
*ngIf="(downloadBitwardenNudgeStatus$ | async)?.hasBadgeDismissed === false"
|
||||
*ngIf="downloadBitwardenNudgeStatus$ | async"
|
||||
bitBadge
|
||||
variant="notification"
|
||||
[attr.aria-label]="'nudgeBadgeAria' | i18n"
|
||||
>1
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -17,7 +17,7 @@ import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { BadgeComponent, ItemModule } from "@bitwarden/components";
|
||||
import { NudgeStatus, VaultNudgesService, VaultNudgeType } from "@bitwarden/vault";
|
||||
import { NudgesService, NudgeType } from "@bitwarden/vault";
|
||||
|
||||
import { CurrentAccountComponent } from "../../../auth/popup/account-switching/current-account.component";
|
||||
import { AutofillBrowserSettingsService } from "../../../autofill/services/autofill-browser-settings.service";
|
||||
@@ -42,7 +42,7 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co
|
||||
],
|
||||
})
|
||||
export class SettingsV2Component implements OnInit {
|
||||
VaultNudgeType = VaultNudgeType;
|
||||
NudgeType = NudgeType;
|
||||
activeUserId: UserId | null = null;
|
||||
protected isBrowserAutofillSettingOverridden = false;
|
||||
|
||||
@@ -51,15 +51,15 @@ export class SettingsV2Component implements OnInit {
|
||||
shareReplay({ bufferSize: 1, refCount: true }),
|
||||
);
|
||||
|
||||
downloadBitwardenNudgeStatus$: Observable<NudgeStatus> = this.authenticatedAccount$.pipe(
|
||||
downloadBitwardenNudgeStatus$: Observable<boolean> = this.authenticatedAccount$.pipe(
|
||||
switchMap((account) =>
|
||||
this.vaultNudgesService.showNudge$(VaultNudgeType.DownloadBitwarden, account.id),
|
||||
this.nudgesService.showNudgeBadge$(NudgeType.DownloadBitwarden, account.id),
|
||||
),
|
||||
);
|
||||
|
||||
showVaultBadge$: Observable<NudgeStatus> = this.authenticatedAccount$.pipe(
|
||||
showVaultBadge$: Observable<boolean> = this.authenticatedAccount$.pipe(
|
||||
switchMap((account) =>
|
||||
this.vaultNudgesService.showNudge$(VaultNudgeType.EmptyVaultNudge, account.id),
|
||||
this.nudgesService.showNudgeBadge$(NudgeType.EmptyVaultNudge, account.id),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -68,9 +68,9 @@ export class SettingsV2Component implements OnInit {
|
||||
this.authenticatedAccount$,
|
||||
]).pipe(
|
||||
switchMap(([defaultBrowserAutofillDisabled, account]) =>
|
||||
this.vaultNudgesService.showNudge$(VaultNudgeType.AutofillNudge, account.id).pipe(
|
||||
map((nudgeStatus) => {
|
||||
return !defaultBrowserAutofillDisabled && nudgeStatus.hasBadgeDismissed === false;
|
||||
this.nudgesService.showNudgeBadge$(NudgeType.AutofillNudge, account.id).pipe(
|
||||
map((badgeStatus) => {
|
||||
return !defaultBrowserAutofillDisabled && badgeStatus;
|
||||
}),
|
||||
),
|
||||
),
|
||||
@@ -81,7 +81,7 @@ export class SettingsV2Component implements OnInit {
|
||||
);
|
||||
|
||||
constructor(
|
||||
private readonly vaultNudgesService: VaultNudgesService,
|
||||
private readonly nudgesService: NudgesService,
|
||||
private readonly accountService: AccountService,
|
||||
private readonly autofillBrowserSettingsService: AutofillBrowserSettingsService,
|
||||
private readonly configService: ConfigService,
|
||||
@@ -94,10 +94,10 @@ export class SettingsV2Component implements OnInit {
|
||||
);
|
||||
}
|
||||
|
||||
async dismissBadge(type: VaultNudgeType) {
|
||||
if (!(await firstValueFrom(this.showVaultBadge$)).hasBadgeDismissed) {
|
||||
async dismissBadge(type: NudgeType) {
|
||||
if (await firstValueFrom(this.showVaultBadge$)) {
|
||||
const account = await firstValueFrom(this.authenticatedAccount$);
|
||||
await this.vaultNudgesService.dismissNudge(type, account.id as UserId, true);
|
||||
await this.nudgesService.dismissNudge(type, account.id as UserId, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { inject } from "@angular/core";
|
||||
import { CanActivateFn } from "@angular/router";
|
||||
import { switchMap, tap } from "rxjs";
|
||||
import { CanActivateFn, Router } from "@angular/router";
|
||||
import { map, switchMap } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@@ -13,18 +13,22 @@ export const canAccessAtRiskPasswords: CanActivateFn = () => {
|
||||
const taskService = inject(TaskService);
|
||||
const toastService = inject(ToastService);
|
||||
const i18nService = inject(I18nService);
|
||||
const router = inject(Router);
|
||||
|
||||
return accountService.activeAccount$.pipe(
|
||||
filterOutNullish(),
|
||||
switchMap((user) => taskService.tasksEnabled$(user.id)),
|
||||
tap((tasksEnabled) => {
|
||||
map((tasksEnabled) => {
|
||||
if (!tasksEnabled) {
|
||||
toastService.showToast({
|
||||
variant: "error",
|
||||
title: "",
|
||||
message: i18nService.t("accessDenied"),
|
||||
message: i18nService.t("noPermissionsViewPage"),
|
||||
});
|
||||
|
||||
return router.createUrlTree(["/tabs/vault"]);
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -11,7 +11,6 @@ import { CollectionService } from "@bitwarden/admin-console/common";
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { OrgKey, UserKey } from "@bitwarden/common/types/key";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import {
|
||||
@@ -66,11 +65,7 @@ export class AssignCollections {
|
||||
route.queryParams.pipe(
|
||||
switchMap(async ({ cipherId }) => {
|
||||
const cipherDomain = await this.cipherService.get(cipherId, userId);
|
||||
const key: UserKey | OrgKey = await this.cipherService.getKeyForCipherKeyDecryption(
|
||||
cipherDomain,
|
||||
userId,
|
||||
);
|
||||
return cipherDomain.decrypt(key);
|
||||
return await this.cipherService.decrypt(cipherDomain, userId);
|
||||
}),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -81,6 +81,7 @@ describe("OpenAttachmentsComponent", () => {
|
||||
useValue: {
|
||||
get: getCipher,
|
||||
getKeyForCipherKeyDecryption: () => Promise.resolve(null),
|
||||
decrypt: jest.fn().mockResolvedValue(cipherView),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -81,9 +81,7 @@ export class OpenAttachmentsComponent implements OnInit {
|
||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
||||
);
|
||||
const cipherDomain = await this.cipherService.get(this.cipherId, activeUserId);
|
||||
const cipher = await cipherDomain.decrypt(
|
||||
await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId),
|
||||
);
|
||||
const cipher = await this.cipherService.decrypt(cipherDomain, activeUserId);
|
||||
|
||||
if (!cipher.organizationId) {
|
||||
this.cipherIsAPartOfFreeOrg = false;
|
||||
|
||||
@@ -40,12 +40,9 @@
|
||||
type="button"
|
||||
bitIconButton="bwi-clone"
|
||||
size="small"
|
||||
[appA11yTitle]="
|
||||
'copyFieldValue' | i18n: singleCopiableLogin.key : singleCopiableLogin.value
|
||||
"
|
||||
[appCopyClick]="singleCopiableLogin.value"
|
||||
[valueLabel]="singleCopiableLogin.key"
|
||||
showToast
|
||||
[appA11yTitle]="singleCopiableLogin.key"
|
||||
[appCopyField]="$any(singleCopiableLogin.field)"
|
||||
[cipher]="cipher"
|
||||
></button>
|
||||
<ng-container *ngIf="!singleCopiableLogin">
|
||||
<button
|
||||
|
||||
@@ -15,6 +15,7 @@ import { VaultPopupCopyButtonsService } from "../../../services/vault-popup-copy
|
||||
type CipherItem = {
|
||||
value: string;
|
||||
key: string;
|
||||
field?: string;
|
||||
};
|
||||
|
||||
@Component({
|
||||
@@ -43,15 +44,23 @@ export class ItemCopyActionsComponent {
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* singleCopiableLogin uses appCopyField instead of appCopyClick. This allows for the TOTP
|
||||
* code to be copied correctly. See #14167
|
||||
*/
|
||||
get singleCopiableLogin() {
|
||||
const loginItems: CipherItem[] = [
|
||||
{ value: this.cipher.login.username, key: "username" },
|
||||
{ value: this.cipher.login.password, key: "password" },
|
||||
{ value: this.cipher.login.totp, key: "totp" },
|
||||
{ value: this.cipher.login.username, key: "copyUsername", field: "username" },
|
||||
{ value: this.cipher.login.password, key: "copyPassword", field: "password" },
|
||||
{ value: this.cipher.login.totp, key: "copyVerificationCode", field: "totp" },
|
||||
];
|
||||
// If both the password and username are visible but the password is hidden, return the username
|
||||
if (!this.cipher.viewPassword && this.cipher.login.username && this.cipher.login.password) {
|
||||
return { value: this.cipher.login.username, key: this.i18nService.t("username") };
|
||||
return {
|
||||
value: this.cipher.login.username,
|
||||
key: this.i18nService.t("copyUsername"),
|
||||
field: "username",
|
||||
};
|
||||
}
|
||||
return this.findSingleCopiableItem(loginItems);
|
||||
}
|
||||
@@ -78,12 +87,10 @@ export class ItemCopyActionsComponent {
|
||||
* Given a list of CipherItems, if there is only one item with a value,
|
||||
* return it with the translated key. Otherwise return null
|
||||
*/
|
||||
findSingleCopiableItem(items: { value: string; key: string }[]): CipherItem | null {
|
||||
const singleItemWithValue = items.find(
|
||||
(key) => key.value && items.every((f) => f === key || !f.value),
|
||||
);
|
||||
return singleItemWithValue
|
||||
? { value: singleItemWithValue.value, key: this.i18nService.t(singleItemWithValue.key) }
|
||||
findSingleCopiableItem(items: CipherItem[]): CipherItem | null {
|
||||
const itemsWithValue = items.filter(({ value }) => !!value);
|
||||
return itemsWithValue.length === 1
|
||||
? { ...itemsWithValue[0], key: this.i18nService.t(itemsWithValue[0].key) }
|
||||
: null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
<ng-container *ngIf="showNewCustomizationSettingsCallout">
|
||||
<button
|
||||
type="button"
|
||||
class="tw-absolute tw-bottom-[12px] tw-right-[47px]"
|
||||
[bitPopoverTriggerFor]="newCustomizationOptionsCallout"
|
||||
[position]="'above-end'"
|
||||
[popoverOpen]="true"
|
||||
#triggerRef="popoverTrigger"
|
||||
></button>
|
||||
<bit-popover
|
||||
[title]="'newCustomizationOptionsCalloutTitle' | i18n"
|
||||
#newCustomizationOptionsCallout
|
||||
(closed)="dismissCallout()"
|
||||
>
|
||||
<div bitTypography="body2" (click)="goToAppearance()">
|
||||
{{ "newCustomizationOptionsCalloutContent" | i18n }}
|
||||
<a
|
||||
tabIndex="0"
|
||||
bitLink
|
||||
class="tw-font-bold"
|
||||
linkType="primary"
|
||||
routerLink="/appearance"
|
||||
(keydown.enter)="goToAppearance()"
|
||||
>
|
||||
{{ "newCustomizationOptionsCalloutLink" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</bit-popover>
|
||||
</ng-container>
|
||||
@@ -1,81 +0,0 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
|
||||
import { ButtonModule, PopoverModule } from "@bitwarden/components";
|
||||
|
||||
import { VaultPopupCopyButtonsService } from "../../../services/vault-popup-copy-buttons.service";
|
||||
import { VaultPageService } from "../vault-page.service";
|
||||
|
||||
@Component({
|
||||
selector: "new-settings-callout",
|
||||
templateUrl: "new-settings-callout.component.html",
|
||||
standalone: true,
|
||||
imports: [PopoverModule, JslibModule, CommonModule, ButtonModule],
|
||||
providers: [VaultPageService],
|
||||
})
|
||||
export class NewSettingsCalloutComponent implements OnInit, OnDestroy {
|
||||
protected showNewCustomizationSettingsCallout = false;
|
||||
protected activeUserId: UserId | null = null;
|
||||
|
||||
constructor(
|
||||
private accountService: AccountService,
|
||||
private vaultProfileService: VaultProfileService,
|
||||
private vaultPageService: VaultPageService,
|
||||
private router: Router,
|
||||
private logService: LogService,
|
||||
private copyButtonService: VaultPopupCopyButtonsService,
|
||||
private vaultSettingsService: VaultSettingsService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
|
||||
const showQuickCopyActions = await firstValueFrom(this.copyButtonService.showQuickCopyActions$);
|
||||
const clickItemsToAutofillVaultView = await firstValueFrom(
|
||||
this.vaultSettingsService.clickItemsToAutofillVaultView$,
|
||||
);
|
||||
|
||||
let profileCreatedDate: Date;
|
||||
|
||||
try {
|
||||
profileCreatedDate = await this.vaultProfileService.getProfileCreationDate(this.activeUserId);
|
||||
} catch (e) {
|
||||
this.logService.error("Error getting profile creation date", e);
|
||||
// Default to before the cutoff date to ensure the callout is shown
|
||||
profileCreatedDate = new Date("2024-12-24");
|
||||
}
|
||||
|
||||
const hasCalloutBeenDismissed = await firstValueFrom(
|
||||
this.vaultPageService.isCalloutDismissed(this.activeUserId),
|
||||
);
|
||||
|
||||
this.showNewCustomizationSettingsCallout =
|
||||
!showQuickCopyActions &&
|
||||
!clickItemsToAutofillVaultView &&
|
||||
!hasCalloutBeenDismissed &&
|
||||
profileCreatedDate < new Date("2024-12-25");
|
||||
}
|
||||
|
||||
async goToAppearance() {
|
||||
await this.router.navigate(["/appearance"]);
|
||||
}
|
||||
|
||||
async dismissCallout() {
|
||||
if (this.activeUserId) {
|
||||
await this.vaultPageService.dismissCallout(this.activeUserId);
|
||||
}
|
||||
}
|
||||
|
||||
async ngOnDestroy() {
|
||||
await this.dismissCallout();
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import { inject, Injectable } from "@angular/core";
|
||||
import { map, Observable } from "rxjs";
|
||||
|
||||
import {
|
||||
BANNERS_DISMISSED_DISK,
|
||||
StateProvider,
|
||||
UserKeyDefinition,
|
||||
} from "@bitwarden/common/platform/state";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
|
||||
export const NEW_CUSTOMIZATION_OPTIONS_CALLOUT_DISMISSED_KEY = new UserKeyDefinition<boolean>(
|
||||
BANNERS_DISMISSED_DISK,
|
||||
"newCustomizationOptionsCalloutDismissed",
|
||||
{
|
||||
deserializer: (calloutDismissed) => calloutDismissed,
|
||||
clearOn: [], // Do not clear dismissed callouts
|
||||
},
|
||||
);
|
||||
|
||||
@Injectable()
|
||||
export class VaultPageService {
|
||||
private stateProvider = inject(StateProvider);
|
||||
|
||||
isCalloutDismissed(userId: UserId): Observable<boolean> {
|
||||
return this.stateProvider
|
||||
.getUser(userId, NEW_CUSTOMIZATION_OPTIONS_CALLOUT_DISMISSED_KEY)
|
||||
.state$.pipe(map((dismissed) => !!dismissed));
|
||||
}
|
||||
|
||||
async dismissCallout(userId: UserId): Promise<void> {
|
||||
await this.stateProvider
|
||||
.getUser(userId, NEW_CUSTOMIZATION_OPTIONS_CALLOUT_DISMISSED_KEY)
|
||||
.update(() => true);
|
||||
}
|
||||
}
|
||||
@@ -69,8 +69,6 @@ export class PasswordHistoryV2Component implements OnInit {
|
||||
const activeUserId = activeAccount.id as UserId;
|
||||
|
||||
const cipher = await this.cipherService.get(cipherId, activeUserId);
|
||||
this.cipher = await cipher.decrypt(
|
||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
||||
);
|
||||
this.cipher = await this.cipherService.decrypt(cipher, activeUserId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
[subtitle]="'emptyVaultNudgeBody' | i18n"
|
||||
[buttonText]="'emptyVaultNudgeButton' | i18n"
|
||||
(onButtonClick)="navigateToImport()"
|
||||
(onDismiss)="dismissVaultNudgeSpotlight(VaultNudgeType.EmptyVaultNudge)"
|
||||
(onDismiss)="dismissVaultNudgeSpotlight(NudgeType.EmptyVaultNudge)"
|
||||
>
|
||||
</bit-spotlight>
|
||||
</ng-container>
|
||||
@@ -44,9 +44,13 @@
|
||||
<div class="tw-mb-4" *ngIf="showHasItemsVaultSpotlight$ | async">
|
||||
<bit-spotlight
|
||||
[title]="'hasItemsVaultNudgeTitle' | i18n"
|
||||
[subtitle]="'hasItemsVaultNudgeBody' | i18n"
|
||||
(onDismiss)="dismissVaultNudgeSpotlight(VaultNudgeType.HasVaultItems)"
|
||||
(onDismiss)="dismissVaultNudgeSpotlight(NudgeType.HasVaultItems)"
|
||||
>
|
||||
<ul class="tw-pl-4 tw-text-main" bitTypography="body2">
|
||||
<li>{{ "hasItemsVaultNudgeBodyOne" | i18n }}</li>
|
||||
<li>{{ "hasItemsVaultNudgeBodyTwo" | i18n }}</li>
|
||||
<li>{{ "hasItemsVaultNudgeBodyThree" | i18n }}</li>
|
||||
</ul>
|
||||
</bit-spotlight>
|
||||
</div>
|
||||
<vault-at-risk-password-callout
|
||||
@@ -103,5 +107,4 @@
|
||||
></app-vault-list-items-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
<new-settings-callout></new-settings-callout>
|
||||
</popup-page>
|
||||
|
||||
@@ -19,16 +19,23 @@ import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { CipherId, CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { ButtonModule, DialogService, Icons, NoItemsModule } from "@bitwarden/components";
|
||||
import {
|
||||
ButtonModule,
|
||||
DialogService,
|
||||
Icons,
|
||||
NoItemsModule,
|
||||
TypographyModule,
|
||||
} from "@bitwarden/components";
|
||||
import {
|
||||
DecryptionFailureDialogComponent,
|
||||
NudgesService,
|
||||
NudgeType,
|
||||
SpotlightComponent,
|
||||
VaultIcons,
|
||||
VaultNudgesService,
|
||||
VaultNudgeType,
|
||||
} from "@bitwarden/vault";
|
||||
|
||||
import { CurrentAccountComponent } from "../../../../auth/popup/account-switching/current-account.component";
|
||||
@@ -49,9 +56,7 @@ import {
|
||||
NewItemDropdownV2Component,
|
||||
NewItemInitialValues,
|
||||
} from "./new-item-dropdown/new-item-dropdown-v2.component";
|
||||
import { NewSettingsCalloutComponent } from "./new-settings-callout/new-settings-callout.component";
|
||||
import { VaultHeaderV2Component } from "./vault-header/vault-header-v2.component";
|
||||
import { VaultPageService } from "./vault-page.service";
|
||||
|
||||
import { AutofillVaultListItemsComponent, VaultListItemsContainerComponent } from ".";
|
||||
|
||||
@@ -83,27 +88,24 @@ enum VaultState {
|
||||
ScrollingModule,
|
||||
VaultHeaderV2Component,
|
||||
AtRiskPasswordCalloutComponent,
|
||||
NewSettingsCalloutComponent,
|
||||
SpotlightComponent,
|
||||
RouterModule,
|
||||
TypographyModule,
|
||||
],
|
||||
providers: [VaultPageService],
|
||||
})
|
||||
export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
|
||||
@ViewChild(CdkVirtualScrollableElement) virtualScrollElement?: CdkVirtualScrollableElement;
|
||||
|
||||
VaultNudgeType = VaultNudgeType;
|
||||
NudgeType = NudgeType;
|
||||
cipherType = CipherType;
|
||||
private activeUserId$ = this.accountService.activeAccount$.pipe(getUserId);
|
||||
showEmptyVaultSpotlight$: Observable<boolean> = this.activeUserId$.pipe(
|
||||
switchMap((userId) =>
|
||||
this.vaultNudgesService.showNudge$(VaultNudgeType.EmptyVaultNudge, userId),
|
||||
this.nudgesService.showNudgeSpotlight$(NudgeType.EmptyVaultNudge, userId),
|
||||
),
|
||||
map((nudgeStatus) => !nudgeStatus.hasSpotlightDismissed),
|
||||
);
|
||||
showHasItemsVaultSpotlight$: Observable<boolean> = this.activeUserId$.pipe(
|
||||
switchMap((userId) => this.vaultNudgesService.showNudge$(VaultNudgeType.HasVaultItems, userId)),
|
||||
map((nudgeStatus) => !nudgeStatus.hasSpotlightDismissed),
|
||||
switchMap((userId) => this.nudgesService.showNudgeSpotlight$(NudgeType.HasVaultItems, userId)),
|
||||
);
|
||||
|
||||
activeUserId: UserId | null = null;
|
||||
@@ -144,7 +146,6 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
|
||||
protected noResultsIcon = Icons.NoResults;
|
||||
|
||||
protected VaultStateEnum = VaultState;
|
||||
protected showNewCustomizationSettingsCallout = false;
|
||||
|
||||
constructor(
|
||||
private vaultPopupItemsService: VaultPopupItemsService,
|
||||
@@ -156,8 +157,9 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
|
||||
private dialogService: DialogService,
|
||||
private vaultCopyButtonsService: VaultPopupCopyButtonsService,
|
||||
private introCarouselService: IntroCarouselService,
|
||||
private vaultNudgesService: VaultNudgesService,
|
||||
private nudgesService: NudgesService,
|
||||
private router: Router,
|
||||
private i18nService: I18nService,
|
||||
) {
|
||||
combineLatest([
|
||||
this.vaultPopupItemsService.emptyVault$,
|
||||
@@ -225,8 +227,8 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
async dismissVaultNudgeSpotlight(type: VaultNudgeType) {
|
||||
await this.vaultNudgesService.dismissNudge(type, this.activeUserId as UserId);
|
||||
async dismissVaultNudgeSpotlight(type: NudgeType) {
|
||||
await this.nudgesService.dismissNudge(type, this.activeUserId as UserId);
|
||||
}
|
||||
|
||||
protected readonly FeatureFlag = FeatureFlag;
|
||||
|
||||
@@ -82,6 +82,7 @@ describe("ViewV2Component", () => {
|
||||
getKeyForCipherKeyDecryption: jest.fn().mockResolvedValue({}),
|
||||
deleteWithServer: jest.fn().mockResolvedValue(undefined),
|
||||
softDeleteWithServer: jest.fn().mockResolvedValue(undefined),
|
||||
decrypt: jest.fn().mockResolvedValue(mockCipher),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
@@ -203,9 +203,7 @@ export class ViewV2Component {
|
||||
|
||||
async getCipherData(id: string, userId: UserId) {
|
||||
const cipher = await this.cipherService.get(id, userId);
|
||||
return await cipher.decrypt(
|
||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, userId),
|
||||
);
|
||||
return await this.cipherService.decrypt(cipher, userId);
|
||||
}
|
||||
|
||||
async editCipher() {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { CardComponent, LinkModule, TypographyModule } from "@bitwarden/components";
|
||||
import { VaultNudgesService, VaultNudgeType } from "@bitwarden/vault";
|
||||
import { NudgesService, NudgeType } from "@bitwarden/vault";
|
||||
|
||||
import { CurrentAccountComponent } from "../../../auth/popup/account-switching/current-account.component";
|
||||
import { PopOutComponent } from "../../../platform/popup/components/pop-out.component";
|
||||
@@ -32,12 +32,12 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co
|
||||
})
|
||||
export class DownloadBitwardenComponent implements OnInit {
|
||||
constructor(
|
||||
private vaultNudgeService: VaultNudgesService,
|
||||
private nudgesService: NudgesService,
|
||||
private accountService: AccountService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
await this.vaultNudgeService.dismissNudge(VaultNudgeType.DownloadBitwarden, userId);
|
||||
await this.nudgesService.dismissNudge(NudgeType.DownloadBitwarden, userId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@
|
||||
"browser-hrtime": "1.1.8",
|
||||
"chalk": "4.1.2",
|
||||
"commander": "11.1.0",
|
||||
"core-js": "3.40.0",
|
||||
"form-data": "4.0.1",
|
||||
"https-proxy-agent": "7.0.6",
|
||||
"inquirer": "8.2.6",
|
||||
|
||||
@@ -59,15 +59,11 @@ export class ShareCommand {
|
||||
return Response.badRequest("This item already belongs to an organization.");
|
||||
}
|
||||
|
||||
const cipherView = await cipher.decrypt(
|
||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
||||
);
|
||||
const cipherView = await this.cipherService.decrypt(cipher, activeUserId);
|
||||
try {
|
||||
await this.cipherService.shareWithServer(cipherView, organizationId, req, activeUserId);
|
||||
const updatedCipher = await this.cipherService.get(cipher.id, activeUserId);
|
||||
const decCipher = await updatedCipher.decrypt(
|
||||
await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId),
|
||||
);
|
||||
const decCipher = await this.cipherService.decrypt(updatedCipher, activeUserId);
|
||||
const res = new CipherResponse(decCipher);
|
||||
return Response.success(res);
|
||||
} catch (e) {
|
||||
|
||||
@@ -106,6 +106,8 @@ export class LoginCommand {
|
||||
return Response.badRequest("client_secret is required.");
|
||||
}
|
||||
} else if (options.sso != null && this.canInteract) {
|
||||
// If the optional Org SSO Identifier isn't provided, the option value is `true`.
|
||||
const orgSsoIdentifier = options.sso === true ? null : options.sso;
|
||||
const passwordOptions: any = {
|
||||
type: "password",
|
||||
length: 64,
|
||||
@@ -119,7 +121,7 @@ export class LoginCommand {
|
||||
const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, "sha256");
|
||||
const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
|
||||
try {
|
||||
const ssoParams = await this.openSsoPrompt(codeChallenge, state);
|
||||
const ssoParams = await this.openSsoPrompt(codeChallenge, state, orgSsoIdentifier);
|
||||
ssoCode = ssoParams.ssoCode;
|
||||
orgIdentifier = ssoParams.orgIdentifier;
|
||||
} catch {
|
||||
@@ -664,6 +666,7 @@ export class LoginCommand {
|
||||
private async openSsoPrompt(
|
||||
codeChallenge: string,
|
||||
state: string,
|
||||
orgSsoIdentifier: string,
|
||||
): Promise<{ ssoCode: string; orgIdentifier: string }> {
|
||||
const env = await firstValueFrom(this.environmentService.environment$);
|
||||
|
||||
@@ -712,6 +715,8 @@ export class LoginCommand {
|
||||
this.ssoRedirectUri,
|
||||
state,
|
||||
codeChallenge,
|
||||
null,
|
||||
orgSsoIdentifier,
|
||||
);
|
||||
this.platformUtilsService.launchUri(webAppSsoUrl);
|
||||
});
|
||||
|
||||
@@ -90,9 +90,7 @@ export class EditCommand {
|
||||
return Response.notFound();
|
||||
}
|
||||
|
||||
let cipherView = await cipher.decrypt(
|
||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
||||
);
|
||||
let cipherView = await this.cipherService.decrypt(cipher, activeUserId);
|
||||
if (cipherView.isDeleted) {
|
||||
return Response.badRequest("You may not edit a deleted item. Use the restore command first.");
|
||||
}
|
||||
@@ -100,9 +98,7 @@ export class EditCommand {
|
||||
const encCipher = await this.cipherService.encrypt(cipherView, activeUserId);
|
||||
try {
|
||||
const updatedCipher = await this.cipherService.updateWithServer(encCipher);
|
||||
const decCipher = await updatedCipher.decrypt(
|
||||
await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId),
|
||||
);
|
||||
const decCipher = await this.cipherService.decrypt(updatedCipher, activeUserId);
|
||||
const res = new CipherResponse(decCipher);
|
||||
return Response.success(res);
|
||||
} catch (e) {
|
||||
@@ -132,12 +128,7 @@ export class EditCommand {
|
||||
cipher,
|
||||
activeUserId,
|
||||
);
|
||||
const decCipher = await updatedCipher.decrypt(
|
||||
await this.cipherService.getKeyForCipherKeyDecryption(
|
||||
updatedCipher,
|
||||
await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)),
|
||||
),
|
||||
);
|
||||
const decCipher = await this.cipherService.decrypt(updatedCipher, activeUserId);
|
||||
const res = new CipherResponse(decCipher);
|
||||
return Response.success(res);
|
||||
} catch (e) {
|
||||
|
||||
@@ -116,9 +116,7 @@ export class GetCommand extends DownloadCommand {
|
||||
if (Utils.isGuid(id)) {
|
||||
const cipher = await this.cipherService.get(id, activeUserId);
|
||||
if (cipher != null) {
|
||||
decCipher = await cipher.decrypt(
|
||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
||||
);
|
||||
decCipher = await this.cipherService.decrypt(cipher, activeUserId);
|
||||
}
|
||||
} else if (id.trim() !== "") {
|
||||
let ciphers = await this.cipherService.getAllDecrypted(activeUserId);
|
||||
|
||||
@@ -118,7 +118,10 @@ export class Program extends BaseProgram {
|
||||
.description("Log into a user account.")
|
||||
.option("--method <method>", "Two-step login method.")
|
||||
.option("--code <code>", "Two-step login code.")
|
||||
.option("--sso", "Log in with Single-Sign On.")
|
||||
.option(
|
||||
"--sso [identifier]",
|
||||
"Log in with Single-Sign On with optional organization identifier.",
|
||||
)
|
||||
.option("--apikey", "Log in with an Api Key.")
|
||||
.option("--passwordenv <passwordenv>", "Environment variable storing your password")
|
||||
.option(
|
||||
|
||||
@@ -139,12 +139,14 @@ import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.s
|
||||
import { SendStateProvider } from "@bitwarden/common/tools/send/services/send-state.provider";
|
||||
import { SendService } from "@bitwarden/common/tools/send/services/send.service";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherEncryptionService } from "@bitwarden/common/vault/abstractions/cipher-encryption.service";
|
||||
import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import {
|
||||
CipherAuthorizationService,
|
||||
DefaultCipherAuthorizationService,
|
||||
} from "@bitwarden/common/vault/services/cipher-authorization.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/services/cipher.service";
|
||||
import { DefaultCipherEncryptionService } from "@bitwarden/common/vault/services/default-cipher-encryption.service";
|
||||
import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service";
|
||||
import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service";
|
||||
import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service";
|
||||
@@ -284,6 +286,7 @@ export class ServiceContainer {
|
||||
ssoUrlService: SsoUrlService;
|
||||
masterPasswordApiService: MasterPasswordApiServiceAbstraction;
|
||||
bulkEncryptService: FallbackBulkEncryptService;
|
||||
cipherEncryptionService: CipherEncryptionService;
|
||||
|
||||
constructor() {
|
||||
let p = null;
|
||||
@@ -679,6 +682,11 @@ export class ServiceContainer {
|
||||
this.accountService,
|
||||
);
|
||||
|
||||
this.cipherEncryptionService = new DefaultCipherEncryptionService(
|
||||
this.sdkService,
|
||||
this.logService,
|
||||
);
|
||||
|
||||
this.cipherService = new CipherService(
|
||||
this.keyService,
|
||||
this.domainSettingsService,
|
||||
@@ -694,6 +702,7 @@ export class ServiceContainer {
|
||||
this.stateProvider,
|
||||
this.accountService,
|
||||
this.logService,
|
||||
this.cipherEncryptionService,
|
||||
);
|
||||
|
||||
this.folderService = new FolderService(
|
||||
|
||||
@@ -93,9 +93,7 @@ export class CreateCommand {
|
||||
const cipher = await this.cipherService.encrypt(CipherExport.toView(req), activeUserId);
|
||||
try {
|
||||
const newCipher = await this.cipherService.createWithServer(cipher);
|
||||
const decCipher = await newCipher.decrypt(
|
||||
await this.cipherService.getKeyForCipherKeyDecryption(newCipher, activeUserId),
|
||||
);
|
||||
const decCipher = await this.cipherService.decrypt(newCipher, activeUserId);
|
||||
const res = new CipherResponse(decCipher);
|
||||
return Response.success(res);
|
||||
} catch (e) {
|
||||
@@ -162,9 +160,7 @@ export class CreateCommand {
|
||||
new Uint8Array(fileBuf).buffer,
|
||||
activeUserId,
|
||||
);
|
||||
const decCipher = await updatedCipher.decrypt(
|
||||
await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId),
|
||||
);
|
||||
const decCipher = await this.cipherService.decrypt(updatedCipher, activeUserId);
|
||||
return Response.success(new CipherResponse(decCipher));
|
||||
} catch (e) {
|
||||
return Response.error(e);
|
||||
|
||||
93
apps/desktop/desktop_native/Cargo.lock
generated
93
apps/desktop/desktop_native/Cargo.lock
generated
@@ -3045,9 +3045,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.43.1"
|
||||
version = "1.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "492a604e2fd7f814268a378409e6c92b5525d747d10db9a229723f55a417958c"
|
||||
checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
@@ -3479,9 +3479,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "widestring"
|
||||
version = "1.1.0"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311"
|
||||
checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
@@ -3568,7 +3568,7 @@ dependencies = [
|
||||
"windows-interface 0.59.1",
|
||||
"windows-link",
|
||||
"windows-result 0.3.2",
|
||||
"windows-strings 0.4.0",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3643,13 +3643,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.4.0"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3"
|
||||
checksum = "ad1da3e436dc7653dfdf3da67332e22bff09bb0e28b0239e1624499c7830842e"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows-result 0.3.2",
|
||||
"windows-strings 0.3.1",
|
||||
"windows-targets 0.53.0",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3670,15 +3670,6 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.4.0"
|
||||
@@ -3730,29 +3721,13 @@ dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.6",
|
||||
"windows_aarch64_msvc 0.52.6",
|
||||
"windows_i686_gnu 0.52.6",
|
||||
"windows_i686_gnullvm 0.52.6",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc 0.52.6",
|
||||
"windows_x86_64_gnu 0.52.6",
|
||||
"windows_x86_64_gnullvm 0.52.6",
|
||||
"windows_x86_64_msvc 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.53.0",
|
||||
"windows_aarch64_msvc 0.53.0",
|
||||
"windows_i686_gnu 0.53.0",
|
||||
"windows_i686_gnullvm 0.53.0",
|
||||
"windows_i686_msvc 0.53.0",
|
||||
"windows_x86_64_gnu 0.53.0",
|
||||
"windows_x86_64_gnullvm 0.53.0",
|
||||
"windows_x86_64_msvc 0.53.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
@@ -3765,12 +3740,6 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
@@ -3783,12 +3752,6 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
@@ -3801,24 +3764,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
@@ -3831,12 +3782,6 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_plugin_authenticator"
|
||||
version = "0.0.0"
|
||||
@@ -3858,12 +3803,6 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
@@ -3876,12 +3815,6 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
@@ -3894,12 +3827,6 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.3"
|
||||
|
||||
@@ -50,17 +50,17 @@ simplelog = "=0.12.2"
|
||||
ssh-encoding = "=0.2.0"
|
||||
ssh-key = {version = "=0.6.7", default-features = false }
|
||||
sysinfo = "0.35.0"
|
||||
thiserror = "=1.0.69"
|
||||
tokio = "=1.43.1"
|
||||
thiserror = "=2.0.12"
|
||||
tokio = "=1.45.0"
|
||||
tokio-stream = "=0.1.15"
|
||||
tokio-util = "=0.7.13"
|
||||
typenum = "=1.18.0"
|
||||
uniffi = "=0.28.3"
|
||||
widestring = "=1.1.0"
|
||||
widestring = "=1.2.0"
|
||||
windows = "=0.61.1"
|
||||
windows-core = "=0.61.0"
|
||||
windows-future = "=0.2.0"
|
||||
windows-registry = "=0.4.0"
|
||||
windows-registry = "=0.5.1"
|
||||
zbus = "=4.4.0"
|
||||
zbus_polkit = "=4.0.0"
|
||||
zeroizing-alloc = "=0.1.0"
|
||||
|
||||
@@ -243,7 +243,7 @@
|
||||
},
|
||||
"snap": {
|
||||
"summary": "Bitwarden is a secure and free password manager for all of your devices.",
|
||||
"description": "**Installation**\nBitwarden requires access to the `password-manager-service`. Please enable it through permissions or by running `sudo snap connect bitwarden:password-manager-service` after installation. See https://btwrdn.com/install-snap for details.",
|
||||
"description": "Password Manager\n**Installation**\nBitwarden requires access to the `password-manager-service`. Please enable it through permissions or by running `sudo snap connect bitwarden:password-manager-service` after installation. See https://btwrdn.com/install-snap for details.",
|
||||
"autoStart": true,
|
||||
"base": "core22",
|
||||
"confinement": "strict",
|
||||
|
||||
@@ -54,6 +54,7 @@ import { NativeMessagingManifestService } from "../services/native-messaging-man
|
||||
@Component({
|
||||
selector: "app-settings",
|
||||
templateUrl: "settings.component.html",
|
||||
standalone: false,
|
||||
})
|
||||
export class SettingsComponent implements OnInit, OnDestroy {
|
||||
// For use in template
|
||||
|
||||
@@ -18,5 +18,6 @@ import { VaultTimeoutInputComponent as VaultTimeoutInputComponentBase } from "@b
|
||||
useExisting: VaultTimeoutInputComponent,
|
||||
},
|
||||
],
|
||||
standalone: false,
|
||||
})
|
||||
export class VaultTimeoutInputComponent extends VaultTimeoutInputComponentBase {}
|
||||
|
||||
@@ -11,7 +11,16 @@ import {
|
||||
} from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { Router } from "@angular/router";
|
||||
import { filter, firstValueFrom, map, Subject, switchMap, takeUntil, timeout } from "rxjs";
|
||||
import {
|
||||
filter,
|
||||
firstValueFrom,
|
||||
lastValueFrom,
|
||||
map,
|
||||
Subject,
|
||||
switchMap,
|
||||
takeUntil,
|
||||
timeout,
|
||||
} from "rxjs";
|
||||
|
||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||
import { DeviceTrustToastService } from "@bitwarden/angular/auth/services/device-trust-toast.service.abstraction";
|
||||
@@ -56,11 +65,11 @@ import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { DialogRef, DialogService, ToastOptions, ToastService } from "@bitwarden/components";
|
||||
import { CredentialGeneratorHistoryDialogComponent } from "@bitwarden/generator-components";
|
||||
import { KeyService, BiometricStateService } from "@bitwarden/key-management";
|
||||
import { AddEditFolderDialogComponent, AddEditFolderDialogResult } from "@bitwarden/vault";
|
||||
|
||||
import { DeleteAccountComponent } from "../auth/delete-account.component";
|
||||
import { PremiumComponent } from "../billing/app/accounts/premium.component";
|
||||
import { MenuAccount, MenuUpdateRequest } from "../main/menu/menu.updater";
|
||||
import { FolderAddEditComponent } from "../vault/app/vault/folder-add-edit.component";
|
||||
|
||||
import { SettingsComponent } from "./accounts/settings.component";
|
||||
import { ExportDesktopComponent } from "./tools/export/export-desktop.component";
|
||||
@@ -78,7 +87,6 @@ const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours
|
||||
<ng-template #settings></ng-template>
|
||||
<ng-template #premium></ng-template>
|
||||
<ng-template #passwordHistory></ng-template>
|
||||
<ng-template #appFolderAddEdit></ng-template>
|
||||
<ng-template #exportVault></ng-template>
|
||||
<ng-template #appGenerator></ng-template>
|
||||
<ng-template #loginApproval></ng-template>
|
||||
@@ -93,6 +101,7 @@ const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours
|
||||
|
||||
<bit-toast-container></bit-toast-container>
|
||||
`,
|
||||
standalone: false,
|
||||
})
|
||||
export class AppComponent implements OnInit, OnDestroy {
|
||||
@ViewChild("settings", { read: ViewContainerRef, static: true }) settingsRef: ViewContainerRef;
|
||||
@@ -101,8 +110,6 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
passwordHistoryRef: ViewContainerRef;
|
||||
@ViewChild("exportVault", { read: ViewContainerRef, static: true })
|
||||
exportVaultModalRef: ViewContainerRef;
|
||||
@ViewChild("appFolderAddEdit", { read: ViewContainerRef, static: true })
|
||||
folderAddEditModalRef: ViewContainerRef;
|
||||
@ViewChild("appGenerator", { read: ViewContainerRef, static: true })
|
||||
generatorModalRef: ViewContainerRef;
|
||||
@ViewChild("loginApproval", { read: ViewContainerRef, static: true })
|
||||
@@ -464,25 +471,11 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
async addFolder() {
|
||||
this.modalService.closeAll();
|
||||
|
||||
const [modal, childComponent] = await this.modalService.openViewRef(
|
||||
FolderAddEditComponent,
|
||||
this.folderAddEditModalRef,
|
||||
(comp) => (comp.folderId = null),
|
||||
);
|
||||
this.modal = modal;
|
||||
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
||||
childComponent.onSavedFolder.subscribe(async () => {
|
||||
this.modal.close();
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.syncService.fullSync(false);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
this.modal.onClosed.subscribe(() => {
|
||||
this.modal = null;
|
||||
});
|
||||
const dialogRef = AddEditFolderDialogComponent.open(this.dialogService);
|
||||
const result = await lastValueFrom(dialogRef.closed);
|
||||
if (result === AddEditFolderDialogResult.Created) {
|
||||
await this.syncService.fullSync(false);
|
||||
}
|
||||
}
|
||||
|
||||
async openGenerator() {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
@Component({
|
||||
selector: "app-avatar",
|
||||
template: `<img *ngIf="src" [src]="src" [ngClass]="{ 'rounded-circle': circle }" />`,
|
||||
standalone: false,
|
||||
})
|
||||
export class AvatarComponent implements OnChanges, OnInit {
|
||||
@Input() size = 45;
|
||||
|
||||
@@ -54,6 +54,7 @@ type InactiveAccount = ActiveAccount & {
|
||||
transition("* => void", animate("100ms linear", style({ opacity: 0 }))),
|
||||
]),
|
||||
],
|
||||
standalone: false,
|
||||
})
|
||||
export class AccountSwitcherComponent implements OnInit {
|
||||
activeAccount$: Observable<ActiveAccount | null>;
|
||||
|
||||
@@ -3,5 +3,6 @@ import { Component } from "@angular/core";
|
||||
@Component({
|
||||
selector: "app-header",
|
||||
templateUrl: "header.component.html",
|
||||
standalone: false,
|
||||
})
|
||||
export class HeaderComponent {}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { SearchBarService, SearchBarState } from "./search-bar.service";
|
||||
@Component({
|
||||
selector: "app-search",
|
||||
templateUrl: "search.component.html",
|
||||
standalone: false,
|
||||
})
|
||||
export class SearchComponent implements OnInit, OnDestroy {
|
||||
state: SearchBarState;
|
||||
|
||||
@@ -28,6 +28,7 @@ const BroadcasterSubscriptionId = "SetPasswordComponent";
|
||||
@Component({
|
||||
selector: "app-set-password",
|
||||
templateUrl: "set-password.component.html",
|
||||
standalone: false,
|
||||
})
|
||||
export class SetPasswordComponent extends BaseSetPasswordComponent implements OnInit, OnDestroy {
|
||||
constructor(
|
||||
|
||||
@@ -5,5 +5,6 @@ import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from "
|
||||
@Component({
|
||||
selector: "app-update-temp-password",
|
||||
templateUrl: "update-temp-password.component.html",
|
||||
standalone: false,
|
||||
})
|
||||
export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {}
|
||||
|
||||
@@ -199,9 +199,7 @@ export class DesktopAutofillService implements OnDestroy {
|
||||
return;
|
||||
}
|
||||
|
||||
const decrypted = await cipher.decrypt(
|
||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
||||
);
|
||||
const decrypted = await this.cipherService.decrypt(cipher, activeUserId);
|
||||
|
||||
const fido2Credential = decrypted.login.fido2Credentials?.[0];
|
||||
if (!fido2Credential) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import { DialogService, ToastService } from "@bitwarden/components";
|
||||
@Component({
|
||||
selector: "app-premium",
|
||||
templateUrl: "premium.component.html",
|
||||
standalone: false,
|
||||
})
|
||||
export class PremiumComponent extends BasePremiumComponent {
|
||||
constructor(
|
||||
|
||||
@@ -5,5 +5,6 @@ import { RemovePasswordComponent as BaseRemovePasswordComponent } from "@bitward
|
||||
@Component({
|
||||
selector: "app-remove-password",
|
||||
templateUrl: "remove-password.component.html",
|
||||
standalone: false,
|
||||
})
|
||||
export class RemovePasswordComponent extends BaseRemovePasswordComponent {}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user