passbolt-browser-extension 5.3.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.
Files changed (84) hide show
  1. package/CHANGELOG.md +30 -1
  2. package/Gruntfile.js +1 -1
  3. package/RELEASE_NOTES.md +29 -48
  4. package/package.json +5 -5
  5. package/src/all/background_page/controller/clipboard/cancelClipboardContentFlushController.js +51 -0
  6. package/src/all/background_page/controller/clipboard/cancelClipboardContentFlushController.test.js +46 -0
  7. package/src/all/background_page/controller/clipboard/copyTemporarilyToClipboardController.js +53 -0
  8. package/src/all/background_page/controller/clipboard/copyTemporarilyToClipboardController.test.js +47 -0
  9. package/src/all/background_page/controller/clipboard/{clipboardController.js → copyToClipboardController.js} +8 -8
  10. package/src/all/background_page/controller/clipboard/copyToClipboardController.test.js +47 -0
  11. package/src/all/background_page/controller/extension/getExtensionVersionController.test.js +1 -1
  12. package/src/all/background_page/controller/metadata/getOrFindMetadataKeysSettingsController.js +53 -0
  13. package/src/all/background_page/controller/metadata/getOrFindMetadataKeysSettingsController.test.js +106 -0
  14. package/src/all/background_page/controller/user/deleteDryRunUserController.js +73 -0
  15. package/src/all/background_page/controller/user/deleteDryRunUserController.test.js +129 -0
  16. package/src/all/background_page/controller/user/deleteUserController.js +76 -0
  17. package/src/all/background_page/controller/user/deleteUserController.test.js +141 -0
  18. package/src/all/background_page/event/actionLogEvents.js +5 -12
  19. package/src/all/background_page/event/appEvents.js +33 -0
  20. package/src/all/background_page/event/findAllForActionLogController.js +58 -0
  21. package/src/all/background_page/event/findAllForActionLogController.test.js +43 -0
  22. package/src/all/background_page/event/quickAccessEvents.js +32 -0
  23. package/src/all/background_page/event/userEvents.js +7 -20
  24. package/src/all/background_page/event/webIntegrationEvents.js +11 -0
  25. package/src/all/background_page/model/actionLog/{actionLogModel.js → findActionLogService.js} +25 -5
  26. package/src/all/background_page/model/actionLog/findActionLogService.test.js +61 -0
  27. package/src/all/background_page/model/comment/commentModel.js +5 -5
  28. package/src/all/background_page/model/entity/actionLog/actionLogsCollection.test.data.js +18 -0
  29. package/src/all/background_page/model/entity/actionLog/defaultActionLogEntity.test.data.js +34 -0
  30. package/src/all/background_page/model/import/resources/resourcesKdbxImportParser.test.js +0 -1
  31. package/src/all/background_page/model/user/userModel.js +0 -60
  32. package/src/all/background_page/pagemod/appPagemod.js +0 -2
  33. package/src/all/background_page/pagemod/appPagemod.test.js +2 -6
  34. package/src/all/background_page/service/alarm/globalAlarmService.js +2 -0
  35. package/src/all/background_page/service/api/actionLog/{actionLogService.js → actionLogApiService.js} +5 -4
  36. package/src/all/background_page/service/api/actionLog/actionLogApiService.test.js +55 -0
  37. package/src/all/background_page/service/api/comment/{commentService.js → commentApiService.js} +6 -7
  38. package/src/all/background_page/service/api/comment/commentApiService.test.js +122 -0
  39. package/src/all/background_page/service/auth/postLogoutService.js +2 -0
  40. package/src/all/background_page/service/auth/postLogoutService.test.js +4 -1
  41. package/src/all/background_page/service/browser/browserService.js +22 -0
  42. package/src/all/background_page/service/clipboard/clipboardProviderService.js +40 -0
  43. package/src/all/background_page/service/clipboard/clipboardProviderService.test.js +61 -0
  44. package/src/all/background_page/service/clipboard/copyToClipboardService.js +123 -0
  45. package/src/all/background_page/service/clipboard/copyToClipboardService.test.js +174 -0
  46. package/src/all/background_page/service/user/deleteUserService.js +97 -0
  47. package/src/all/background_page/service/user/deleteUserService.test.js +178 -0
  48. package/src/chrome/manifest.json +1 -1
  49. package/src/chrome/polyfill/clipboard/edgeBackgroundPageClipboardService.js +31 -0
  50. package/src/chrome/polyfill/clipboard/edgeBackgroundPageClipboardService.test.js +51 -0
  51. package/src/chrome-mv3/index.js +3 -3
  52. package/src/chrome-mv3/manifest.json +1 -1
  53. package/src/chrome-mv3/offscreens/{fetch.html → offscreen.html} +1 -1
  54. package/src/chrome-mv3/offscreens/{fetch.js → offscreen.js} +2 -2
  55. package/src/chrome-mv3/offscreens/service/clipboard/writeClipobardOffscreenService.js +54 -0
  56. package/src/chrome-mv3/offscreens/service/clipboard/writeClipobardOffscreenService.test.js +56 -0
  57. package/src/chrome-mv3/offscreens/service/network/fetchOffscreenService.js +36 -44
  58. package/src/chrome-mv3/offscreens/service/network/fetchOffscreenService.test.data.js +0 -1
  59. package/src/chrome-mv3/offscreens/service/network/fetchOffscreenService.test.js +90 -120
  60. package/src/chrome-mv3/offscreens/service/offscreen/handleOffscreenRequestService.js +85 -0
  61. package/src/chrome-mv3/offscreens/service/offscreen/handleOffscreenRequestService.test.js +99 -0
  62. package/src/chrome-mv3/polyfill/clipboardOffscreenPolyfill.js +19 -0
  63. package/src/chrome-mv3/serviceWorker/service/clipboard/requestClipboardOffscreenService.js +51 -0
  64. package/src/chrome-mv3/serviceWorker/service/clipboard/requestClipboardOffscreenService.test.js +70 -0
  65. package/src/chrome-mv3/serviceWorker/service/clipboard/responseClipboardOffscreenService.js +25 -0
  66. package/src/chrome-mv3/serviceWorker/service/clipboard/responseClipboardOffscreenService.test.data.js +21 -0
  67. package/src/chrome-mv3/serviceWorker/service/clipboard/responseClipboardOffscreenService.test.js +33 -0
  68. package/src/chrome-mv3/serviceWorker/service/network/requestFetchOffscreenService.js +25 -50
  69. package/src/chrome-mv3/serviceWorker/service/network/requestFetchOffscreenService.test.js +16 -39
  70. package/src/chrome-mv3/serviceWorker/service/network/responseFetchOffscreenService.js +14 -45
  71. package/src/chrome-mv3/serviceWorker/service/network/responseFetchOffscreenService.test.js +5 -37
  72. package/src/chrome-mv3/serviceWorker/service/offscreen/createOffscreenDocumentService.js +43 -0
  73. package/src/chrome-mv3/serviceWorker/service/offscreen/createOffscreenDocumentService.test.js +48 -0
  74. package/src/chrome-mv3/serviceWorker/service/offscreen/handleOffscreenResponseService.js +119 -0
  75. package/src/chrome-mv3/serviceWorker/service/offscreen/handleOffscreenResponseService.test.js +159 -0
  76. package/src/firefox/manifest.json +1 -1
  77. package/src/safari/manifest.json +1 -1
  78. package/test/jest.setup.js +4 -0
  79. package/test/mocks/mockNavigatorClipboard.js +40 -0
  80. package/test/mocks/mockWebExtensionPolyfill.js +2 -1
  81. package/{webpack-offscreens.fetch.config.js → webpack-offscreens.config.js} +1 -1
  82. package/webpack.service-worker.config.js +1 -0
  83. package/src/all/background_page/controller/clipboard/clipboardController.test.js +0 -68
  84. package/src/all/background_page/event/clipboardEvents.js +0 -28
