passbolt-browser-extension 5.2.0 → 5.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +79 -1
- package/Gruntfile.js +1 -1
- package/RELEASE_NOTES.md +27 -71
- package/doc/browser-extension-class-diagram.md +9 -0
- package/package.json +5 -5
- package/src/all/_locales/sl/messages.json +2 -2
- package/src/all/background_page/controller/clipboard/cancelClipboardContentFlushController.js +51 -0
- package/src/all/background_page/controller/clipboard/cancelClipboardContentFlushController.test.js +46 -0
- package/src/all/background_page/controller/clipboard/copyTemporarilyToClipboardController.js +53 -0
- package/src/all/background_page/controller/clipboard/copyTemporarilyToClipboardController.test.js +47 -0
- package/src/all/background_page/controller/clipboard/{clipboardController.js → copyToClipboardController.js} +8 -8
- package/src/all/background_page/controller/clipboard/copyToClipboardController.test.js +47 -0
- package/src/all/background_page/controller/extension/getExtensionVersionController.test.js +1 -1
- package/src/all/background_page/controller/metadata/getOrFindMetadataKeysSettingsController.js +53 -0
- package/src/all/background_page/controller/metadata/getOrFindMetadataKeysSettingsController.test.js +106 -0
- package/src/all/background_page/controller/resource/updateResourceLocalStorageByFolderParentIdController.js +70 -0
- package/src/all/background_page/controller/resource/updateResourceLocalStorageByFolderParentIdController.test.js +78 -0
- package/src/all/background_page/controller/share/shareOneFolderController.test.js +2 -1
- package/src/all/background_page/controller/share/shareResourcesController.test.js +1 -1
- package/src/all/background_page/controller/user/deleteDryRunUserController.js +73 -0
- package/src/all/background_page/controller/user/deleteDryRunUserController.test.js +129 -0
- package/src/all/background_page/controller/user/deleteUserController.js +76 -0
- package/src/all/background_page/controller/user/deleteUserController.test.js +141 -0
- package/src/all/background_page/event/actionLogEvents.js +5 -12
- package/src/all/background_page/event/appEvents.js +33 -0
- package/src/all/background_page/event/findAllForActionLogController.js +58 -0
- package/src/all/background_page/event/findAllForActionLogController.test.js +43 -0
- package/src/all/background_page/event/quickAccessEvents.js +32 -0
- package/src/all/background_page/event/resourceEvents.js +11 -0
- package/src/all/background_page/event/userEvents.js +7 -20
- package/src/all/background_page/event/webIntegrationEvents.js +11 -0
- package/src/all/background_page/model/actionLog/{actionLogModel.js → findActionLogService.js} +25 -5
- package/src/all/background_page/model/actionLog/findActionLogService.test.js +61 -0
- package/src/all/background_page/model/comment/commentModel.js +5 -5
- package/src/all/background_page/model/entity/actionLog/actionLogsCollection.test.data.js +18 -0
- package/src/all/background_page/model/entity/actionLog/defaultActionLogEntity.test.data.js +34 -0
- package/src/all/background_page/model/entity/resource/resourcesCollection.js +25 -0
- package/src/all/background_page/model/entity/resource/resourcesCollection.test.js +34 -0
- package/src/all/background_page/model/import/resources/resourcesKdbxImportParser.test.js +0 -1
- package/src/all/background_page/model/user/userModel.js +0 -60
- package/src/all/background_page/pagemod/appPagemod.js +0 -2
- package/src/all/background_page/pagemod/appPagemod.test.js +2 -6
- package/src/all/background_page/service/alarm/globalAlarmService.js +2 -0
- package/src/all/background_page/service/api/actionLog/{actionLogService.js → actionLogApiService.js} +5 -4
- package/src/all/background_page/service/api/actionLog/actionLogApiService.test.js +55 -0
- package/src/all/background_page/service/api/comment/{commentService.js → commentApiService.js} +6 -7
- package/src/all/background_page/service/api/comment/commentApiService.test.js +122 -0
- package/src/all/background_page/service/api/resource/resourceService.js +1 -0
- package/src/all/background_page/service/auth/postLogoutService.js +2 -0
- package/src/all/background_page/service/auth/postLogoutService.test.js +4 -1
- package/src/all/background_page/service/browser/browserService.js +22 -0
- package/src/all/background_page/service/clipboard/clipboardProviderService.js +40 -0
- package/src/all/background_page/service/clipboard/clipboardProviderService.test.js +61 -0
- package/src/all/background_page/service/clipboard/copyToClipboardService.js +123 -0
- package/src/all/background_page/service/clipboard/copyToClipboardService.test.js +174 -0
- package/src/all/background_page/service/resource/findAndUpdateResourcesLocalStorageService.js +52 -1
- package/src/all/background_page/service/resource/findAndUpdateResourcesLocalStorageService.test.js +100 -0
- package/src/all/background_page/service/resource/findResourcesService.js +53 -19
- package/src/all/background_page/service/resource/findResourcesService.test.js +191 -0
- package/src/all/background_page/service/resourceType/updateResourceTypesService.test.js +1 -1
- package/src/all/background_page/service/share/shareResourceService.test.js +1 -1
- package/src/all/background_page/service/user/deleteUserService.js +97 -0
- package/src/all/background_page/service/user/deleteUserService.test.js +178 -0
- package/src/all/locales/de-DE/common.json +2 -2
- package/src/all/locales/es-ES/common.json +2 -2
- package/src/all/locales/fr-FR/common.json +5 -5
- package/src/all/locales/it-IT/common.json +2 -2
- package/src/all/locales/ja-JP/common.json +2 -2
- package/src/all/locales/ko-KR/common.json +2 -2
- package/src/all/locales/lt-LT/common.json +2 -2
- package/src/all/locales/nl-NL/common.json +2 -2
- package/src/all/locales/pl-PL/common.json +5 -5
- package/src/all/locales/pt-BR/common.json +2 -2
- package/src/all/locales/ro-RO/common.json +2 -2
- package/src/all/locales/ru-RU/common.json +2 -2
- package/src/all/locales/sl-SI/common.json +33 -33
- package/src/all/locales/sv-SE/common.json +2 -2
- package/src/all/locales/uk-UA/common.json +3 -3
- package/src/chrome/manifest.json +1 -1
- package/src/chrome/polyfill/clipboard/edgeBackgroundPageClipboardService.js +31 -0
- package/src/chrome/polyfill/clipboard/edgeBackgroundPageClipboardService.test.js +51 -0
- package/src/chrome-mv3/index.js +3 -3
- package/src/chrome-mv3/manifest.json +1 -1
- package/src/chrome-mv3/offscreens/{fetch.html → offscreen.html} +1 -1
- package/src/chrome-mv3/offscreens/{fetch.js → offscreen.js} +2 -2
- package/src/chrome-mv3/offscreens/service/clipboard/writeClipobardOffscreenService.js +54 -0
- package/src/chrome-mv3/offscreens/service/clipboard/writeClipobardOffscreenService.test.js +56 -0
- package/src/chrome-mv3/offscreens/service/network/fetchOffscreenService.js +36 -44
- package/src/chrome-mv3/offscreens/service/network/fetchOffscreenService.test.data.js +0 -1
- package/src/chrome-mv3/offscreens/service/network/fetchOffscreenService.test.js +90 -120
- package/src/chrome-mv3/offscreens/service/offscreen/handleOffscreenRequestService.js +85 -0
- package/src/chrome-mv3/offscreens/service/offscreen/handleOffscreenRequestService.test.js +99 -0
- package/src/chrome-mv3/polyfill/clipboardOffscreenPolyfill.js +19 -0
- package/src/chrome-mv3/serviceWorker/service/clipboard/requestClipboardOffscreenService.js +51 -0
- package/src/chrome-mv3/serviceWorker/service/clipboard/requestClipboardOffscreenService.test.js +70 -0
- package/src/chrome-mv3/serviceWorker/service/clipboard/responseClipboardOffscreenService.js +25 -0
- package/src/chrome-mv3/serviceWorker/service/clipboard/responseClipboardOffscreenService.test.data.js +21 -0
- package/src/chrome-mv3/serviceWorker/service/clipboard/responseClipboardOffscreenService.test.js +33 -0
- package/src/chrome-mv3/serviceWorker/service/network/requestFetchOffscreenService.js +25 -50
- package/src/chrome-mv3/serviceWorker/service/network/requestFetchOffscreenService.test.js +16 -39
- package/src/chrome-mv3/serviceWorker/service/network/responseFetchOffscreenService.js +14 -45
- package/src/chrome-mv3/serviceWorker/service/network/responseFetchOffscreenService.test.js +5 -37
- package/src/chrome-mv3/serviceWorker/service/offscreen/createOffscreenDocumentService.js +43 -0
- package/src/chrome-mv3/serviceWorker/service/offscreen/createOffscreenDocumentService.test.js +48 -0
- package/src/chrome-mv3/serviceWorker/service/offscreen/handleOffscreenResponseService.js +119 -0
- package/src/chrome-mv3/serviceWorker/service/offscreen/handleOffscreenResponseService.test.js +159 -0
- package/src/firefox/manifest.json +1 -1
- package/src/safari/manifest.json +1 -1
- package/test/jest.setup.js +4 -0
- package/test/mocks/mockNavigatorClipboard.js +40 -0
- package/test/mocks/mockWebExtensionPolyfill.js +2 -1
- package/{webpack-offscreens.fetch.config.js → webpack-offscreens.config.js} +1 -1
- package/webpack.service-worker.config.js +1 -0
- package/src/all/background_page/controller/clipboard/clipboardController.test.js +0 -68
- package/src/all/background_page/event/clipboardEvents.js +0 -28
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Passbolt ~ Open source password manager for teams
|
|
3
|
+
* Copyright (c) Passbolt SA (https://www.passbolt.com)
|
|
4
|
+
*
|
|
5
|
+
* Licensed under GNU Affero General Public License version 3 of the or any later version.
|
|
6
|
+
* For full copyright and license information, please see the LICENSE.txt
|
|
7
|
+
* Redistributions of files must retain the above copyright notice.
|
|
8
|
+
*
|
|
9
|
+
* @copyright Copyright (c) Passbolt SA (https://www.passbolt.com)
|
|
10
|
+
* @license https://opensource.org/licenses/AGPL-3.0 AGPL License
|
|
11
|
+
* @link https://www.passbolt.com Passbolt(tm)
|
|
12
|
+
* @since 5.3.2
|
|
13
|
+
*/
|
|
14
|
+
import "../../../../../test/mocks/mockNavigatorClipboard";
|
|
15
|
+
import ClipboardProviderService from "./clipboardProviderService";
|
|
16
|
+
import BrowserService from "../browser/browserService";
|
|
17
|
+
import EdgeBackgroundPageClipboardService from "../../../../chrome/polyfill/clipboard/edgeBackgroundPageClipboardService";
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
global.customNavigatorClipboard = undefined;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("ClipboardProviderService", () => {
|
|
24
|
+
describe("::getClipboard", () => {
|
|
25
|
+
it("should return the customNavigatorClipboard if it is set", async() => {
|
|
26
|
+
expect.assertions(1);
|
|
27
|
+
|
|
28
|
+
const customNavigatorClipboard = {writeText: jest.fn()};
|
|
29
|
+
global.customNavigatorClipboard = customNavigatorClipboard;
|
|
30
|
+
|
|
31
|
+
expect(ClipboardProviderService.getClipboard()).toStrictEqual(customNavigatorClipboard);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("should return the EdgeBackgroundPageClipboardService if not on Firefox and is on MV2", async() => {
|
|
35
|
+
expect.assertions(1);
|
|
36
|
+
|
|
37
|
+
chrome.runtime.getManifest.mockImplementation(() => ({manifest_version: 2}));
|
|
38
|
+
jest.spyOn(BrowserService, "isFirefox").mockImplementation(() => false);
|
|
39
|
+
|
|
40
|
+
expect(ClipboardProviderService.getClipboard()).toStrictEqual(EdgeBackgroundPageClipboardService);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should return the navigator.clipboard in every other cases: Firefox", async() => {
|
|
44
|
+
expect.assertions(1);
|
|
45
|
+
|
|
46
|
+
chrome.runtime.getManifest.mockImplementation(() => ({manifest_version: 2}));
|
|
47
|
+
jest.spyOn(BrowserService, "isFirefox").mockImplementation(() => true);
|
|
48
|
+
|
|
49
|
+
expect(ClipboardProviderService.getClipboard()).toStrictEqual(navigator.clipboard);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("should return the navigator.clipboard in every other cases: Firefox + mv3", async() => {
|
|
53
|
+
expect.assertions(1);
|
|
54
|
+
|
|
55
|
+
chrome.runtime.getManifest.mockImplementation(() => ({manifest_version: 3}));
|
|
56
|
+
jest.spyOn(BrowserService, "isFirefox").mockImplementation(() => true);
|
|
57
|
+
|
|
58
|
+
expect(ClipboardProviderService.getClipboard()).toStrictEqual(navigator.clipboard);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Passbolt ~ Open source password manager for teams
|
|
3
|
+
* Copyright (c) Passbolt SA (https://www.passbolt.com)
|
|
4
|
+
*
|
|
5
|
+
* Licensed under GNU Affero General Public License version 3 of the or any later version.
|
|
6
|
+
* For full copyright and license information, please see the LICENSE.txt
|
|
7
|
+
* Redistributions of files must retain the above copyright notice.
|
|
8
|
+
*
|
|
9
|
+
* @copyright Copyright (c) Passbolt SA (https://www.passbolt.com)
|
|
10
|
+
* @license https://opensource.org/licenses/AGPL-3.0 AGPL License
|
|
11
|
+
* @link https://www.passbolt.com Passbolt(tm)
|
|
12
|
+
* @since 5.3.2
|
|
13
|
+
*/
|
|
14
|
+
import {assertString} from "../../utils/assertions";
|
|
15
|
+
import BrowserService from "../browser/browserService";
|
|
16
|
+
import ClipboardProviderService from "./clipboardProviderService";
|
|
17
|
+
|
|
18
|
+
const CLIPBOARD_TEMPORARY_CONTENT_FLUSH_DELAY_IN_SECOND = 30;
|
|
19
|
+
const CLIPBOARD_TEMPORARY_CONTENT_FLUSH_ALARM = "ClipboardTemporaryContentFlush";
|
|
20
|
+
/**
|
|
21
|
+
* The service aims to use the clipboard capability on Edge MV2 where:
|
|
22
|
+
* - navigator.clipboard.writeText cannot be used in Edge MV2 background page due to a focus issue.
|
|
23
|
+
* - offscreen clipboard cannot be used due to offscreen API available only with MV3.
|
|
24
|
+
*/
|
|
25
|
+
export default class CopyToClipboardService {
|
|
26
|
+
/**
|
|
27
|
+
* @constructor
|
|
28
|
+
*/
|
|
29
|
+
constructor() {
|
|
30
|
+
this.clipboard = ClipboardProviderService.getClipboard();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Copies the given data into the clipboard and sets a timer to remove after 30sec.
|
|
35
|
+
* @param {string} data
|
|
36
|
+
* @return {Promise<void>}
|
|
37
|
+
*/
|
|
38
|
+
async copyTemporarily(data) {
|
|
39
|
+
assertString(data);
|
|
40
|
+
|
|
41
|
+
await this.clearAlarm();
|
|
42
|
+
await this.clipboard.writeText(data);
|
|
43
|
+
|
|
44
|
+
await browser.alarms.create(CopyToClipboardService.ALARM_NAME, {
|
|
45
|
+
when: Date.now() + CLIPBOARD_TEMPORARY_CONTENT_FLUSH_DELAY_IN_SECOND * 1000
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Copies the given data into the clipboard and unsets any flush timer.
|
|
51
|
+
* @param {string} data
|
|
52
|
+
* @return {Promise<void>}
|
|
53
|
+
*/
|
|
54
|
+
async copy(data) {
|
|
55
|
+
assertString(data);
|
|
56
|
+
|
|
57
|
+
await this.clearAlarm();
|
|
58
|
+
await this.clipboard.writeText(data);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Flushes the clipboard if the content is the expected one.
|
|
63
|
+
* @returns {Promise<void>}
|
|
64
|
+
*/
|
|
65
|
+
async flushTemporaryContent() {
|
|
66
|
+
await this.clearAlarm();
|
|
67
|
+
await this.clipboard.writeText(this._getContentToFlushClipboard());
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Flushes the clipboard content if there is any temporary content in it.
|
|
72
|
+
* @returns {Promise<void>}
|
|
73
|
+
*/
|
|
74
|
+
async flushTemporaryContentIfAny() {
|
|
75
|
+
const hasTemporaryContent = Boolean(await browser.alarms.get(CopyToClipboardService.ALARM_NAME));
|
|
76
|
+
if (hasTemporaryContent) {
|
|
77
|
+
this.flushTemporaryContent();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Removes the flush alarm if any.
|
|
83
|
+
* @returns {Promise<void>}
|
|
84
|
+
*/
|
|
85
|
+
async clearAlarm() {
|
|
86
|
+
await browser.alarms.clear(CopyToClipboardService.ALARM_NAME);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Returns a string that the browser can use to clean the clipboard.
|
|
91
|
+
* The "empty" string depends on the browser.
|
|
92
|
+
* Chromimum: "" is not accepted but "\x00" does the trick
|
|
93
|
+
* Firefox: "" is working fine but "\x00" is a bit buggy (the character could be pasted)
|
|
94
|
+
* @returns {string}
|
|
95
|
+
* @private
|
|
96
|
+
*/
|
|
97
|
+
_getContentToFlushClipboard() {
|
|
98
|
+
return BrowserService.isFirefox()
|
|
99
|
+
? ""
|
|
100
|
+
: "\x00";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Flush the current stored passphrase when the PassphraseStorageFlush alarm triggers.
|
|
105
|
+
* This is a top-level alarm callback
|
|
106
|
+
* @param {Alarm} alarm
|
|
107
|
+
* @returns {Promise<void>}
|
|
108
|
+
*/
|
|
109
|
+
static async handleClipboardTemporaryContentFlushEvent(alarm) {
|
|
110
|
+
if (alarm.name === CopyToClipboardService.ALARM_NAME) {
|
|
111
|
+
const clipboardService = new CopyToClipboardService();
|
|
112
|
+
await clipboardService.flushTemporaryContent();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Returns the PASSPHRASE_FLUSH_ALARM name
|
|
118
|
+
* @returns {string}
|
|
119
|
+
*/
|
|
120
|
+
static get ALARM_NAME() {
|
|
121
|
+
return CLIPBOARD_TEMPORARY_CONTENT_FLUSH_ALARM;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Passbolt ~ Open source password manager for teams
|
|
3
|
+
* Copyright (c) Passbolt SA (https://www.passbolt.com)
|
|
4
|
+
*
|
|
5
|
+
* Licensed under GNU Affero General Public License version 3 of the or any later version.
|
|
6
|
+
* For full copyright and license information, please see the LICENSE.txt
|
|
7
|
+
* Redistributions of files must retain the above copyright notice.
|
|
8
|
+
*
|
|
9
|
+
* @copyright Copyright (c) Passbolt SA (https://www.passbolt.com)
|
|
10
|
+
* @license https://opensource.org/licenses/AGPL-3.0 AGPL License
|
|
11
|
+
* @link https://www.passbolt.com Passbolt(tm)
|
|
12
|
+
* @since 5.3.2
|
|
13
|
+
*/
|
|
14
|
+
import AccountEntity from "../../model/entity/account/accountEntity";
|
|
15
|
+
import {defaultAccountDto} from "../../model/entity/account/accountEntity.test.data";
|
|
16
|
+
import CopyToClipboardService from "./copyToClipboardService";
|
|
17
|
+
import "../../../../../test/mocks/mockNavigatorClipboard";
|
|
18
|
+
import "../../../../../test/mocks/mockAlarms";
|
|
19
|
+
|
|
20
|
+
beforeEach(async() => {
|
|
21
|
+
jest.clearAllMocks();
|
|
22
|
+
jest.useFakeTimers();
|
|
23
|
+
await browser.alarms.clearAll();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("CopyToClipboardService", () => {
|
|
27
|
+
describe("::copyTemporarily", () => {
|
|
28
|
+
it("should copy the given data into the clipboard and set an alarm", async() => {
|
|
29
|
+
expect.assertions(5);
|
|
30
|
+
|
|
31
|
+
const account = new AccountEntity(defaultAccountDto());
|
|
32
|
+
const service = new CopyToClipboardService(account);
|
|
33
|
+
|
|
34
|
+
//ensures the test is not blocked by an unmocked promise
|
|
35
|
+
jest.spyOn(browser.alarms, "create").mockImplementation(() => {});
|
|
36
|
+
jest.spyOn(browser.alarms, "clear").mockImplementation(() => {});
|
|
37
|
+
|
|
38
|
+
const data = "data";
|
|
39
|
+
await service.copyTemporarily(data);
|
|
40
|
+
|
|
41
|
+
expect(await navigator.clipboard.readText()).toStrictEqual(data);
|
|
42
|
+
expect(browser.alarms.create).toHaveBeenCalledTimes(1);
|
|
43
|
+
expect(browser.alarms.create).toHaveBeenCalledWith(CopyToClipboardService.ALARM_NAME, {when: Date.now() + 30_000});
|
|
44
|
+
expect(browser.alarms.clear).toHaveBeenCalledTimes(1);
|
|
45
|
+
expect(browser.alarms.clear).toHaveBeenCalledWith(CopyToClipboardService.ALARM_NAME);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should throw an error if the data is not a string", async() => {
|
|
49
|
+
expect.assertions(1);
|
|
50
|
+
|
|
51
|
+
const account = new AccountEntity(defaultAccountDto());
|
|
52
|
+
const service = new CopyToClipboardService(account);
|
|
53
|
+
|
|
54
|
+
await expect(() => service.copyTemporarily(42)).rejects.toThrowError();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("::copy", () => {
|
|
59
|
+
it("should copy the given data into the clipboard and unset any flush alarm", async() => {
|
|
60
|
+
expect.assertions(3);
|
|
61
|
+
|
|
62
|
+
const account = new AccountEntity(defaultAccountDto());
|
|
63
|
+
const service = new CopyToClipboardService(account);
|
|
64
|
+
|
|
65
|
+
//ensures the test is not blocked by an unmocked promise
|
|
66
|
+
jest.spyOn(browser.alarms, "clear").mockImplementation(() => {});
|
|
67
|
+
|
|
68
|
+
const data = "data";
|
|
69
|
+
await service.copy(data);
|
|
70
|
+
|
|
71
|
+
expect(await navigator.clipboard.readText()).toStrictEqual(data);
|
|
72
|
+
expect(browser.alarms.clear).toHaveBeenCalledTimes(1);
|
|
73
|
+
expect(browser.alarms.clear).toHaveBeenCalledWith(CopyToClipboardService.ALARM_NAME);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should throw an error if the data is not a string", async() => {
|
|
77
|
+
expect.assertions(1);
|
|
78
|
+
|
|
79
|
+
const account = new AccountEntity(defaultAccountDto());
|
|
80
|
+
const service = new CopyToClipboardService(account);
|
|
81
|
+
|
|
82
|
+
await expect(() => service.copy(42)).rejects.toThrowError();
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe("::flushTemporaryContent", () => {
|
|
87
|
+
it("should flush the data in the clipboard", async() => {
|
|
88
|
+
expect.assertions(1);
|
|
89
|
+
|
|
90
|
+
const account = new AccountEntity(defaultAccountDto());
|
|
91
|
+
const service = new CopyToClipboardService(account);
|
|
92
|
+
|
|
93
|
+
await service.copyTemporarily("data");
|
|
94
|
+
await service.flushTemporaryContent();
|
|
95
|
+
|
|
96
|
+
expect(await navigator.clipboard.readText()).toStrictEqual("\x00");
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe("::flushTemporaryContentIfAny", () => {
|
|
101
|
+
it("should flush the data in the clipboard if an alarm has been set", async() => {
|
|
102
|
+
expect.assertions(3);
|
|
103
|
+
|
|
104
|
+
const account = new AccountEntity(defaultAccountDto());
|
|
105
|
+
const service = new CopyToClipboardService(account);
|
|
106
|
+
|
|
107
|
+
jest.spyOn(browser.alarms, "get").mockImplementation(async() => ({test: 42}));
|
|
108
|
+
jest.spyOn(navigator.clipboard, "writeText").mockImplementation(async() => {});
|
|
109
|
+
|
|
110
|
+
await service.copyTemporarily("data");
|
|
111
|
+
await service.flushTemporaryContentIfAny();
|
|
112
|
+
|
|
113
|
+
expect(await navigator.clipboard.writeText).toHaveBeenCalledTimes(2);
|
|
114
|
+
expect(await navigator.clipboard.writeText).toHaveBeenCalledWith("data");
|
|
115
|
+
expect(await navigator.clipboard.writeText).toHaveBeenCalledWith("\x00");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("should not flush the data in the clipboard if not alarm has been set", async() => {
|
|
119
|
+
expect.assertions(2);
|
|
120
|
+
|
|
121
|
+
const account = new AccountEntity(defaultAccountDto());
|
|
122
|
+
const service = new CopyToClipboardService(account);
|
|
123
|
+
const clipboardContent = "data";
|
|
124
|
+
|
|
125
|
+
jest.spyOn(browser.alarms, "get").mockImplementation(() => null);
|
|
126
|
+
jest.spyOn(navigator.clipboard, "writeText").mockImplementation(async() => {});
|
|
127
|
+
|
|
128
|
+
await service.copy(clipboardContent);
|
|
129
|
+
await service.flushTemporaryContentIfAny();
|
|
130
|
+
|
|
131
|
+
expect(await navigator.clipboard.writeText).toHaveBeenCalledTimes(1);
|
|
132
|
+
expect(await navigator.clipboard.writeText).toHaveBeenCalledWith(clipboardContent);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe("::clearAlarm", () => {
|
|
137
|
+
it("should remove the alarm if any", async() => {
|
|
138
|
+
expect.assertions(1);
|
|
139
|
+
|
|
140
|
+
jest.spyOn(browser.alarms, "clear").mockImplementation(() => {});
|
|
141
|
+
|
|
142
|
+
const account = new AccountEntity(defaultAccountDto());
|
|
143
|
+
const service = new CopyToClipboardService(account);
|
|
144
|
+
|
|
145
|
+
await service.clearAlarm();
|
|
146
|
+
|
|
147
|
+
expect(browser.alarms.clear).toHaveBeenCalledTimes(1);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe("::handleClipboardTemporaryContentFlushEvent", () => {
|
|
152
|
+
it("should call to flush the clipboard if the alarm triggers", async() => {
|
|
153
|
+
expect.assertions(1);
|
|
154
|
+
|
|
155
|
+
jest.spyOn(CopyToClipboardService.prototype, "flushTemporaryContent").mockImplementation(() => {});
|
|
156
|
+
|
|
157
|
+
const alarm = {name: "ClipboardTemporaryContentFlush"};
|
|
158
|
+
await CopyToClipboardService.handleClipboardTemporaryContentFlushEvent(alarm);
|
|
159
|
+
|
|
160
|
+
expect(CopyToClipboardService.prototype.flushTemporaryContent).toHaveBeenCalledTimes(1);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("should do nothing if the alarm is not the right one", async() => {
|
|
164
|
+
expect.assertions(1);
|
|
165
|
+
|
|
166
|
+
jest.spyOn(CopyToClipboardService.prototype, "flushTemporaryContent").mockImplementation(() => {});
|
|
167
|
+
|
|
168
|
+
const alarm = {name: "other-alarm"};
|
|
169
|
+
await CopyToClipboardService.handleClipboardTemporaryContentFlushEvent(alarm);
|
|
170
|
+
|
|
171
|
+
expect(CopyToClipboardService.prototype.flushTemporaryContent).not.toHaveBeenCalled();
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
});
|
package/src/all/background_page/service/resource/findAndUpdateResourcesLocalStorageService.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* @since 4.6.0
|
|
13
13
|
*/
|
|
14
14
|
import ResourceLocalStorage from "../local_storage/resourceLocalStorage";
|
|
15
|
-
import {assertNumber} from "../../utils/assertions";
|
|
15
|
+
import {assertNumber, assertUuid} from "../../utils/assertions";
|
|
16
16
|
import FindResourcesService from "./findResourcesService";
|
|
17
17
|
import ResourcesCollection from "../../model/entity/resource/resourcesCollection";
|
|
18
18
|
import ResourceTypeModel from "../../model/resourceType/resourceTypeModel";
|
|
@@ -113,6 +113,57 @@ class FindAndUpdateResourcesLocalStorage {
|
|
|
113
113
|
await ResourceLocalStorage.addOrReplaceResourcesCollection(resourcesCollection);
|
|
114
114
|
return resourcesCollection;
|
|
115
115
|
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Find and update the local storage with the resources filtered by parent folder id retrieved from the API.
|
|
119
|
+
* @param {string} parentFolderId The parent folder id to filter the resources with.
|
|
120
|
+
* @param {string|null} [passphrase = null] The passphrase to use to decrypt the metadata. Marked as optional as it
|
|
121
|
+
* might be available in the passphrase session storage.
|
|
122
|
+
* @return {Promise<ResourcesCollection>} The resource shared with the group
|
|
123
|
+
* @throw {TypeError} If the parentFolderId is not valid UUID
|
|
124
|
+
*/
|
|
125
|
+
async findAndUpdateAllByParentFolderId(parentFolderId, passphrase = null) {
|
|
126
|
+
assertUuid(parentFolderId);
|
|
127
|
+
const resourceTypes = await this.resourceTypeModel.getOrFindAll();
|
|
128
|
+
|
|
129
|
+
const apiResourcesCollection = await this.findResourcesServices.findAllByParentFolderIdForLocalStorage(parentFolderId);
|
|
130
|
+
apiResourcesCollection.filterByResourceTypes(resourceTypes);
|
|
131
|
+
|
|
132
|
+
const apiResourcesCollectionMapIds = apiResourcesCollection.items.reduce((result, resource) => {
|
|
133
|
+
result[resource.id] = resource;
|
|
134
|
+
return result;
|
|
135
|
+
}, {});
|
|
136
|
+
|
|
137
|
+
const localStorageResourcesCollection = new ResourcesCollection(await ResourceLocalStorage.get(), {validate: false});
|
|
138
|
+
const knownResourcesCollectionInFolderIds = localStorageResourcesCollection.items.reduce((result, resource) => {
|
|
139
|
+
if (resource.folderParentId === parentFolderId) {
|
|
140
|
+
result.push(resource.id);
|
|
141
|
+
}
|
|
142
|
+
return result;
|
|
143
|
+
}, Array(apiResourcesCollection.length));
|
|
144
|
+
|
|
145
|
+
apiResourcesCollection.setDecryptedMetadataFromCollection(localStorageResourcesCollection);
|
|
146
|
+
localStorageResourcesCollection.updateWithCollection(apiResourcesCollection);
|
|
147
|
+
|
|
148
|
+
const movedOrRemovedResourcesIds = knownResourcesCollectionInFolderIds.filter(id => !apiResourcesCollectionMapIds[id]);
|
|
149
|
+
if (movedOrRemovedResourcesIds.length > 0) {
|
|
150
|
+
const updatedResources = await this.findResourcesServices.findAllByIdsForLocalStorage(movedOrRemovedResourcesIds);
|
|
151
|
+
|
|
152
|
+
updatedResources.setDecryptedMetadataFromCollection(localStorageResourcesCollection);
|
|
153
|
+
localStorageResourcesCollection.updateWithCollection(updatedResources);
|
|
154
|
+
|
|
155
|
+
// These resources have been deleted (or permissions revoked) and need to be removed from the local storage.
|
|
156
|
+
const remainingResourcesIds = movedOrRemovedResourcesIds
|
|
157
|
+
.filter(id => !updatedResources.getFirstById(id));
|
|
158
|
+
|
|
159
|
+
localStorageResourcesCollection.removeMany(remainingResourcesIds);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
await this.decryptMetadataService.decryptAllFromForeignModels(localStorageResourcesCollection, passphrase, {ignoreDecryptionError: true, updateSessionKeys: true});
|
|
163
|
+
localStorageResourcesCollection.filterOutMetadataEncrypted();
|
|
164
|
+
|
|
165
|
+
await ResourceLocalStorage.set(localStorageResourcesCollection);
|
|
166
|
+
}
|
|
116
167
|
}
|
|
117
168
|
|
|
118
169
|
export default FindAndUpdateResourcesLocalStorage;
|
package/src/all/background_page/service/resource/findAndUpdateResourcesLocalStorageService.test.js
CHANGED
|
@@ -371,4 +371,104 @@ describe("UpdateResourcesLocalStorage", () => {
|
|
|
371
371
|
expect(expectLocalStorageResult[1]).toEqual(ResourceEntity.transformDtoFromV4toV5(resourceDto2));
|
|
372
372
|
});
|
|
373
373
|
});
|
|
374
|
+
|
|
375
|
+
describe("::findAndUpdateAllByParentFolderId", () => {
|
|
376
|
+
let service;
|
|
377
|
+
|
|
378
|
+
beforeEach(() => {
|
|
379
|
+
service = new FindAndUpdateResourcesLocalStorage(account, apiClientOptions);
|
|
380
|
+
jest.spyOn(ResourceTypeService.prototype, "findAll").mockImplementation(() => resourceTypesCollectionDto());
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it("should extract the id from the resource collection", async() => {
|
|
384
|
+
expect.assertions(2);
|
|
385
|
+
|
|
386
|
+
const parentFolderId = uuidv4();
|
|
387
|
+
|
|
388
|
+
const resourcesDto = multipleResourceDtos();
|
|
389
|
+
jest.spyOn(ResourceService.prototype, "findAll").mockImplementation(() => resourcesDto);
|
|
390
|
+
jest.spyOn(service.findResourcesServices, "findAllByParentFolderIdForLocalStorage");
|
|
391
|
+
|
|
392
|
+
await service.findAndUpdateAllByParentFolderId(parentFolderId);
|
|
393
|
+
|
|
394
|
+
expect(service.findResourcesServices.findAllByParentFolderIdForLocalStorage).toHaveBeenCalledTimes(1);
|
|
395
|
+
expect(service.findResourcesServices.findAllByParentFolderIdForLocalStorage).toHaveBeenCalledWith(parentFolderId);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it("should assert its parameter", async() => {
|
|
399
|
+
expect.assertions(1);
|
|
400
|
+
await expect(() => service.findAndUpdateAllByParentFolderId("test")).rejects.toThrow();
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it("should update the local storage resource collection by removing deleted resources, moving resources and updating resources data", async() => {
|
|
404
|
+
expect.assertions(8);
|
|
405
|
+
|
|
406
|
+
const parentFolderId = uuidv4();
|
|
407
|
+
const otherParentFolderId = uuidv4();
|
|
408
|
+
|
|
409
|
+
/*
|
|
410
|
+
* Resources collection in local storage:
|
|
411
|
+
* Resource0.folderParentId = null;
|
|
412
|
+
* Resource1.folderParentId = parentFolderId; // on API it should be modified only
|
|
413
|
+
* Resource2.folderParentId = parentFolderId; // on API it should be moved
|
|
414
|
+
* Resource3.folderParentId = parentFolderId; // on API it should be removed
|
|
415
|
+
*/
|
|
416
|
+
|
|
417
|
+
const resourcesDto = multipleResourceDtos();
|
|
418
|
+
resourcesDto[1].folder_parent_id = parentFolderId;
|
|
419
|
+
resourcesDto[2].folder_parent_id = parentFolderId;
|
|
420
|
+
resourcesDto[3].folder_parent_id = parentFolderId;
|
|
421
|
+
|
|
422
|
+
delete resourcesDto[0].name;
|
|
423
|
+
delete resourcesDto[1].name;
|
|
424
|
+
delete resourcesDto[2].name;
|
|
425
|
+
delete resourcesDto[3].name;
|
|
426
|
+
|
|
427
|
+
const localStorageResourceCollection = new ResourcesCollection(resourcesDto);
|
|
428
|
+
await ResourceLocalStorage.set(localStorageResourceCollection);
|
|
429
|
+
|
|
430
|
+
// the resources in the folder on the API does not have resource2 anymore but resources1 remains and is changed.
|
|
431
|
+
const apiResourcesDtoInFolder = [
|
|
432
|
+
{...resourcesDto[1]},
|
|
433
|
+
];
|
|
434
|
+
apiResourcesDtoInFolder[0].metadata.name = "Resource1 - UPDATED";
|
|
435
|
+
apiResourcesDtoInFolder[0].modified = (new Date()).toISOString();
|
|
436
|
+
|
|
437
|
+
// resource2 on the API will be return and updated, resource3 will never be sent back as it is deleted
|
|
438
|
+
const allIdsApiResourcesDto = [
|
|
439
|
+
{...resourcesDto[2]},
|
|
440
|
+
];
|
|
441
|
+
allIdsApiResourcesDto[0].folder_parent_id = otherParentFolderId;
|
|
442
|
+
|
|
443
|
+
async function mockedFindAllApi(_, filter) {
|
|
444
|
+
const isParentFolderSearchRequest = Boolean(filter["has-parent"]);
|
|
445
|
+
|
|
446
|
+
return isParentFolderSearchRequest
|
|
447
|
+
? apiResourcesDtoInFolder
|
|
448
|
+
: allIdsApiResourcesDto;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
jest.spyOn(ResourceService.prototype, "findAll").mockImplementation(mockedFindAllApi);
|
|
452
|
+
|
|
453
|
+
await service.findAndUpdateAllByParentFolderId(parentFolderId);
|
|
454
|
+
|
|
455
|
+
const updatedResourceLocalStorage = new ResourcesCollection(await ResourceLocalStorage.get());
|
|
456
|
+
|
|
457
|
+
expect(updatedResourceLocalStorage).toHaveLength(3); //1 resource should be removed compared to the original data
|
|
458
|
+
const resource0 = updatedResourceLocalStorage.getFirstById(resourcesDto[0].id);
|
|
459
|
+
expect(resource0.toDto(ResourceEntity.ALL_CONTAIN_OPTIONS)).toStrictEqual(resourcesDto[0]); //this resource should remain unchanged
|
|
460
|
+
|
|
461
|
+
const updatedResource1 = updatedResourceLocalStorage.getFirstById(resourcesDto[1].id); // this resource should have been updated but not moved
|
|
462
|
+
expect(updatedResource1.metadata.name).toStrictEqual("Resource1 - UPDATED");
|
|
463
|
+
expect(updatedResource1.modified).toStrictEqual(apiResourcesDtoInFolder[0].modified);
|
|
464
|
+
expect(updatedResource1.folderParentId).toStrictEqual(parentFolderId);
|
|
465
|
+
|
|
466
|
+
const updatedResource2 = updatedResourceLocalStorage.getFirstById(resourcesDto[2].id); // this resource should not have changed per say but only moved
|
|
467
|
+
expect(updatedResource2.modified).toStrictEqual(resourcesDto[2].modified);
|
|
468
|
+
expect(updatedResource2.folderParentId).toStrictEqual(otherParentFolderId);
|
|
469
|
+
|
|
470
|
+
const updatedResource3 = updatedResourceLocalStorage.getFirstById(resourcesDto[3].id); // this resource should have been removed
|
|
471
|
+
expect(updatedResource3).toBeUndefined();
|
|
472
|
+
});
|
|
473
|
+
});
|
|
374
474
|
});
|
|
@@ -46,18 +46,9 @@ export default class FindResourcesService {
|
|
|
46
46
|
* @returns {Promise<ResourcesCollection>}
|
|
47
47
|
* @private
|
|
48
48
|
*/
|
|
49
|
-
async findAll(contains, filters, ignoreInvalidEntity) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const supportedFilter = ResourceService.getSupportedFiltersOptions();
|
|
53
|
-
|
|
54
|
-
if (contains && !Object.keys(contains).every(option => supportedOptions.includes(option))) {
|
|
55
|
-
throw new Error("Unsupported contains parameter used, please check supported contains");
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (filters && !Object.keys(filters).every(filter => supportedFilter.includes(filter))) {
|
|
59
|
-
throw new Error("Unsupported filter parameter used, please check supported filters");
|
|
60
|
-
}
|
|
49
|
+
async findAll(contains, filters, ignoreInvalidEntity = false) {
|
|
50
|
+
this.assertContains(contains);
|
|
51
|
+
this.assertFilters(filters);
|
|
61
52
|
|
|
62
53
|
const resourcesDto = await this.resourceService.findAll(contains, filters);
|
|
63
54
|
return new ResourcesCollection(resourcesDto, {clone: false, ignoreInvalidEntity: ignoreInvalidEntity});
|
|
@@ -69,7 +60,7 @@ export default class FindResourcesService {
|
|
|
69
60
|
* @param {Array<string>} resourcesIds
|
|
70
61
|
* @returns {Promise<ResourcesCollection>}
|
|
71
62
|
*/
|
|
72
|
-
async findAllByIds(resourcesIds, contains = {}) {
|
|
63
|
+
async findAllByIds(resourcesIds, contains = {}, ignoreInvalidEntity = false) {
|
|
73
64
|
assertArrayUUID(resourcesIds);
|
|
74
65
|
|
|
75
66
|
// We split the requests in chunks in order to avoid any too long url error.
|
|
@@ -78,7 +69,7 @@ export default class FindResourcesService {
|
|
|
78
69
|
const filter = {
|
|
79
70
|
"has-id": resourceIds
|
|
80
71
|
};
|
|
81
|
-
return async() => await this.findAll(contains, filter);
|
|
72
|
+
return async() => await this.findAll(contains, filter, ignoreInvalidEntity);
|
|
82
73
|
});
|
|
83
74
|
|
|
84
75
|
// @todo Later (tm). The Collection should provide this capability, ensuring that validation build rules are executed and performance is guaranteed.
|
|
@@ -196,12 +187,8 @@ export default class FindResourcesService {
|
|
|
196
187
|
*/
|
|
197
188
|
async findOneById(resourceId, contains = {}) {
|
|
198
189
|
assertUuid(resourceId);
|
|
199
|
-
|
|
200
|
-
const supportedOptions = ResourceService.getSupportedContainOptions();
|
|
190
|
+
this.assertContains(contains);
|
|
201
191
|
|
|
202
|
-
if (contains && !Object.keys(contains).every(option => supportedOptions.includes(option))) {
|
|
203
|
-
throw new Error("Unsupported contains parameter used, please check supported contains");
|
|
204
|
-
}
|
|
205
192
|
const resourcesDto = await this.resourceService.get(resourceId, contains);
|
|
206
193
|
|
|
207
194
|
return new ResourceEntity(resourcesDto);
|
|
@@ -224,4 +211,51 @@ export default class FindResourcesService {
|
|
|
224
211
|
const resource = this.findOneById(resourceId, contains);
|
|
225
212
|
return resource;
|
|
226
213
|
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Find all the resources matching the given ids for the local storage.
|
|
217
|
+
* All entities that cannot be validated will be ignored and excluded from the resulting collection.
|
|
218
|
+
* @param {Array<uuid>} resourceIds
|
|
219
|
+
* @returns {Promise<ResourcesCollection>}
|
|
220
|
+
*/
|
|
221
|
+
async findAllByIdsForLocalStorage(resourceIds) {
|
|
222
|
+
assertArrayUUID(resourceIds);
|
|
223
|
+
return await this.findAllByIds(resourceIds, ResourceLocalStorage.DEFAULT_CONTAIN, true);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Find all the resources having the given parentFolderId as direct ancestor for the local storage
|
|
228
|
+
* All entities that cannot be validated will be ignored and excluded from the resulting collection.
|
|
229
|
+
* @param {uuid} parentFolderId
|
|
230
|
+
* @returns {Promise<ResourcesCollection>}
|
|
231
|
+
*/
|
|
232
|
+
async findAllByParentFolderIdForLocalStorage(parentFolderId) {
|
|
233
|
+
assertUuid(parentFolderId);
|
|
234
|
+
const filters = {"has-parent": parentFolderId};
|
|
235
|
+
return await this.findAll(ResourceLocalStorage.DEFAULT_CONTAIN, filters, true);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Assert the contains to ensure they match the supported ones.
|
|
240
|
+
* @param {object} contains
|
|
241
|
+
* @private
|
|
242
|
+
*/
|
|
243
|
+
assertContains(contains) {
|
|
244
|
+
const supportedOptions = ResourceService.getSupportedContainOptions();
|
|
245
|
+
if (contains && !Object.keys(contains).every(option => supportedOptions.includes(option))) {
|
|
246
|
+
throw new Error("Unsupported contains parameter used, please check supported contains");
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Assert the filters to ensure they match the supported ones.
|
|
252
|
+
* @param {object} filters
|
|
253
|
+
* @private
|
|
254
|
+
*/
|
|
255
|
+
assertFilters(filters) {
|
|
256
|
+
const supportedFilter = ResourceService.getSupportedFiltersOptions();
|
|
257
|
+
if (filters && !Object.keys(filters).every(filter => supportedFilter.includes(filter))) {
|
|
258
|
+
throw new Error("Unsupported filter parameter used, please check supported filters");
|
|
259
|
+
}
|
|
260
|
+
}
|
|
227
261
|
}
|