mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 22:33:35 +00:00
[PM-6072] Add get$ method on SendService (#7839)
* Added missing get$ method on SendService * Added distinctUntilChanged to the get$ method and added more tests * Added more validations and tests to get$ * Added some more test cases to get$ * Refactored test cases from get$
This commit is contained in:
@@ -18,7 +18,17 @@ export abstract class SendService {
|
|||||||
password: string,
|
password: string,
|
||||||
key?: SymmetricCryptoKey,
|
key?: SymmetricCryptoKey,
|
||||||
) => Promise<[Send, EncArrayBuffer]>;
|
) => Promise<[Send, EncArrayBuffer]>;
|
||||||
|
/**
|
||||||
|
* @deprecated Do not call this, use the get$ method
|
||||||
|
*/
|
||||||
get: (id: string) => Send;
|
get: (id: string) => Send;
|
||||||
|
/**
|
||||||
|
* Provides a send for a determined id
|
||||||
|
* updates after a change occurs to the send that matches the id
|
||||||
|
* @param id The id of the desired send
|
||||||
|
* @returns An observable that listens to the value of the desired send
|
||||||
|
*/
|
||||||
|
get$: (id: string) => Observable<Send | undefined>;
|
||||||
/**
|
/**
|
||||||
* Provides re-encrypted user sends for the key rotation process
|
* Provides re-encrypted user sends for the key rotation process
|
||||||
* @param newUserKey The new user key to use for re-encryption
|
* @param newUserKey The new user key to use for re-encryption
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ import { EncString } from "../../../platform/models/domain/enc-string";
|
|||||||
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
||||||
import { ContainerService } from "../../../platform/services/container.service";
|
import { ContainerService } from "../../../platform/services/container.service";
|
||||||
import { UserKey } from "../../../types/key";
|
import { UserKey } from "../../../types/key";
|
||||||
|
import { SendType } from "../enums/send-type";
|
||||||
|
import { SendFileApi } from "../models/api/send-file.api";
|
||||||
|
import { SendTextApi } from "../models/api/send-text.api";
|
||||||
|
import { SendFileData } from "../models/data/send-file.data";
|
||||||
|
import { SendTextData } from "../models/data/send-text.data";
|
||||||
import { SendData } from "../models/data/send.data";
|
import { SendData } from "../models/data/send.data";
|
||||||
import { Send } from "../models/domain/send";
|
import { Send } from "../models/domain/send";
|
||||||
import { SendView } from "../models/view/send.view";
|
import { SendView } from "../models/view/send.view";
|
||||||
@@ -67,6 +72,295 @@ describe("SendService", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("get$", () => {
|
||||||
|
it("exists", async () => {
|
||||||
|
const result = await firstValueFrom(sendService.get$("1"));
|
||||||
|
|
||||||
|
expect(result).toEqual(send("1", "Test Send"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not exist", async () => {
|
||||||
|
const result = await firstValueFrom(sendService.get$("2"));
|
||||||
|
|
||||||
|
expect(result).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updated observable", async () => {
|
||||||
|
const singleSendObservable = sendService.get$("1");
|
||||||
|
const result = await firstValueFrom(singleSendObservable);
|
||||||
|
expect(result).toEqual(send("1", "Test Send"));
|
||||||
|
|
||||||
|
await sendService.replace({
|
||||||
|
"1": sendData("1", "Test Send Updated"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const result2 = await firstValueFrom(singleSendObservable);
|
||||||
|
expect(result2).toEqual(send("1", "Test Send Updated"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reports a change when name changes on a new send", async () => {
|
||||||
|
let changed = false;
|
||||||
|
sendService.get$("1").subscribe(() => {
|
||||||
|
changed = true;
|
||||||
|
});
|
||||||
|
const sendDataObject = sendData("1", "Test Send 2");
|
||||||
|
|
||||||
|
//it is immediately called when subscribed, we need to reset the value
|
||||||
|
changed = false;
|
||||||
|
await sendService.replace({
|
||||||
|
"1": sendDataObject,
|
||||||
|
"2": sendData("2", "Test Send 2"),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(changed).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reports a change when notes changes on a new send", async () => {
|
||||||
|
const sendDataObject = createSendData() as SendData;
|
||||||
|
|
||||||
|
await sendService.replace({
|
||||||
|
"1": sendDataObject,
|
||||||
|
"2": sendData("2", "Test Send 2"),
|
||||||
|
});
|
||||||
|
|
||||||
|
let changed = false;
|
||||||
|
sendService.get$("1").subscribe(() => {
|
||||||
|
changed = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
sendDataObject.notes = "New notes";
|
||||||
|
//it is immediately called when subscribed, we need to reset the value
|
||||||
|
changed = false;
|
||||||
|
|
||||||
|
await sendService.replace({
|
||||||
|
"1": sendDataObject,
|
||||||
|
"2": sendData("2", "Test Send 2"),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(changed).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reports a change when Text changes on a new send", async () => {
|
||||||
|
const sendDataObject = createSendData() as SendData;
|
||||||
|
|
||||||
|
await sendService.replace({
|
||||||
|
"1": sendDataObject,
|
||||||
|
"2": sendData("2", "Test Send 2"),
|
||||||
|
});
|
||||||
|
|
||||||
|
let changed = false;
|
||||||
|
sendService.get$("1").subscribe(() => {
|
||||||
|
changed = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
//it is immediately called when subscribed, we need to reset the value
|
||||||
|
changed = false;
|
||||||
|
|
||||||
|
sendDataObject.text.text = "new text";
|
||||||
|
await sendService.replace({
|
||||||
|
"1": sendDataObject,
|
||||||
|
"2": sendData("2", "Test Send 2"),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(changed).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reports a change when Text is set as null on a new send", async () => {
|
||||||
|
const sendDataObject = createSendData() as SendData;
|
||||||
|
|
||||||
|
await sendService.replace({
|
||||||
|
"1": sendDataObject,
|
||||||
|
"2": sendData("2", "Test Send 2"),
|
||||||
|
});
|
||||||
|
|
||||||
|
let changed = false;
|
||||||
|
sendService.get$("1").subscribe(() => {
|
||||||
|
changed = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
//it is immediately called when subscribed, we need to reset the value
|
||||||
|
changed = false;
|
||||||
|
|
||||||
|
sendDataObject.text = null;
|
||||||
|
await sendService.replace({
|
||||||
|
"1": sendDataObject,
|
||||||
|
"2": sendData("2", "Test Send 2"),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(changed).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Doesn't reports a change when File changes on a new send", async () => {
|
||||||
|
const sendDataObject = createSendData({
|
||||||
|
type: SendType.File,
|
||||||
|
file: new SendFileData(new SendFileApi({ FileName: "name of file" })),
|
||||||
|
}) as SendData;
|
||||||
|
await sendService.replace({
|
||||||
|
"1": sendDataObject,
|
||||||
|
"2": sendData("2", "Test Send 2"),
|
||||||
|
});
|
||||||
|
|
||||||
|
sendDataObject.file = new SendFileData(new SendFileApi({ FileName: "updated name of file" }));
|
||||||
|
let changed = false;
|
||||||
|
sendService.get$("1").subscribe(() => {
|
||||||
|
changed = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
//it is immediately called when subscribed, we need to reset the value
|
||||||
|
changed = false;
|
||||||
|
|
||||||
|
await sendService.replace({
|
||||||
|
"1": sendDataObject,
|
||||||
|
"2": sendData("2", "Test Send 2"),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(changed).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reports a change when key changes on a new send", async () => {
|
||||||
|
const sendDataObject = createSendData() as SendData;
|
||||||
|
|
||||||
|
await sendService.replace({
|
||||||
|
"1": sendDataObject,
|
||||||
|
"2": sendData("2", "Test Send 2"),
|
||||||
|
});
|
||||||
|
|
||||||
|
let changed = false;
|
||||||
|
sendService.get$("1").subscribe(() => {
|
||||||
|
changed = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
//it is immediately called when subscribed, we need to reset the value
|
||||||
|
changed = false;
|
||||||
|
|
||||||
|
sendDataObject.key = "newKey";
|
||||||
|
await sendService.replace({
|
||||||
|
"1": sendDataObject,
|
||||||
|
"2": sendData("2", "Test Send 2"),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(changed).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reports a change when revisionDate changes on a new send", async () => {
|
||||||
|
const sendDataObject = createSendData() as SendData;
|
||||||
|
|
||||||
|
await sendService.replace({
|
||||||
|
"1": sendDataObject,
|
||||||
|
"2": sendData("2", "Test Send 2"),
|
||||||
|
});
|
||||||
|
|
||||||
|
let changed = false;
|
||||||
|
sendService.get$("1").subscribe(() => {
|
||||||
|
changed = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
//it is immediately called when subscribed, we need to reset the value
|
||||||
|
changed = false;
|
||||||
|
|
||||||
|
sendDataObject.revisionDate = "2025-04-05";
|
||||||
|
await sendService.replace({
|
||||||
|
"1": sendDataObject,
|
||||||
|
"2": sendData("2", "Test Send 2"),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(changed).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reports a change when a property is set as null on a new send", async () => {
|
||||||
|
const sendDataObject = createSendData() as SendData;
|
||||||
|
|
||||||
|
await sendService.replace({
|
||||||
|
"1": sendDataObject,
|
||||||
|
"2": sendData("2", "Test Send 2"),
|
||||||
|
});
|
||||||
|
|
||||||
|
let changed = false;
|
||||||
|
sendService.get$("1").subscribe(() => {
|
||||||
|
changed = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
//it is immediately called when subscribed, we need to reset the value
|
||||||
|
changed = false;
|
||||||
|
|
||||||
|
sendDataObject.name = null;
|
||||||
|
await sendService.replace({
|
||||||
|
"1": sendDataObject,
|
||||||
|
"2": sendData("2", "Test Send 2"),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(changed).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not reports a change when text's text is set as null on a new send and old send and reports a change then new send sets a text", async () => {
|
||||||
|
const sendDataObject = createSendData({
|
||||||
|
text: new SendTextData(new SendTextApi({ Text: null })),
|
||||||
|
}) as SendData;
|
||||||
|
|
||||||
|
await sendService.replace({
|
||||||
|
"1": sendDataObject,
|
||||||
|
"2": sendData("2", "Test Send 2"),
|
||||||
|
});
|
||||||
|
|
||||||
|
let changed = false;
|
||||||
|
sendService.get$("1").subscribe(() => {
|
||||||
|
changed = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
//it is immediately called when subscribed, we need to reset the value
|
||||||
|
changed = false;
|
||||||
|
|
||||||
|
await sendService.replace({
|
||||||
|
"1": sendDataObject,
|
||||||
|
"2": sendData("2", "Test Send 2"),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(changed).toEqual(false);
|
||||||
|
|
||||||
|
sendDataObject.text.text = "Asdf";
|
||||||
|
await sendService.replace({
|
||||||
|
"1": sendDataObject,
|
||||||
|
"2": sendData("2", "Test Send 2"),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(changed).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("do not reports a change when nothing changes on the observed send", async () => {
|
||||||
|
let changed = false;
|
||||||
|
sendService.get$("1").subscribe(() => {
|
||||||
|
changed = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
const sendDataObject = sendData("1", "Test Send");
|
||||||
|
|
||||||
|
//it is immediately called when subscribed, we need to reset the value
|
||||||
|
changed = false;
|
||||||
|
|
||||||
|
await sendService.replace({
|
||||||
|
"1": sendDataObject,
|
||||||
|
"2": sendData("3", "Test Send 3"),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(changed).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reports a change when the observed send is deleted", async () => {
|
||||||
|
let changed = false;
|
||||||
|
sendService.get$("1").subscribe(() => {
|
||||||
|
changed = true;
|
||||||
|
});
|
||||||
|
//it is immediately called when subscribed, we need to reset the value
|
||||||
|
changed = false;
|
||||||
|
|
||||||
|
await sendService.replace({
|
||||||
|
"2": sendData("2", "Test Send 2"),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(changed).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("getAll", async () => {
|
it("getAll", async () => {
|
||||||
const sends = await sendService.getAll();
|
const sends = await sendService.getAll();
|
||||||
const send1 = sends[0];
|
const send1 = sends[0];
|
||||||
@@ -184,6 +478,33 @@ describe("SendService", () => {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultSendData: Partial<SendData> = {
|
||||||
|
id: "1",
|
||||||
|
name: "Test Send",
|
||||||
|
accessId: "123",
|
||||||
|
type: SendType.Text,
|
||||||
|
notes: "notes!",
|
||||||
|
file: null,
|
||||||
|
text: new SendTextData(new SendTextApi({ Text: "send text" })),
|
||||||
|
key: "key",
|
||||||
|
maxAccessCount: 12,
|
||||||
|
accessCount: 2,
|
||||||
|
revisionDate: "2024-09-04",
|
||||||
|
expirationDate: "2024-09-04",
|
||||||
|
deletionDate: "2024-09-04",
|
||||||
|
password: "password",
|
||||||
|
disabled: false,
|
||||||
|
hideEmail: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
function createSendData(value: Partial<SendData> = {}) {
|
||||||
|
const testSend: any = {};
|
||||||
|
for (const prop in defaultSendData) {
|
||||||
|
testSend[prop] = value[prop as keyof SendData] ?? defaultSendData[prop as keyof SendData];
|
||||||
|
}
|
||||||
|
return testSend;
|
||||||
|
}
|
||||||
|
|
||||||
function sendView(id: string, name: string) {
|
function sendView(id: string, name: string) {
|
||||||
const data = new SendView({} as any);
|
const data = new SendView({} as any);
|
||||||
data.id = id;
|
data.id = id;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { BehaviorSubject, concatMap } from "rxjs";
|
import { BehaviorSubject, Observable, concatMap, distinctUntilChanged, map } from "rxjs";
|
||||||
|
|
||||||
import { CryptoFunctionService } from "../../../platform/abstractions/crypto-function.service";
|
import { CryptoFunctionService } from "../../../platform/abstractions/crypto-function.service";
|
||||||
import { CryptoService } from "../../../platform/abstractions/crypto.service";
|
import { CryptoService } from "../../../platform/abstractions/crypto.service";
|
||||||
@@ -116,6 +116,68 @@ export class SendService implements InternalSendServiceAbstraction {
|
|||||||
return sends.find((send) => send.id === id);
|
return sends.find((send) => send.id === id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get$(id: string): Observable<Send | undefined> {
|
||||||
|
return this.sends$.pipe(
|
||||||
|
distinctUntilChanged((oldSends, newSends) => {
|
||||||
|
const oldSend = oldSends.find((oldSend) => oldSend.id === id);
|
||||||
|
const newSend = newSends.find((newSend) => newSend.id === id);
|
||||||
|
if (!oldSend || !newSend) {
|
||||||
|
// If either oldSend or newSend is not found, consider them different
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare each property of the old and new Send objects
|
||||||
|
const allPropertiesSame = Object.keys(newSend).every((key) => {
|
||||||
|
if (
|
||||||
|
(oldSend[key as keyof Send] != null && newSend[key as keyof Send] === null) ||
|
||||||
|
(oldSend[key as keyof Send] === null && newSend[key as keyof Send] != null)
|
||||||
|
) {
|
||||||
|
// If a key from either old or new send is not found, and the key from the other send has a value, consider them different
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (key) {
|
||||||
|
case "name":
|
||||||
|
case "notes":
|
||||||
|
case "key":
|
||||||
|
if (oldSend[key] === null && newSend[key] === null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return oldSend[key].encryptedString === newSend[key].encryptedString;
|
||||||
|
case "text":
|
||||||
|
if (oldSend[key].text == null && newSend[key].text == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
(oldSend[key].text != null && newSend[key].text == null) ||
|
||||||
|
(oldSend[key].text == null && newSend[key].text != null)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return oldSend[key].text.encryptedString === newSend[key].text.encryptedString;
|
||||||
|
case "file":
|
||||||
|
//Files are never updated so never will be changed.
|
||||||
|
return true;
|
||||||
|
case "revisionDate":
|
||||||
|
case "expirationDate":
|
||||||
|
case "deletionDate":
|
||||||
|
if (oldSend[key] === null && newSend[key] === null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return oldSend[key].getTime() === newSend[key].getTime();
|
||||||
|
default:
|
||||||
|
// For other properties, compare directly
|
||||||
|
return oldSend[key as keyof Send] === newSend[key as keyof Send];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return allPropertiesSame;
|
||||||
|
}),
|
||||||
|
map((sends) => sends.find((o) => o.id === id)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async getFromState(id: string): Promise<Send> {
|
async getFromState(id: string): Promise<Send> {
|
||||||
const sends = await this.stateService.getEncryptedSends();
|
const sends = await this.stateService.getEncryptedSends();
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
|
|||||||
Reference in New Issue
Block a user