@@ -0,0 +1,97 @@
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.4.0
13
+ */
14
+ import UserService from "../api/user/userService";
15
+ import {assertType, assertUuid} from "../../utils/assertions";
16
+ import DeleteDryRunError from "../../error/deleteDryRunError";
17
+ import PassboltApiFetchError from "passbolt-styleguide/src/shared/lib/Error/PassboltApiFetchError";
18
+ import UserDeleteTransferEntity from "../../model/entity/user/transfer/userDeleteTransferEntity";
19
+ import UserLocalStorage from "../local_storage/userLocalStorage";
20
+
21
+ /**
22
+ * The service aims to delete user from the API.
23
+ */
24
+ export default class DeleteUserService {
25
+ /**
26
+ *
27
+ * @param {AccountEntity} account The user account
28
+ * @param {ApiClientOptions} apiClientOptions The api client options
29
+ */
30
+ constructor(account, apiClientOptions) {
31
+ this.account = account;
32
+ this.userServiceApi = new UserService(apiClientOptions);
33
+ }
34
+
35
+ /**
36
+ * Delete dry run a user.
37
+ * Check if a user can be deleted.
38
+ *
39
+ * A user can not be deleted if:
40
+ * - they are the only owner of a shared resource
41
+ * - they are the only group manager of a group that owns a shared resource
42
+ * In such case ownership transfer is required.
43
+ *
44
+ * @param {string} userId The user id.
45
+ * @returns {Promise<void>}
46
+ * @throws {DeleteDryRunError} if the data should be transferred to someone.
47
+ * @throws {Error} if the data returned by the API is not a PassboltApiFetchError with error code 400 and groups, resources or folders to transfer.
48
+ */
49
+ async deleteDryRun(userId) {
50
+ assertUuid(userId, "The parameter \"userId\" should be a UUID");
51
+ try {
52
+ await this.userServiceApi.delete(userId, {}, true);
53
+ } catch (error) {
54
+ await this.handleError(error);
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Delete a user and transfer ownership if needed.
60
+ * @param {string} userId The user id.
61
+ * @param {UserDeleteTransferEntity} [transfer] optional ownership transfer information if needed.
62
+ * @returns {Promise<void>}
63
+ * @throws {DeleteDryRunError} if some permissions must be transferred.
64
+ * @throws {Error} if the data returned by the API is not a PassboltApiFetchError with error code 400 and groups, resources or folders to transfer.
65
+ */
66
+ async delete(userId, transfer) {
67
+ assertUuid(userId, "The parameter \"userId\" should be a UUID");
68
+ if (transfer !== null) {
69
+ assertType(transfer, UserDeleteTransferEntity, 'The `transfer` parameter should be a UserDeleteTransferEntity.');
70
+ }
71
+ try {
72
+ const deleteData = transfer ? transfer.toDto() : {};
73
+ await this.userServiceApi.delete(userId, deleteData);
74
+ } catch (error) {
75
+ await this.handleError(error);
76
+ }
77
+ // Update local storage
78
+ await UserLocalStorage.delete(userId);
79
+ }
80
+
81
+ /**
82
+ * @private
83
+ * @param {object} error The error
84
+ * @throws {DeleteDryRunError} if some permissions must be transferred.
85
+ * @throws {Error} if the data returned by the API is not a PassboltApiFetchError with error code 400 and groups, resources or folders to transfer.
86
+ */
87
+ async handleError(error) {
88
+ if (error instanceof PassboltApiFetchError && error.data.code === 400 && error.data.body.errors) {
89
+ /*
90
+ * recast generic 400 error into a delete dry run error
91
+ * allowing validation of the returned entities and reuse down the line to transfer permissions
92
+ */
93
+ throw new DeleteDryRunError(error.message, error.data.body.errors);
94
+ }
95
+ throw error;
96
+ }
97
+ }
@@ -0,0 +1,178 @@
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.4.0
13
+ */
14
+
15
+ import AccountEntity from "../../model/entity/account/accountEntity";
16
+ import {defaultAccountDto} from "../../model/entity/account/accountEntity.test.data";
17
+ import {defaultApiClientOptions} from "passbolt-styleguide/src/shared/lib/apiClient/apiClientOptions.test.data";
18
+ import DeleteUserService from "./deleteUserService";
19
+ import {v4 as uuidv4} from "uuid";
20
+ import DeleteDryRunError from "../../error/deleteDryRunError";
21
+ import PassboltApiFetchError from "passbolt-styleguide/src/shared/lib/Error/PassboltApiFetchError";
22
+ import UserDeleteTransferEntity from "../../model/entity/user/transfer/userDeleteTransferEntity";
23
+ import {defaultUserDeleteTransferDto} from "passbolt-styleguide/src/shared/models/entity/user/userDeleteTransferEntity.test.data";
24
+ import {defaultResourceDto} from "passbolt-styleguide/src/shared/models/entity/resource/resourceEntity.test.data";
25
+ import UserLocalStorage from "../local_storage/userLocalStorage";
26
+
27
+ beforeEach(() => {
28
+ jest.clearAllMocks();
29
+ });
30
+
31
+ describe("DeleteUserService", () => {
32
+ let deleteUserService, apiClientOptions;
33
+ const account = new AccountEntity(defaultAccountDto());
34
+
35
+ beforeEach(async() => {
36
+ apiClientOptions = defaultApiClientOptions();
37
+ deleteUserService = new DeleteUserService(account, apiClientOptions);
38
+ });
39
+
40
+ describe("::deleteDryRun", () => {
41
+ it("Do not need to transfer ownership.", async() => {
42
+ expect.assertions(1);
43
+
44
+ const usersId = uuidv4();
45
+ jest.spyOn(deleteUserService.userServiceApi, "delete").mockImplementationOnce(() => {});
46
+
47
+ await deleteUserService.deleteDryRun(usersId);
48
+
49
+ expect(deleteUserService.userServiceApi.delete).toHaveBeenCalledWith(usersId, {}, true);
50
+ });
51
+
52
+ it("Need to transfer ownership.", async() => {
53
+ expect.assertions(1);
54
+
55
+ const usersId = uuidv4();
56
+ const resourceDto = defaultResourceDto();
57
+ const error = {
58
+ code: 400,
59
+ body: {
60
+ errors: {
61
+ resources: {
62
+ sole_owner: [resourceDto]
63
+ }
64
+ }
65
+ }
66
+ };
67
+ jest.spyOn(deleteUserService.userServiceApi, "delete").mockImplementationOnce(() => { throw new PassboltApiFetchError("Error", error); });
68
+
69
+ try {
70
+ await deleteUserService.deleteDryRun(usersId);
71
+ } catch (error) {
72
+ expect(error).toBeInstanceOf(DeleteDryRunError);
73
+ }
74
+ });
75
+
76
+ it("throw any error not handled.", async() => {
77
+ expect.assertions(1);
78
+
79
+ const usersId = uuidv4();
80
+ const error = {
81
+ code: 404,
82
+ };
83
+ jest.spyOn(deleteUserService.userServiceApi, "delete").mockImplementationOnce(() => { throw new PassboltApiFetchError("Error", error); });
84
+
85
+ try {
86
+ await deleteUserService.deleteDryRun(usersId);
87
+ } catch (error) {
88
+ expect(error).toBeInstanceOf(PassboltApiFetchError);
89
+ }
90
+ });
91
+
92
+ it("throws if user id is not an uuid.", async() => {
93
+ expect.assertions(1);
94
+
95
+ const promise = deleteUserService.deleteDryRun({});
96
+
97
+ expect(promise).rejects.toThrow(Error("The parameter \"userId\" should be a UUID"));
98
+ });
99
+ });
100
+
101
+ describe("::delete", () => {
102
+ it("delete a user with no transfer.", async() => {
103
+ expect.assertions(2);
104
+
105
+ const usersId = uuidv4();
106
+ jest.spyOn(deleteUserService.userServiceApi, "delete").mockImplementationOnce(() => {});
107
+ jest.spyOn(UserLocalStorage, "delete").mockImplementationOnce(() => {});
108
+
109
+ await deleteUserService.delete(usersId, null);
110
+
111
+ expect(deleteUserService.userServiceApi.delete).toHaveBeenCalledWith(usersId, {});
112
+ expect(UserLocalStorage.delete).toHaveBeenCalledWith(usersId);
113
+ });
114
+
115
+ it("delete a user with transfer.", async() => {
116
+ expect.assertions(2);
117
+
118
+ const usersId = uuidv4();
119
+ const dto = defaultUserDeleteTransferDto();
120
+ const userDeleteTransfer = new UserDeleteTransferEntity(dto);
121
+ jest.spyOn(deleteUserService.userServiceApi, "delete").mockImplementationOnce(() => {});
122
+ jest.spyOn(UserLocalStorage, "delete").mockImplementationOnce(() => {});
123
+
124
+ await deleteUserService.delete(usersId, userDeleteTransfer);
125
+
126
+ expect(deleteUserService.userServiceApi.delete).toHaveBeenCalledWith(usersId, dto);
127
+ expect(UserLocalStorage.delete).toHaveBeenCalledWith(usersId);
128
+ });
129
+
130
+ it("delete a user with an error and need to transfer ownership.", async() => {
131
+ expect.assertions(1);
132
+
133
+ const usersId = uuidv4();
134
+ const resourceDto = defaultResourceDto();
135
+ const error = {
136
+ code: 400,
137
+ body: {
138
+ errors: {
139
+ resources: {
140
+ sole_owner: [resourceDto]
141
+ }
142
+ }
143
+ }
144
+ };
145
+ jest.spyOn(deleteUserService.userServiceApi, "delete").mockImplementationOnce(() => { throw new PassboltApiFetchError("Error", error); });
146
+
147
+ try {
148
+ await deleteUserService.delete(usersId, null);
149
+ } catch (error) {
150
+ expect(error).toBeInstanceOf(DeleteDryRunError);
151
+ }
152
+ });
153
+
154
+ it("delete a user with an unexpected error.", async() => {
155
+ expect.assertions(1);
156
+
157
+ const usersId = uuidv4();
158
+ const error = {
159
+ code: 404,
160
+ };
161
+ jest.spyOn(deleteUserService.userServiceApi, "delete").mockImplementationOnce(() => { throw new PassboltApiFetchError("Error", error); });
162
+
163
+ try {
164
+ await deleteUserService.delete(usersId, null);
165
+ } catch (error) {
166
+ expect(error).toBeInstanceOf(PassboltApiFetchError);
167
+ }
168
+ });
169
+
170
+ it("throws if user id is not an uuid.", async() => {
171
+ expect.assertions(1);
172
+
173
+ const promise = deleteUserService.delete({});
174
+
175
+ expect(promise).rejects.toThrow(Error("The parameter \"userId\" should be a UUID"));
176
+ });
177
+ });
178
+ });
@@ -2,7 +2,7 @@
2
2
  "manifest_version": 2,
3
3
  "name": "__MSG_appName__",
4
4
  "short_name": "passbolt",
5
- "version": "5.3.0",
5
+ "version": "5.3.2",
6
6
  "description": "__MSG_appDescription__",
7
7
  "default_locale": "en",
8
8
  "externally_connectable": {},
@@ -0,0 +1,31 @@
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
+
15
+ /**
16
+ * The service aims to create metadata key.
17
+ */
18
+ export default class EdgeBackgroundPageClipboardService {
19
+ /**
20
+ * @inheritDoc navigator.clipboard.writeText
21
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/writeText
22
+ */
23
+ static async writeText(data) {
24
+ const textarea = document.createElement("textarea");
25
+ document.body.appendChild(textarea);
26
+ textarea.value = data;
27
+ textarea.select();
28
+ document.execCommand("cut");
29
+ document.body.removeChild(textarea);
30
+ }
31
+ }
@@ -0,0 +1,51 @@
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 EdgeBackgroundPageClipboardService from "./edgeBackgroundPageClipboardService";
15
+
16
+ describe("EdgeBackgroundPageClipboardService", () => {
17
+ describe("::writeText", () => {
18
+ it("should write the given data to the clipboard", async() => {
19
+ expect.assertions(8);
20
+
21
+ let copiedValue = "";
22
+ const fakeElement = {
23
+ value: "",
24
+ select: jest.fn().mockImplementation(() => { copiedValue = fakeElement.value; }),
25
+ };
26
+ // Faking document for this specific context
27
+ global.document = {
28
+ createElement: () => fakeElement,
29
+ body: {
30
+ appendChild: jest.fn(),
31
+ removeChild: jest.fn(),
32
+ },
33
+ execCommand: jest.fn()
34
+ };
35
+
36
+ document.execCommand.mockImplementation(() => {});
37
+
38
+ const dataToWrite = "text-to-copy";
39
+ await EdgeBackgroundPageClipboardService.writeText(dataToWrite);
40
+
41
+ expect(copiedValue).toStrictEqual(dataToWrite);
42
+ expect(fakeElement.select).toHaveBeenCalledTimes(1);
43
+ expect(document.body.appendChild).toHaveBeenCalledTimes(1);
44
+ expect(document.body.appendChild).toHaveBeenCalledWith(fakeElement);
45
+ expect(document.body.removeChild).toHaveBeenCalledTimes(1);
46
+ expect(document.body.removeChild).toHaveBeenCalledWith(fakeElement);
47
+ expect(document.execCommand).toHaveBeenCalledTimes(1);
48
+ expect(document.execCommand).toHaveBeenCalledWith("cut");
49
+ });
50
+ });
51
+ });
@@ -18,9 +18,9 @@ import TabService from "../all/background_page/service/tab/tabService";
18
18
  import OnExtensionUpdateAvailableService
19
19
  from "../all/background_page/service/extension/onExtensionUpdateAvailableService";
20
20
  import GlobalAlarmService from "../all/background_page/service/alarm/globalAlarmService";
21
- import ResponseFetchOffscreenService from "./serviceWorker/service/network/responseFetchOffscreenService";
22
21
  import OnStartUpService from "../all/background_page/service/extension/onStartUpService";
23
22
  import ToolbarService from "../all/background_page/service/toolbar/toolbarService";
23
+ import HandleOffscreenResponseService from "./serviceWorker/service/offscreen/handleOffscreenResponseService";
24
24
 
25
25
  /**
26
26
  * Load all system requirement
@@ -68,9 +68,9 @@ browser.alarms.onAlarm.removeListener(GlobalAlarmService.exec);
68
68
  browser.alarms.onAlarm.addListener(GlobalAlarmService.exec);
69
69
 
70
70
  /**
71
- * Handle offscreen fetch responses.
71
+ * Handle offscreen responses.
72
72
  */
73
- chrome.runtime.onMessage.addListener(ResponseFetchOffscreenService.handleFetchResponse);
73
+ chrome.runtime.onMessage.addListener(HandleOffscreenResponseService.handleOffscreenResponse);
74
74
 
75
75
  /**
76
76
  * Handle suggested resources on toolbar icon
@@ -2,7 +2,7 @@
2
2
  "manifest_version": 3,
3
3
  "name": "__MSG_appName__",
4
4
  "short_name": "passbolt",
5
- "version": "5.3.0",
5
+ "version": "5.3.2",
6
6
  "description": "__MSG_appDescription__",
7
7
  "default_locale": "en",
8
8
  "minimum_chrome_version": "116",
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="utf-8"/>
5
5
  <script src="vendors.js"></script>
6
- <script src="fetch.js"></script>
6
+ <script src="offscreen.js"></script>
7
7
  </head>
8
8
  <body spellcheck="false">
9
9
  </body>
@@ -12,6 +12,6 @@
12
12
  * @since 4.7.0
13
13
  */
14
14
 
15
- import FetchOffscreenService from "./service/network/fetchOffscreenService";
15
+ import HandleOffscreenRequestService from "./service/offscreen/handleOffscreenRequestService";
16
16
 
17
- chrome.runtime.onMessage.addListener(FetchOffscreenService.handleFetchRequest);
17
+ chrome.runtime.onMessage.addListener(HandleOffscreenRequestService.handleOffscreenRequest);
@@ -0,0 +1,54 @@
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
+ export const SEND_MESSAGE_TARGET_CLIPBOARD_WRITE_OFFSCREEN = "clipboard-write-offscreen";
15
+ export const SEND_MESSAGE_TARGET_CLIPBOARD_WRITE_OFFSCREEN_RESPONSE_HANDLER = "service-worker-clipboard-write-text-offscreen-response-handler";
16
+
17
+ export default class WriteClipobardOffscreenService {
18
+ /**
19
+ * Handle clipboard request.
20
+ * @param {{clipboardContent: string}} message arguments to pass to the clipboard.writeText.
21
+ * @returns {Promise<object>}
22
+ */
23
+ static async handleClipboardRequest({clipboardContent}) {
24
+ await WriteClipobardOffscreenService._handleClipboardWriteTextRequest(clipboardContent);
25
+ return WriteClipobardOffscreenService._endClipboardWrite();
26
+ }
27
+
28
+ /**
29
+ * Handles and emulates a clipboard::writeText(data)
30
+ * @param {string} clipboardContent
31
+ * @returns {Promise<void>}
32
+ * @private
33
+ */
34
+ static async _handleClipboardWriteTextRequest(clipboardContent) {
35
+ const textarea = document.createElement("textarea");
36
+ document.body.appendChild(textarea);
37
+ textarea.value = clipboardContent;
38
+ textarea.select();
39
+ document.execCommand("cut");
40
+ document.body.removeChild(textarea);
41
+ }
42
+
43
+ /**
44
+ * Return the clipboard write success data to send back to the requester.
45
+ * @returns {object}
46
+ * @private
47
+ */
48
+ static _endClipboardWrite() {
49
+ return {
50
+ data: null,
51
+ target: SEND_MESSAGE_TARGET_CLIPBOARD_WRITE_OFFSCREEN_RESPONSE_HANDLER,
52
+ };
53
+ }
54
+ }
@@ -0,0 +1,56 @@
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 WriteClipobardOffscreenService from "./writeClipobardOffscreenService";
15
+
16
+ export const SEND_MESSAGE_TARGET_CLIPBOARD_WRITE_OFFSCREEN = "clipboard-write-offscreen";
17
+ export const SEND_MESSAGE_TARGET_CLIPBOARD_WRITE_OFFSCREEN_RESPONSE_HANDLER = "service-worker-clipboard-write-text-offscreen-response-handler";
18
+
19
+ describe("WriteClipobardOffscreenService", () => {
20
+ describe("::handleClipboardRequest", () => {
21
+ it("should run the copy to clipboard and return a message tailored for the requester", async() => {
22
+ expect.assertions(9);
23
+
24
+ let copiedValue = "";
25
+ const fakeElement = {
26
+ value: "",
27
+ select: jest.fn().mockImplementation(() => { copiedValue = fakeElement.value; }),
28
+ };
29
+ global.document = {
30
+ createElement: () => fakeElement,
31
+ body: {
32
+ appendChild: jest.fn(),
33
+ removeChild: jest.fn(),
34
+ },
35
+ execCommand: jest.fn()
36
+ };
37
+
38
+ const clipboardContent = "test";
39
+ const result = await WriteClipobardOffscreenService.handleClipboardRequest({clipboardContent});
40
+ const expectedResult = {
41
+ target: "service-worker-clipboard-write-text-offscreen-response-handler",
42
+ data: null,
43
+ };
44
+
45
+ expect(copiedValue).toStrictEqual(clipboardContent);
46
+ expect(fakeElement.select).toHaveBeenCalledTimes(1);
47
+ expect(document.body.appendChild).toHaveBeenCalledTimes(1);
48
+ expect(document.body.appendChild).toHaveBeenCalledWith(fakeElement);
49
+ expect(document.body.removeChild).toHaveBeenCalledTimes(1);
50
+ expect(document.body.removeChild).toHaveBeenCalledWith(fakeElement);
51
+ expect(document.execCommand).toHaveBeenCalledTimes(1);
52
+ expect(document.execCommand).toHaveBeenCalledWith("cut");
53
+ expect(result).toStrictEqual(expectedResult);
54
+ });
55
+ });
56
+ });
@@ -11,8 +11,6 @@
11
11
  * @link https://www.passbolt.com Passbolt(tm)
12
12
  * @since 4.7.0
13
13
  */
14
-
15
- import Validator from "validator";
16
14
  import FormDataUtils from "../../../../all/background_page/utils/format/formDataUtils";
17
15
 
18
16
  export const SEND_MESSAGE_TARGET_FETCH_OFFSCREEN = "fetch-offscreen";
@@ -43,94 +41,88 @@ export default class FetchOffscreenService {
43
41
 
44
42
  /**
45
43
  * Handle fetch request.
46
- * @param {object} message Browser runtime.onMessage listener message.
47
- * @returns {Promise<void>}
44
+ * @param {string} id the request id.
45
+ * @param {{resource: string, options: object}} data the fetch parameters
46
+ * @returns {Promise<object>}
48
47
  */
49
- static async handleFetchRequest(message) {
50
- // Return early if this message isn't meant for the offscreen document.
51
- if (message.target !== SEND_MESSAGE_TARGET_FETCH_OFFSCREEN) {
52
- console.debug("FetchOffscreenService received message not specific to offscreen.");
53
- return;
48
+ static async handleFetchRequest({resource, options}) {
49
+ const validationErrors = FetchOffscreenService.validateMessageData(resource, options);
50
+ if (validationErrors) {
51
+ return validationErrors;
54
52
  }
55
53
 
56
- if (!(await FetchOffscreenService.validateMessageData(message.data))) {
57
- return;
58
- }
59
- const {id, resource, options} = message?.data || {};
60
54
  // Update the body to fit the data type to send (JSON or FORM DATA)
61
- options.body = options.body.dataType === FETCH_OFFSCREEN_DATA_TYPE_JSON ? options.body.data : FormDataUtils.arrayToFormData(options.body.data);
55
+ options.body = options.body?.dataType === FETCH_OFFSCREEN_DATA_TYPE_JSON ? options.body.data : FormDataUtils.arrayToFormData(options.body.data);
62
56
  await FetchOffscreenService.increaseAwaitingRequests();
63
57
  try {
64
58
  const response = await fetch(resource, options);
65
- await FetchOffscreenService.handleSuccessResponse(id, response);
59
+ return await FetchOffscreenService.handleSuccessResponse(response);
66
60
  } catch (error) {
67
- await FetchOffscreenService.handleErrorResponse(id, error);
61
+ console.log(error);
62
+ return FetchOffscreenService.handleErrorResponse(error);
63
+ } finally {
64
+ await FetchOffscreenService.decreaseAwaitingRequests();
68
65
  }
69
- await FetchOffscreenService.decreaseAwaitingRequests();
70
66
  }
71
67
 
72
68
  /**
73
69
  * Validate message data.
74
- * @param {object} messageData The message data
75
- * @returns {Promise<boolean>}
70
+ * @param {string} resource
71
+ * @param {object} options
72
+ * @returns {object|null}
76
73
  */
77
- static async validateMessageData(messageData = {}) {
74
+ static validateMessageData(resource, options) {
78
75
  let error;
79
-
80
- if (!messageData.id || typeof messageData.id !== "string" || !Validator.isUUID(messageData.id)) {
81
- error = new Error("FetchOffscreenService: message.id should be a valid uuid.");
82
- } else if (typeof messageData.resource !== "string") {
76
+ if (typeof resource !== "string") {
83
77
  error = new Error("FetchOffscreenService: message.resource should be a valid valid.");
84
- } else if (typeof messageData.options === "undefined" || !(messageData.options instanceof Object)) {
78
+ } else if (typeof options === "undefined" || !(options instanceof Object)) {
85
79
  error = new Error("FetchOffscreenService: message.options should be an object.");
86
80
  }
87
81
 
88
82
  if (error) {
89
- await FetchOffscreenService.handleErrorResponse(messageData.id, error);
90
- return false;
83
+ return FetchOffscreenService.handleErrorResponse(error);
91
84
  }
92
85
 
93
- return true;
86
+ return null;
94
87
  }
95
88
 
96
89
  /**
97
90
  * Handle fetch success, and send response to the service worker.
98
- * @param {string} id The fetch offscreen request id
99
91
  * @param {Response} response The fetch response
100
92
  * @returns {Promise<void>}
93
+ * @private
101
94
  */
102
- static async handleSuccessResponse(id, response) {
103
- await chrome.runtime.sendMessage({
104
- target: SEND_MESSAGE_TARGET_FETCH_OFFSCREEN_RESPONSE_HANDLER,
105
- id: id,
95
+ static async handleSuccessResponse(response) {
96
+ return {
97
+ data: await FetchOffscreenService.serializeResponse(response),
106
98
  type: FETCH_OFFSCREEN_RESPONSE_TYPE_SUCCESS,
107
- data: await FetchOffscreenService.serializeResponse(response)
108
- });
99
+ target: SEND_MESSAGE_TARGET_FETCH_OFFSCREEN_RESPONSE_HANDLER,
100
+ };
109
101
  }
110
102
 
111
103
  /**
112
104
  * Handle fetch error, and communicate it the service worker.
113
- * @param {string} id The fetch offscreen request id
114
105
  * @param {Error} error The fetch error
115
- * @returns {Promise<void>}
106
+ * @returns {object}
107
+ * @private
116
108
  */
117
- static async handleErrorResponse(id, error) {
109
+ static handleErrorResponse(error) {
118
110
  console.error(error);
119
- await chrome.runtime.sendMessage({
120
- target: SEND_MESSAGE_TARGET_FETCH_OFFSCREEN_RESPONSE_HANDLER,
121
- id: id,
122
- type: FETCH_OFFSCREEN_RESPONSE_TYPE_ERROR,
111
+ return {
123
112
  data: {
124
113
  name: error?.name,
125
114
  message: error?.message || "FetchOffscreenService: an unexpected error occurred"
126
- }
127
- });
115
+ },
116
+ type: FETCH_OFFSCREEN_RESPONSE_TYPE_ERROR,
117
+ target: SEND_MESSAGE_TARGET_FETCH_OFFSCREEN_RESPONSE_HANDLER,
118
+ };
128
119
  }
129
120
 
130
121
  /**
131
122
  * Serialize the fetch response to return to the service worker.
132
123
  * @param {Response} response The response to serialize
133
124
  * @returns {Promise<object>}
125
+ * @private
134
126
  */
135
127
  static async serializeResponse(response) {
136
128
  return {