mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 06:13:38 +00:00
[PM-328] Move Send to Tools (#5104)
* Move send in libs/common * Move send in libs/angular * Move send in browser * Move send in cli * Move send in desktop * Move send in web
This commit is contained in:
committed by
GitHub
parent
e645688f8a
commit
e238ea20a9
@@ -1,322 +0,0 @@
|
||||
import { DatePipe } from "@angular/common";
|
||||
import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
|
||||
import { Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { SendApiService } from "@bitwarden/common/abstractions/send/send-api.service.abstraction";
|
||||
import { SendService } from "@bitwarden/common/abstractions/send/send.service.abstraction";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums/policy-type";
|
||||
import { SendType } from "@bitwarden/common/enums/sendType";
|
||||
import { EncArrayBuffer } from "@bitwarden/common/models/domain/enc-array-buffer";
|
||||
import { Send } from "@bitwarden/common/models/domain/send";
|
||||
import { SendFileView } from "@bitwarden/common/models/view/send-file.view";
|
||||
import { SendTextView } from "@bitwarden/common/models/view/send-text.view";
|
||||
import { SendView } from "@bitwarden/common/models/view/send.view";
|
||||
|
||||
@Directive()
|
||||
export class AddEditComponent implements OnInit, OnDestroy {
|
||||
@Input() sendId: string;
|
||||
@Input() type: SendType;
|
||||
|
||||
@Output() onSavedSend = new EventEmitter<SendView>();
|
||||
@Output() onDeletedSend = new EventEmitter<SendView>();
|
||||
@Output() onCancelled = new EventEmitter<SendView>();
|
||||
|
||||
copyLink = false;
|
||||
disableSend = false;
|
||||
disableHideEmail = false;
|
||||
send: SendView;
|
||||
deletionDate: string;
|
||||
expirationDate: string;
|
||||
hasPassword: boolean;
|
||||
password: string;
|
||||
showPassword = false;
|
||||
formPromise: Promise<any>;
|
||||
deletePromise: Promise<any>;
|
||||
sendType = SendType;
|
||||
typeOptions: any[];
|
||||
canAccessPremium = true;
|
||||
emailVerified = true;
|
||||
alertShown = false;
|
||||
showOptions = false;
|
||||
|
||||
protected componentName = "";
|
||||
private sendLinkBaseUrl: string;
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
protected i18nService: I18nService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected environmentService: EnvironmentService,
|
||||
protected datePipe: DatePipe,
|
||||
protected sendService: SendService,
|
||||
protected messagingService: MessagingService,
|
||||
protected policyService: PolicyService,
|
||||
private logService: LogService,
|
||||
protected stateService: StateService,
|
||||
protected sendApiService: SendApiService
|
||||
) {
|
||||
this.typeOptions = [
|
||||
{ name: i18nService.t("sendTypeFile"), value: SendType.File },
|
||||
{ name: i18nService.t("sendTypeText"), value: SendType.Text },
|
||||
];
|
||||
this.sendLinkBaseUrl = this.environmentService.getSendUrl();
|
||||
}
|
||||
|
||||
get link(): string {
|
||||
if (this.send.id != null && this.send.accessId != null) {
|
||||
return this.sendLinkBaseUrl + this.send.accessId + "/" + this.send.urlB64Key;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
get isSafari() {
|
||||
return this.platformUtilsService.isSafari();
|
||||
}
|
||||
|
||||
get isDateTimeLocalSupported(): boolean {
|
||||
return !(this.platformUtilsService.isFirefox() || this.platformUtilsService.isSafari());
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.policyService
|
||||
.policyAppliesToActiveUser$(PolicyType.DisableSend)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((policyAppliesToActiveUser) => {
|
||||
this.disableSend = policyAppliesToActiveUser;
|
||||
});
|
||||
|
||||
this.policyService
|
||||
.policyAppliesToActiveUser$(PolicyType.SendOptions, (p) => p.data.disableHideEmail)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((policyAppliesToActiveUser) => {
|
||||
this.disableHideEmail = policyAppliesToActiveUser;
|
||||
});
|
||||
|
||||
await this.load();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
get editMode(): boolean {
|
||||
return this.sendId != null;
|
||||
}
|
||||
|
||||
get title(): string {
|
||||
return this.i18nService.t(this.editMode ? "editSend" : "createSend");
|
||||
}
|
||||
|
||||
setDates(event: { deletionDate: string; expirationDate: string }) {
|
||||
this.deletionDate = event.deletionDate;
|
||||
this.expirationDate = event.expirationDate;
|
||||
}
|
||||
|
||||
async load() {
|
||||
this.canAccessPremium = await this.stateService.getCanAccessPremium();
|
||||
this.emailVerified = await this.stateService.getEmailVerified();
|
||||
if (!this.canAccessPremium || !this.emailVerified) {
|
||||
this.type = SendType.Text;
|
||||
}
|
||||
|
||||
if (this.send == null) {
|
||||
if (this.editMode) {
|
||||
const send = this.loadSend();
|
||||
this.send = await send.decrypt();
|
||||
} else {
|
||||
this.send = new SendView();
|
||||
this.send.type = this.type == null ? SendType.File : this.type;
|
||||
this.send.file = new SendFileView();
|
||||
this.send.text = new SendTextView();
|
||||
this.send.deletionDate = new Date();
|
||||
this.send.deletionDate.setDate(this.send.deletionDate.getDate() + 7);
|
||||
}
|
||||
}
|
||||
|
||||
this.hasPassword = this.send.password != null && this.send.password.trim() !== "";
|
||||
}
|
||||
|
||||
async submit(): Promise<boolean> {
|
||||
if (this.disableSend) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("sendDisabledWarning")
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.send.name == null || this.send.name === "") {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("nameRequired")
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
let file: File = null;
|
||||
if (this.send.type === SendType.File && !this.editMode) {
|
||||
const fileEl = document.getElementById("file") as HTMLInputElement;
|
||||
const files = fileEl.files;
|
||||
if (files == null || files.length === 0) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("selectFile")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
file = files[0];
|
||||
if (files[0].size > 524288000) {
|
||||
// 500 MB
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("maxFileSize")
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.password != null && this.password.trim() === "") {
|
||||
this.password = null;
|
||||
}
|
||||
|
||||
this.formPromise = this.encryptSend(file).then(async (encSend) => {
|
||||
const uploadPromise = this.sendApiService.save(encSend);
|
||||
await uploadPromise;
|
||||
if (this.send.id == null) {
|
||||
this.send.id = encSend[0].id;
|
||||
}
|
||||
if (this.send.accessId == null) {
|
||||
this.send.accessId = encSend[0].accessId;
|
||||
}
|
||||
this.onSavedSend.emit(this.send);
|
||||
if (this.copyLink && this.link != null) {
|
||||
await this.handleCopyLinkToClipboard();
|
||||
return;
|
||||
}
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t(this.editMode ? "editedSend" : "createdSend")
|
||||
);
|
||||
});
|
||||
try {
|
||||
await this.formPromise;
|
||||
return true;
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async copyLinkToClipboard(link: string): Promise<void | boolean> {
|
||||
return Promise.resolve(this.platformUtilsService.copyToClipboard(link));
|
||||
}
|
||||
|
||||
async delete(): Promise<boolean> {
|
||||
if (this.deletePromise != null) {
|
||||
return false;
|
||||
}
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("deleteSendConfirmation"),
|
||||
this.i18nService.t("deleteSend"),
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("no"),
|
||||
"warning",
|
||||
false,
|
||||
this.componentName != "" ? this.componentName + " .modal-content" : null
|
||||
);
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
this.deletePromise = this.sendApiService.delete(this.send.id);
|
||||
await this.deletePromise;
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("deletedSend"));
|
||||
await this.load();
|
||||
this.onDeletedSend.emit(this.send);
|
||||
return true;
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
typeChanged() {
|
||||
if (this.send.type === SendType.File && !this.alertShown) {
|
||||
if (!this.canAccessPremium) {
|
||||
this.alertShown = true;
|
||||
this.messagingService.send("premiumRequired");
|
||||
} else if (!this.emailVerified) {
|
||||
this.alertShown = true;
|
||||
this.messagingService.send("emailVerificationRequired");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toggleOptions() {
|
||||
this.showOptions = !this.showOptions;
|
||||
}
|
||||
|
||||
protected loadSend(): Send {
|
||||
return this.sendService.get(this.sendId);
|
||||
}
|
||||
|
||||
protected async encryptSend(file: File): Promise<[Send, EncArrayBuffer]> {
|
||||
const sendData = await this.sendService.encrypt(this.send, file, this.password, null);
|
||||
|
||||
// Parse dates
|
||||
try {
|
||||
sendData[0].deletionDate = this.deletionDate == null ? null : new Date(this.deletionDate);
|
||||
} catch {
|
||||
sendData[0].deletionDate = null;
|
||||
}
|
||||
try {
|
||||
sendData[0].expirationDate =
|
||||
this.expirationDate == null ? null : new Date(this.expirationDate);
|
||||
} catch {
|
||||
sendData[0].expirationDate = null;
|
||||
}
|
||||
|
||||
return sendData;
|
||||
}
|
||||
|
||||
protected togglePasswordVisible() {
|
||||
this.showPassword = !this.showPassword;
|
||||
document.getElementById("password").focus();
|
||||
}
|
||||
private async handleCopyLinkToClipboard() {
|
||||
const copySuccess = await this.copyLinkToClipboard(this.link);
|
||||
if (copySuccess ?? true) {
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t(this.editMode ? "editedSend" : "createdSend")
|
||||
);
|
||||
} else {
|
||||
await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t(this.editMode ? "editedSend" : "createdSend"),
|
||||
null,
|
||||
this.i18nService.t("ok"),
|
||||
null,
|
||||
"success",
|
||||
null
|
||||
);
|
||||
await this.copyLinkToClipboard(this.link);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,356 +0,0 @@
|
||||
import { DatePipe } from "@angular/common";
|
||||
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||
import { UntypedFormControl, UntypedFormGroup } from "@angular/forms";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
|
||||
// Different BrowserPath = different controls.
|
||||
enum BrowserPath {
|
||||
// Native datetime-locale.
|
||||
// We are happy.
|
||||
Default = "default",
|
||||
|
||||
// Native date and time inputs, but no datetime-locale.
|
||||
// We use individual date and time inputs and create a datetime programatically on submit.
|
||||
Firefox = "firefox",
|
||||
|
||||
// No native date, time, or datetime-locale inputs.
|
||||
// We use a polyfill for dates and a dropdown for times.
|
||||
Safari = "safari",
|
||||
}
|
||||
|
||||
enum DateField {
|
||||
DeletionDate = "deletion",
|
||||
ExpriationDate = "expiration",
|
||||
}
|
||||
|
||||
// Value = hours
|
||||
enum DatePreset {
|
||||
OneHour = 1,
|
||||
OneDay = 24,
|
||||
TwoDays = 48,
|
||||
ThreeDays = 72,
|
||||
SevenDays = 168,
|
||||
ThirtyDays = 720,
|
||||
Custom = 0,
|
||||
Never = null,
|
||||
}
|
||||
|
||||
// TimeOption is used for the dropdown implementation of custom times
|
||||
// twelveHour = displayed time; twentyFourHour = time used in logic
|
||||
interface TimeOption {
|
||||
twelveHour: string;
|
||||
twentyFourHour: string;
|
||||
}
|
||||
|
||||
@Directive()
|
||||
export class EffluxDatesComponent implements OnInit {
|
||||
@Input() readonly initialDeletionDate: Date;
|
||||
@Input() readonly initialExpirationDate: Date;
|
||||
@Input() readonly editMode: boolean;
|
||||
@Input() readonly disabled: boolean;
|
||||
|
||||
@Output() datesChanged = new EventEmitter<{ deletionDate: string; expirationDate: string }>();
|
||||
|
||||
get browserPath(): BrowserPath {
|
||||
if (this.platformUtilsService.isFirefox()) {
|
||||
return BrowserPath.Firefox;
|
||||
} else if (this.platformUtilsService.isSafari()) {
|
||||
return BrowserPath.Safari;
|
||||
}
|
||||
return BrowserPath.Default;
|
||||
}
|
||||
|
||||
datesForm = new UntypedFormGroup({
|
||||
selectedDeletionDatePreset: new UntypedFormControl(),
|
||||
selectedExpirationDatePreset: new UntypedFormControl(),
|
||||
defaultDeletionDateTime: new UntypedFormControl(),
|
||||
defaultExpirationDateTime: new UntypedFormControl(),
|
||||
fallbackDeletionDate: new UntypedFormControl(),
|
||||
fallbackDeletionTime: new UntypedFormControl(),
|
||||
fallbackExpirationDate: new UntypedFormControl(),
|
||||
fallbackExpirationTime: new UntypedFormControl(),
|
||||
});
|
||||
|
||||
deletionDatePresets: any[] = [
|
||||
{ name: this.i18nService.t("oneHour"), value: DatePreset.OneHour },
|
||||
{ name: this.i18nService.t("oneDay"), value: DatePreset.OneDay },
|
||||
{ name: this.i18nService.t("days", "2"), value: DatePreset.TwoDays },
|
||||
{ name: this.i18nService.t("days", "3"), value: DatePreset.ThreeDays },
|
||||
{ name: this.i18nService.t("days", "7"), value: DatePreset.SevenDays },
|
||||
{ name: this.i18nService.t("days", "30"), value: DatePreset.ThirtyDays },
|
||||
{ name: this.i18nService.t("custom"), value: DatePreset.Custom },
|
||||
];
|
||||
|
||||
expirationDatePresets: any[] = [
|
||||
{ name: this.i18nService.t("never"), value: DatePreset.Never },
|
||||
].concat([...this.deletionDatePresets]);
|
||||
|
||||
get selectedDeletionDatePreset(): UntypedFormControl {
|
||||
return this.datesForm.get("selectedDeletionDatePreset") as UntypedFormControl;
|
||||
}
|
||||
|
||||
get selectedExpirationDatePreset(): UntypedFormControl {
|
||||
return this.datesForm.get("selectedExpirationDatePreset") as UntypedFormControl;
|
||||
}
|
||||
|
||||
get defaultDeletionDateTime(): UntypedFormControl {
|
||||
return this.datesForm.get("defaultDeletionDateTime") as UntypedFormControl;
|
||||
}
|
||||
|
||||
get defaultExpirationDateTime(): UntypedFormControl {
|
||||
return this.datesForm.get("defaultExpirationDateTime") as UntypedFormControl;
|
||||
}
|
||||
|
||||
get fallbackDeletionDate(): UntypedFormControl {
|
||||
return this.datesForm.get("fallbackDeletionDate") as UntypedFormControl;
|
||||
}
|
||||
|
||||
get fallbackDeletionTime(): UntypedFormControl {
|
||||
return this.datesForm.get("fallbackDeletionTime") as UntypedFormControl;
|
||||
}
|
||||
|
||||
get fallbackExpirationDate(): UntypedFormControl {
|
||||
return this.datesForm.get("fallbackExpirationDate") as UntypedFormControl;
|
||||
}
|
||||
|
||||
get fallbackExpirationTime(): UntypedFormControl {
|
||||
return this.datesForm.get("fallbackExpirationTime") as UntypedFormControl;
|
||||
}
|
||||
|
||||
// Should be able to call these at any time and compute a submitable value
|
||||
get formattedDeletionDate(): string {
|
||||
switch (this.selectedDeletionDatePreset.value as DatePreset) {
|
||||
case DatePreset.Never:
|
||||
this.selectedDeletionDatePreset.setValue(DatePreset.SevenDays);
|
||||
return this.formattedDeletionDate;
|
||||
case DatePreset.Custom:
|
||||
switch (this.browserPath) {
|
||||
case BrowserPath.Safari:
|
||||
case BrowserPath.Firefox:
|
||||
return this.fallbackDeletionDate.value + "T" + this.fallbackDeletionTime.value;
|
||||
default:
|
||||
return this.defaultDeletionDateTime.value;
|
||||
}
|
||||
default: {
|
||||
const now = new Date();
|
||||
const miliseconds = now.setTime(
|
||||
now.getTime() + (this.selectedDeletionDatePreset.value as number) * 60 * 60 * 1000
|
||||
);
|
||||
return new Date(miliseconds).toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get formattedExpirationDate(): string {
|
||||
switch (this.selectedExpirationDatePreset.value as DatePreset) {
|
||||
case DatePreset.Never:
|
||||
return null;
|
||||
case DatePreset.Custom:
|
||||
switch (this.browserPath) {
|
||||
case BrowserPath.Safari:
|
||||
case BrowserPath.Firefox:
|
||||
if (
|
||||
(!this.fallbackExpirationDate.value || !this.fallbackExpirationTime.value) &&
|
||||
this.editMode
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return this.fallbackExpirationDate.value + "T" + this.fallbackExpirationTime.value;
|
||||
default:
|
||||
if (!this.defaultExpirationDateTime.value) {
|
||||
return null;
|
||||
}
|
||||
return this.defaultExpirationDateTime.value;
|
||||
}
|
||||
default: {
|
||||
const now = new Date();
|
||||
const miliseconds = now.setTime(
|
||||
now.getTime() + (this.selectedExpirationDatePreset.value as number) * 60 * 60 * 1000
|
||||
);
|
||||
return new Date(miliseconds).toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
|
||||
get safariDeletionTimePresetOptions() {
|
||||
return this.safariTimePresetOptions(DateField.DeletionDate);
|
||||
}
|
||||
|
||||
get safariExpirationTimePresetOptions() {
|
||||
return this.safariTimePresetOptions(DateField.ExpriationDate);
|
||||
}
|
||||
|
||||
private get nextWeek(): Date {
|
||||
const nextWeek = new Date();
|
||||
nextWeek.setDate(nextWeek.getDate() + 7);
|
||||
return nextWeek;
|
||||
}
|
||||
|
||||
constructor(
|
||||
protected i18nService: I18nService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected datePipe: DatePipe
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.setInitialFormValues();
|
||||
this.emitDates();
|
||||
this.datesForm.valueChanges.subscribe(() => {
|
||||
this.emitDates();
|
||||
});
|
||||
}
|
||||
|
||||
onDeletionDatePresetSelect(value: DatePreset) {
|
||||
this.selectedDeletionDatePreset.setValue(value);
|
||||
}
|
||||
|
||||
clearExpiration() {
|
||||
switch (this.browserPath) {
|
||||
case BrowserPath.Safari:
|
||||
case BrowserPath.Firefox:
|
||||
this.fallbackExpirationDate.setValue(null);
|
||||
this.fallbackExpirationTime.setValue(null);
|
||||
break;
|
||||
case BrowserPath.Default:
|
||||
this.defaultExpirationDateTime.setValue(null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected emitDates() {
|
||||
this.datesChanged.emit({
|
||||
deletionDate: this.formattedDeletionDate,
|
||||
expirationDate: this.formattedExpirationDate,
|
||||
});
|
||||
}
|
||||
|
||||
protected setInitialFormValues() {
|
||||
if (this.editMode) {
|
||||
this.selectedDeletionDatePreset.setValue(DatePreset.Custom);
|
||||
this.selectedExpirationDatePreset.setValue(DatePreset.Custom);
|
||||
switch (this.browserPath) {
|
||||
case BrowserPath.Safari:
|
||||
case BrowserPath.Firefox:
|
||||
this.fallbackDeletionDate.setValue(this.initialDeletionDate.toISOString().slice(0, 10));
|
||||
this.fallbackDeletionTime.setValue(this.initialDeletionDate.toTimeString().slice(0, 5));
|
||||
if (this.initialExpirationDate != null) {
|
||||
this.fallbackExpirationDate.setValue(
|
||||
this.initialExpirationDate.toISOString().slice(0, 10)
|
||||
);
|
||||
this.fallbackExpirationTime.setValue(
|
||||
this.initialExpirationDate.toTimeString().slice(0, 5)
|
||||
);
|
||||
}
|
||||
break;
|
||||
case BrowserPath.Default:
|
||||
if (this.initialExpirationDate) {
|
||||
this.defaultExpirationDateTime.setValue(
|
||||
this.datePipe.transform(new Date(this.initialExpirationDate), "yyyy-MM-ddTHH:mm")
|
||||
);
|
||||
}
|
||||
this.defaultDeletionDateTime.setValue(
|
||||
this.datePipe.transform(new Date(this.initialDeletionDate), "yyyy-MM-ddTHH:mm")
|
||||
);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
this.selectedDeletionDatePreset.setValue(DatePreset.SevenDays);
|
||||
this.selectedExpirationDatePreset.setValue(DatePreset.Never);
|
||||
|
||||
switch (this.browserPath) {
|
||||
case BrowserPath.Safari:
|
||||
this.fallbackDeletionDate.setValue(this.nextWeek.toISOString().slice(0, 10));
|
||||
this.fallbackDeletionTime.setValue(
|
||||
this.safariTimePresetOptions(DateField.DeletionDate)[1].twentyFourHour
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected safariTimePresetOptions(field: DateField): TimeOption[] {
|
||||
// init individual arrays for major sort groups
|
||||
const noon: TimeOption[] = [];
|
||||
const midnight: TimeOption[] = [];
|
||||
const ams: TimeOption[] = [];
|
||||
const pms: TimeOption[] = [];
|
||||
|
||||
// determine minute skip (5 min, 10 min, 15 min, etc.)
|
||||
const minuteIncrementer = 15;
|
||||
|
||||
// loop through each hour on a 12 hour system
|
||||
for (let h = 1; h <= 12; h++) {
|
||||
// loop through each minute in the hour using the skip to incriment
|
||||
for (let m = 0; m < 60; m += minuteIncrementer) {
|
||||
// init the final strings that will be added to the lists
|
||||
let hour = h.toString();
|
||||
let minutes = m.toString();
|
||||
|
||||
// add prepending 0s to single digit hours/minutes
|
||||
if (h < 10) {
|
||||
hour = "0" + hour;
|
||||
}
|
||||
if (m < 10) {
|
||||
minutes = "0" + minutes;
|
||||
}
|
||||
|
||||
// build time strings and push to relevant sort groups
|
||||
if (h === 12) {
|
||||
const midnightOption: TimeOption = {
|
||||
twelveHour: `${hour}:${minutes} AM`,
|
||||
twentyFourHour: `00:${minutes}`,
|
||||
};
|
||||
midnight.push(midnightOption);
|
||||
|
||||
const noonOption: TimeOption = {
|
||||
twelveHour: `${hour}:${minutes} PM`,
|
||||
twentyFourHour: `${hour}:${minutes}`,
|
||||
};
|
||||
noon.push(noonOption);
|
||||
} else {
|
||||
const amOption: TimeOption = {
|
||||
twelveHour: `${hour}:${minutes} AM`,
|
||||
twentyFourHour: `${hour}:${minutes}`,
|
||||
};
|
||||
ams.push(amOption);
|
||||
|
||||
const pmOption: TimeOption = {
|
||||
twelveHour: `${hour}:${minutes} PM`,
|
||||
twentyFourHour: `${h + 12}:${minutes}`,
|
||||
};
|
||||
pms.push(pmOption);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// bring all the arrays together in the right order
|
||||
const validTimes = [...midnight, ...ams, ...noon, ...pms];
|
||||
|
||||
// determine if an unsupported value already exists on the send & add that to the top of the option list
|
||||
// example: if the Send was created with a different client
|
||||
if (field === DateField.ExpriationDate && this.initialExpirationDate != null && this.editMode) {
|
||||
const previousValue: TimeOption = {
|
||||
twelveHour: this.datePipe.transform(this.initialExpirationDate, "hh:mm a"),
|
||||
twentyFourHour: this.datePipe.transform(this.initialExpirationDate, "HH:mm"),
|
||||
};
|
||||
return [previousValue, { twelveHour: null, twentyFourHour: null }, ...validTimes];
|
||||
} else if (
|
||||
field === DateField.DeletionDate &&
|
||||
this.initialDeletionDate != null &&
|
||||
this.editMode
|
||||
) {
|
||||
const previousValue: TimeOption = {
|
||||
twelveHour: this.datePipe.transform(this.initialDeletionDate, "hh:mm a"),
|
||||
twentyFourHour: this.datePipe.transform(this.initialDeletionDate, "HH:mm"),
|
||||
};
|
||||
return [previousValue, ...validTimes];
|
||||
} else {
|
||||
return [{ twelveHour: null, twentyFourHour: null }, ...validTimes];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,225 +0,0 @@
|
||||
import { Directive, NgZone, OnDestroy, OnInit } from "@angular/core";
|
||||
import { Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||
import { SendApiService } from "@bitwarden/common/abstractions/send/send-api.service.abstraction";
|
||||
import { SendService } from "@bitwarden/common/abstractions/send/send.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums/policy-type";
|
||||
import { SendType } from "@bitwarden/common/enums/sendType";
|
||||
import { SendView } from "@bitwarden/common/models/view/send.view";
|
||||
|
||||
@Directive()
|
||||
export class SendComponent implements OnInit, OnDestroy {
|
||||
disableSend = false;
|
||||
sendType = SendType;
|
||||
loaded = false;
|
||||
loading = true;
|
||||
refreshing = false;
|
||||
expired = false;
|
||||
type: SendType = null;
|
||||
sends: SendView[] = [];
|
||||
filteredSends: SendView[] = [];
|
||||
searchText: string;
|
||||
selectedType: SendType;
|
||||
selectedAll: boolean;
|
||||
searchPlaceholder: string;
|
||||
filter: (cipher: SendView) => boolean;
|
||||
searchPending = false;
|
||||
hasSearched = false; // search() function called - returns true if text qualifies for search
|
||||
|
||||
actionPromise: any;
|
||||
onSuccessfulRemovePassword: () => Promise<any>;
|
||||
onSuccessfulDelete: () => Promise<any>;
|
||||
onSuccessfulLoad: () => Promise<any>;
|
||||
|
||||
private searchTimeout: any;
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
protected sendService: SendService,
|
||||
protected i18nService: I18nService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected environmentService: EnvironmentService,
|
||||
protected ngZone: NgZone,
|
||||
protected searchService: SearchService,
|
||||
protected policyService: PolicyService,
|
||||
private logService: LogService,
|
||||
protected sendApiService: SendApiService
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.policyService
|
||||
.policyAppliesToActiveUser$(PolicyType.DisableSend)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((policyAppliesToActiveUser) => {
|
||||
this.disableSend = policyAppliesToActiveUser;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
async load(filter: (send: SendView) => boolean = null) {
|
||||
this.loading = true;
|
||||
this.sendService.sendViews$.pipe(takeUntil(this.destroy$)).subscribe((sends) => {
|
||||
this.sends = sends;
|
||||
});
|
||||
if (this.onSuccessfulLoad != null) {
|
||||
await this.onSuccessfulLoad();
|
||||
} else {
|
||||
// Default action
|
||||
this.selectAll();
|
||||
}
|
||||
this.loading = false;
|
||||
this.loaded = true;
|
||||
}
|
||||
|
||||
async reload(filter: (send: SendView) => boolean = null) {
|
||||
this.loaded = false;
|
||||
this.sends = [];
|
||||
await this.load(filter);
|
||||
}
|
||||
|
||||
async refresh() {
|
||||
try {
|
||||
this.refreshing = true;
|
||||
await this.reload(this.filter);
|
||||
} finally {
|
||||
this.refreshing = false;
|
||||
}
|
||||
}
|
||||
|
||||
async applyFilter(filter: (send: SendView) => boolean = null) {
|
||||
this.filter = filter;
|
||||
await this.search(null);
|
||||
}
|
||||
|
||||
async search(timeout: number = null) {
|
||||
this.searchPending = false;
|
||||
if (this.searchTimeout != null) {
|
||||
clearTimeout(this.searchTimeout);
|
||||
}
|
||||
if (timeout == null) {
|
||||
this.hasSearched = this.searchService.isSearchable(this.searchText);
|
||||
this.filteredSends = this.sends.filter((s) => this.filter == null || this.filter(s));
|
||||
this.applyTextSearch();
|
||||
return;
|
||||
}
|
||||
this.searchPending = true;
|
||||
this.searchTimeout = setTimeout(async () => {
|
||||
this.hasSearched = this.searchService.isSearchable(this.searchText);
|
||||
this.filteredSends = this.sends.filter((s) => this.filter == null || this.filter(s));
|
||||
this.applyTextSearch();
|
||||
this.searchPending = false;
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
async removePassword(s: SendView): Promise<boolean> {
|
||||
if (this.actionPromise != null || s.password == null) {
|
||||
return;
|
||||
}
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("removePasswordConfirmation"),
|
||||
this.i18nService.t("removePassword"),
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("no"),
|
||||
"warning"
|
||||
);
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
this.actionPromise = this.sendApiService.removePassword(s.id);
|
||||
await this.actionPromise;
|
||||
if (this.onSuccessfulRemovePassword != null) {
|
||||
this.onSuccessfulRemovePassword();
|
||||
} else {
|
||||
// Default actions
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("removedPassword"));
|
||||
await this.load();
|
||||
}
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
this.actionPromise = null;
|
||||
}
|
||||
|
||||
async delete(s: SendView): Promise<boolean> {
|
||||
if (this.actionPromise != null) {
|
||||
return false;
|
||||
}
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("deleteSendConfirmation"),
|
||||
this.i18nService.t("deleteSend"),
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("no"),
|
||||
"warning"
|
||||
);
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
this.actionPromise = this.sendApiService.delete(s.id);
|
||||
await this.actionPromise;
|
||||
|
||||
if (this.onSuccessfulDelete != null) {
|
||||
this.onSuccessfulDelete();
|
||||
} else {
|
||||
// Default actions
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("deletedSend"));
|
||||
await this.refresh();
|
||||
}
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
this.actionPromise = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
copy(s: SendView) {
|
||||
const sendLinkBaseUrl = this.environmentService.getSendUrl();
|
||||
const link = sendLinkBaseUrl + s.accessId + "/" + s.urlB64Key;
|
||||
this.platformUtilsService.copyToClipboard(link);
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("valueCopied", this.i18nService.t("sendLink"))
|
||||
);
|
||||
}
|
||||
|
||||
searchTextChanged() {
|
||||
this.search(200);
|
||||
}
|
||||
|
||||
selectAll() {
|
||||
this.clearSelections();
|
||||
this.selectedAll = true;
|
||||
this.applyFilter(null);
|
||||
}
|
||||
|
||||
selectType(type: SendType) {
|
||||
this.clearSelections();
|
||||
this.selectedType = type;
|
||||
this.applyFilter((s) => s.type === type);
|
||||
}
|
||||
|
||||
clearSelections() {
|
||||
this.selectedAll = false;
|
||||
this.selectedType = null;
|
||||
}
|
||||
|
||||
private applyTextSearch() {
|
||||
if (this.searchText != null) {
|
||||
this.filteredSends = this.searchService.searchSends(this.filteredSends, this.searchText);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user