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.
Files changed (115) hide show
  1. package/CHANGELOG.md +79 -1
  2. package/Gruntfile.js +1 -1
  3. package/RELEASE_NOTES.md +27 -71
  4. package/doc/browser-extension-class-diagram.md +9 -0
  5. package/package.json +5 -5
  6. package/src/all/_locales/sl/messages.json +2 -2
  7. package/src/all/background_page/controller/clipboard/cancelClipboardContentFlushController.js +51 -0
  8. package/src/all/background_page/controller/clipboard/cancelClipboardContentFlushController.test.js +46 -0
  9. package/src/all/background_page/controller/clipboard/copyTemporarilyToClipboardController.js +53 -0
  10. package/src/all/background_page/controller/clipboard/copyTemporarilyToClipboardController.test.js +47 -0
  11. package/src/all/background_page/controller/clipboard/{clipboardController.js → copyToClipboardController.js} +8 -8
  12. package/src/all/background_page/controller/clipboard/copyToClipboardController.test.js +47 -0
  13. package/src/all/background_page/controller/extension/getExtensionVersionController.test.js +1 -1
  14. package/src/all/background_page/controller/metadata/getOrFindMetadataKeysSettingsController.js +53 -0
  15. package/src/all/background_page/controller/metadata/getOrFindMetadataKeysSettingsController.test.js +106 -0
  16. package/src/all/background_page/controller/resource/updateResourceLocalStorageByFolderParentIdController.js +70 -0
  17. package/src/all/background_page/controller/resource/updateResourceLocalStorageByFolderParentIdController.test.js +78 -0
  18. package/src/all/background_page/controller/share/shareOneFolderController.test.js +2 -1
  19. package/src/all/background_page/controller/share/shareResourcesController.test.js +1 -1
  20. package/src/all/background_page/controller/user/deleteDryRunUserController.js +73 -0
  21. package/src/all/background_page/controller/user/deleteDryRunUserController.test.js +129 -0
  22. package/src/all/background_page/controller/user/deleteUserController.js +76 -0
  23. package/src/all/background_page/controller/user/deleteUserController.test.js +141 -0
  24. package/src/all/background_page/event/actionLogEvents.js +5 -12
  25. package/src/all/background_page/event/appEvents.js +33 -0
  26. package/src/all/background_page/event/findAllForActionLogController.js +58 -0
  27. package/src/all/background_page/event/findAllForActionLogController.test.js +43 -0
  28. package/src/all/background_page/event/quickAccessEvents.js +32 -0
  29. package/src/all/background_page/event/resourceEvents.js +11 -0
  30. package/src/all/background_page/event/userEvents.js +7 -20
  31. package/src/all/background_page/event/webIntegrationEvents.js +11 -0
  32. package/src/all/background_page/model/actionLog/{actionLogModel.js → findActionLogService.js} +25 -5
  33. package/src/all/background_page/model/actionLog/findActionLogService.test.js +61 -0
  34. package/src/all/background_page/model/comment/commentModel.js +5 -5
  35. package/src/all/background_page/model/entity/actionLog/actionLogsCollection.test.data.js +18 -0
  36. package/src/all/background_page/model/entity/actionLog/defaultActionLogEntity.test.data.js +34 -0
  37. package/src/all/background_page/model/entity/resource/resourcesCollection.js +25 -0
  38. package/src/all/background_page/model/entity/resource/resourcesCollection.test.js +34 -0
  39. package/src/all/background_page/model/import/resources/resourcesKdbxImportParser.test.js +0 -1
  40. package/src/all/background_page/model/user/userModel.js +0 -60
  41. package/src/all/background_page/pagemod/appPagemod.js +0 -2
  42. package/src/all/background_page/pagemod/appPagemod.test.js +2 -6
  43. package/src/all/background_page/service/alarm/globalAlarmService.js +2 -0
  44. package/src/all/background_page/service/api/actionLog/{actionLogService.js → actionLogApiService.js} +5 -4
  45. package/src/all/background_page/service/api/actionLog/actionLogApiService.test.js +55 -0
  46. package/src/all/background_page/service/api/comment/{commentService.js → commentApiService.js} +6 -7
  47. package/src/all/background_page/service/api/comment/commentApiService.test.js +122 -0
  48. package/src/all/background_page/service/api/resource/resourceService.js +1 -0
  49. package/src/all/background_page/service/auth/postLogoutService.js +2 -0
  50. package/src/all/background_page/service/auth/postLogoutService.test.js +4 -1
  51. package/src/all/background_page/service/browser/browserService.js +22 -0
  52. package/src/all/background_page/service/clipboard/clipboardProviderService.js +40 -0
  53. package/src/all/background_page/service/clipboard/clipboardProviderService.test.js +61 -0
  54. package/src/all/background_page/service/clipboard/copyToClipboardService.js +123 -0
  55. package/src/all/background_page/service/clipboard/copyToClipboardService.test.js +174 -0
  56. package/src/all/background_page/service/resource/findAndUpdateResourcesLocalStorageService.js +52 -1
  57. package/src/all/background_page/service/resource/findAndUpdateResourcesLocalStorageService.test.js +100 -0
  58. package/src/all/background_page/service/resource/findResourcesService.js +53 -19
  59. package/src/all/background_page/service/resource/findResourcesService.test.js +191 -0
  60. package/src/all/background_page/service/resourceType/updateResourceTypesService.test.js +1 -1
  61. package/src/all/background_page/service/share/shareResourceService.test.js +1 -1
  62. package/src/all/background_page/service/user/deleteUserService.js +97 -0
  63. package/src/all/background_page/service/user/deleteUserService.test.js +178 -0
  64. package/src/all/locales/de-DE/common.json +2 -2
  65. package/src/all/locales/es-ES/common.json +2 -2
  66. package/src/all/locales/fr-FR/common.json +5 -5
  67. package/src/all/locales/it-IT/common.json +2 -2
  68. package/src/all/locales/ja-JP/common.json +2 -2
  69. package/src/all/locales/ko-KR/common.json +2 -2
  70. package/src/all/locales/lt-LT/common.json +2 -2
  71. package/src/all/locales/nl-NL/common.json +2 -2
  72. package/src/all/locales/pl-PL/common.json +5 -5
  73. package/src/all/locales/pt-BR/common.json +2 -2
  74. package/src/all/locales/ro-RO/common.json +2 -2
  75. package/src/all/locales/ru-RU/common.json +2 -2
  76. package/src/all/locales/sl-SI/common.json +33 -33
  77. package/src/all/locales/sv-SE/common.json +2 -2
  78. package/src/all/locales/uk-UA/common.json +3 -3
  79. package/src/chrome/manifest.json +1 -1
  80. package/src/chrome/polyfill/clipboard/edgeBackgroundPageClipboardService.js +31 -0
  81. package/src/chrome/polyfill/clipboard/edgeBackgroundPageClipboardService.test.js +51 -0
  82. package/src/chrome-mv3/index.js +3 -3
  83. package/src/chrome-mv3/manifest.json +1 -1
  84. package/src/chrome-mv3/offscreens/{fetch.html → offscreen.html} +1 -1
  85. package/src/chrome-mv3/offscreens/{fetch.js → offscreen.js} +2 -2
  86. package/src/chrome-mv3/offscreens/service/clipboard/writeClipobardOffscreenService.js +54 -0
  87. package/src/chrome-mv3/offscreens/service/clipboard/writeClipobardOffscreenService.test.js +56 -0
  88. package/src/chrome-mv3/offscreens/service/network/fetchOffscreenService.js +36 -44
  89. package/src/chrome-mv3/offscreens/service/network/fetchOffscreenService.test.data.js +0 -1
  90. package/src/chrome-mv3/offscreens/service/network/fetchOffscreenService.test.js +90 -120
  91. package/src/chrome-mv3/offscreens/service/offscreen/handleOffscreenRequestService.js +85 -0
  92. package/src/chrome-mv3/offscreens/service/offscreen/handleOffscreenRequestService.test.js +99 -0
  93. package/src/chrome-mv3/polyfill/clipboardOffscreenPolyfill.js +19 -0
  94. package/src/chrome-mv3/serviceWorker/service/clipboard/requestClipboardOffscreenService.js +51 -0
  95. package/src/chrome-mv3/serviceWorker/service/clipboard/requestClipboardOffscreenService.test.js +70 -0
  96. package/src/chrome-mv3/serviceWorker/service/clipboard/responseClipboardOffscreenService.js +25 -0
  97. package/src/chrome-mv3/serviceWorker/service/clipboard/responseClipboardOffscreenService.test.data.js +21 -0
  98. package/src/chrome-mv3/serviceWorker/service/clipboard/responseClipboardOffscreenService.test.js +33 -0
  99. package/src/chrome-mv3/serviceWorker/service/network/requestFetchOffscreenService.js +25 -50
  100. package/src/chrome-mv3/serviceWorker/service/network/requestFetchOffscreenService.test.js +16 -39
  101. package/src/chrome-mv3/serviceWorker/service/network/responseFetchOffscreenService.js +14 -45
  102. package/src/chrome-mv3/serviceWorker/service/network/responseFetchOffscreenService.test.js +5 -37
  103. package/src/chrome-mv3/serviceWorker/service/offscreen/createOffscreenDocumentService.js +43 -0
  104. package/src/chrome-mv3/serviceWorker/service/offscreen/createOffscreenDocumentService.test.js +48 -0
  105. package/src/chrome-mv3/serviceWorker/service/offscreen/handleOffscreenResponseService.js +119 -0
  106. package/src/chrome-mv3/serviceWorker/service/offscreen/handleOffscreenResponseService.test.js +159 -0
  107. package/src/firefox/manifest.json +1 -1
  108. package/src/safari/manifest.json +1 -1
  109. package/test/jest.setup.js +4 -0
  110. package/test/mocks/mockNavigatorClipboard.js +40 -0
  111. package/test/mocks/mockWebExtensionPolyfill.js +2 -1
  112. package/{webpack-offscreens.fetch.config.js → webpack-offscreens.config.js} +1 -1
  113. package/webpack.service-worker.config.js +1 -0
  114. package/src/all/background_page/controller/clipboard/clipboardController.test.js +0 -68
  115. package/src/all/background_page/event/clipboardEvents.js +0 -28
@@ -11,7 +11,6 @@
11
11
  * @link https://www.passbolt.com Passbolt(tm)
12
12
  * @since 4.7.0
13
13
  */
14
- import {v4 as uuid} from 'uuid';
15
14
  import {enableFetchMocks} from "jest-fetch-mock";
16
15
  import FetchOffscreenService from './fetchOffscreenService';
17
16
  import each from "jest-each";
@@ -19,97 +18,119 @@ import {defaultFetchMessage, defaultFetchResponse} from "./fetchOffscreenService
19
18
 
20
19
  beforeEach(() => {
21
20
  enableFetchMocks();
22
- fetch.resetMocks();
23
21
  jest.clearAllMocks();
24
22
  // Flush runtime memory cache.
25
23
  FetchOffscreenService.pollingIntervalId = null;
26
24
  FetchOffscreenService.pendingRequestsCount = 0;
25
+
26
+ jest.spyOn(chrome.runtime, "sendMessage").mockImplementation(() => {});
27
27
  });
28
28
 
29
29
  describe("FetchOffscreenService", () => {
30
- describe("::handleFetchRequest", () => {
31
- it("should not proceed message if the message doesn't have a target", async() => {
30
+ describe("::handleSuccessResponse", () => {
31
+ it("should send a message through the chrome.runtime", async() => {
32
32
  expect.assertions(1);
33
- const spyOnFetch = jest.spyOn(self, "fetch");
34
- await FetchOffscreenService.handleFetchRequest({});
35
33
 
36
- expect(spyOnFetch).not.toHaveBeenCalled();
34
+ const message = defaultFetchMessage();
35
+ const fetchResponse = {
36
+ headers: new Array([]),
37
+ status: 200,
38
+ statusText: "OK",
39
+ text: async() => "",
40
+ ok: true,
41
+ url: message.data.resource,
42
+ redirected: false,
43
+ };
44
+
45
+ const result = await FetchOffscreenService.handleSuccessResponse(fetchResponse);
46
+
47
+ const expectedResult = {
48
+ target: "service-worker-fetch-offscreen-response-handler",
49
+ type: "success",
50
+ data: await FetchOffscreenService.serializeResponse(fetchResponse),
51
+ };
52
+
53
+ expect(result).toStrictEqual(expectedResult);
37
54
  });
55
+ });
38
56
 
39
- it("should not proceed message if targetting another process", async() => {
57
+ describe("::handleErrorResponse", () => {
58
+ it("should send an error message through the chrome.runtime", async() => {
40
59
  expect.assertions(1);
41
- const spyOnFetch = jest.spyOn(self, "fetch");
42
- await FetchOffscreenService.handleFetchRequest({target: "wrong-target"});
43
- expect(spyOnFetch).not.toHaveBeenCalled();
60
+
61
+ const error = new Error("Something went wrong");
62
+ error.name = "A fake error name";
63
+
64
+ const result = await FetchOffscreenService.handleErrorResponse(error);
65
+
66
+ const expectedResult = {
67
+ target: "service-worker-fetch-offscreen-response-handler",
68
+ type: "error",
69
+ data: {
70
+ name: error.name,
71
+ message: error.message
72
+ }
73
+ };
74
+
75
+ expect(result).toStrictEqual(expectedResult);
44
76
  });
77
+ });
45
78
 
79
+ describe("::handleFetchRequest", () => {
46
80
  it("should increase and decrease the pending request count", async() => {
47
81
  expect.assertions(5);
48
82
  const message = defaultFetchMessage();
49
- const spyOnFetch = jest.spyOn(self, "fetch").mockImplementation(async() => ({header: {}, body: {}}));
50
- const spyOnIncreaseRequestCount = jest.spyOn(FetchOffscreenService, "increaseAwaitingRequests");
51
- const spyOnDecreaseRequestCount = jest.spyOn(FetchOffscreenService, "decreaseAwaitingRequests");
52
- await FetchOffscreenService.handleFetchRequest(message);
53
-
54
- expect(spyOnFetch).toHaveBeenCalledTimes(1);
55
- expect(spyOnFetch).toHaveBeenCalledWith(message.data.resource, message.data.options);
56
- expect(spyOnIncreaseRequestCount).toHaveBeenCalledTimes(1);
57
- expect(spyOnDecreaseRequestCount).toHaveBeenCalledTimes(1);
83
+ jest.spyOn(self, "fetch").mockImplementation(async() => ({header: {}, body: {}}));
84
+ jest.spyOn(FetchOffscreenService, "increaseAwaitingRequests");
85
+ jest.spyOn(FetchOffscreenService, "decreaseAwaitingRequests");
86
+
87
+ await FetchOffscreenService.handleFetchRequest(message.data);
88
+
89
+ expect(self.fetch).toHaveBeenCalledTimes(1);
90
+ expect(self.fetch).toHaveBeenCalledWith(message.data.resource, message.data.options);
91
+ expect(FetchOffscreenService.increaseAwaitingRequests).toHaveBeenCalledTimes(1);
92
+ expect(FetchOffscreenService.decreaseAwaitingRequests).toHaveBeenCalledTimes(1);
58
93
  expect(FetchOffscreenService.pendingRequestsCount).toStrictEqual(0);
59
94
  });
60
95
 
61
96
  it("should handle a successful response", async() => {
62
97
  expect.assertions(3);
63
98
  const message = defaultFetchMessage();
64
- const expectedResponse = {header: {}, body: {}};
65
- const spyOnFetch = jest.spyOn(self, "fetch").mockImplementation(async() => expectedResponse);
66
- const spyOnSuccessResponse = jest.spyOn(FetchOffscreenService, "handleSuccessResponse");
67
- const spyOnErrorResponse = jest.spyOn(FetchOffscreenService, "handleErrorResponse");
68
- await FetchOffscreenService.handleFetchRequest(message);
69
-
70
- expect(spyOnFetch).toHaveBeenCalledTimes(1);
71
- expect(spyOnSuccessResponse).toHaveBeenCalledWith(message.data.id, expectedResponse);
72
- expect(spyOnErrorResponse).not.toHaveBeenCalledWith();
99
+ const expectedResponse = {headers: new Array([]), body: {}};
100
+
101
+ jest.spyOn(self, "fetch").mockImplementation(async() => expectedResponse);
102
+ jest.spyOn(FetchOffscreenService, "handleSuccessResponse").mockImplementation(() => {});
103
+ jest.spyOn(FetchOffscreenService, "handleErrorResponse").mockImplementation(() => {});
104
+
105
+ await FetchOffscreenService.handleFetchRequest(message.data);
106
+
107
+ expect(self.fetch).toHaveBeenCalledTimes(1);
108
+ expect(FetchOffscreenService.handleSuccessResponse).toHaveBeenCalledWith(expectedResponse);
109
+ expect(FetchOffscreenService.handleErrorResponse).not.toHaveBeenCalled();
73
110
  });
74
111
 
75
112
  it("should handle a erroneous response", async() => {
76
113
  expect.assertions(3);
77
114
  const message = defaultFetchMessage();
78
115
  const expectedError = new Error("Something went wrong!");
79
- const spyOnFetch = jest.spyOn(self, "fetch").mockImplementation(async() => { throw expectedError; });
80
- const spyOnSuccessResponse = jest.spyOn(FetchOffscreenService, "handleSuccessResponse");
81
- const spyOnErrorResponse = jest.spyOn(FetchOffscreenService, "handleErrorResponse");
82
- await FetchOffscreenService.handleFetchRequest(message);
83
-
84
- expect(spyOnFetch).toHaveBeenCalledTimes(1);
85
- expect(spyOnSuccessResponse).not.toHaveBeenCalledWith();
86
- expect(spyOnErrorResponse).toHaveBeenCalledWith(message.data.id, expectedError);
116
+
117
+ jest.spyOn(self, "fetch").mockImplementation(async() => { throw expectedError; });
118
+ jest.spyOn(FetchOffscreenService, "handleSuccessResponse").mockImplementation(() => {});
119
+ jest.spyOn(FetchOffscreenService, "handleErrorResponse").mockImplementation(() => {});
120
+
121
+ await FetchOffscreenService.handleFetchRequest(message.data);
122
+
123
+ expect(self.fetch).toHaveBeenCalledTimes(1);
124
+ expect(FetchOffscreenService.handleSuccessResponse).not.toHaveBeenCalledWith();
125
+ expect(FetchOffscreenService.handleErrorResponse).toHaveBeenCalledWith(expectedError);
87
126
  });
88
127
  });
89
128
 
90
129
  describe("::validateMessageData", () => {
91
- it("should validate if the message data respects the format", async() => {
130
+ it("should validate if the message data respects the format", () => {
92
131
  const message = defaultFetchMessage();
93
- const validation = await FetchOffscreenService.validateMessageData(message.data);
94
- expect(validation).toBeTruthy();
95
- expect(chrome.runtime.sendMessage).not.toHaveBeenCalled();
96
- });
97
-
98
- each([
99
- {scenario: "undefined", id: undefined},
100
- {scenario: "null", id: null},
101
- {scenario: "invalid string", id: "invalid"},
102
- {scenario: "boolean", id: true},
103
- {scenario: "object", id: {data: crypto.randomUUID()}},
104
- ]).describe("should fail if message id is not valid", _props => {
105
- it(`should trow if message id: ${_props.scenario}`, async() => {
106
- const message = defaultFetchMessage();
107
- message.data.id = _props.id;
108
- const spyOnErrorResponse = jest.spyOn(FetchOffscreenService, "handleErrorResponse");
109
- const validation = await FetchOffscreenService.validateMessageData(message.data);
110
- expect(validation).toBeFalsy();
111
- expect(spyOnErrorResponse).toHaveBeenCalledWith(message.data.id, expect.any(Error));
112
- });
132
+ const validation = FetchOffscreenService.validateMessageData(message.data.resource, message.data.options);
133
+ expect(validation).toBeNull();
113
134
  });
114
135
 
115
136
  each([
@@ -122,10 +143,13 @@ describe("FetchOffscreenService", () => {
122
143
  it(`should fail if message resource: ${_props.scenario}`, async() => {
123
144
  const message = defaultFetchMessage();
124
145
  message.data.resource = _props.resource;
125
- const spyOnErrorResponse = jest.spyOn(FetchOffscreenService, "handleErrorResponse");
126
- const validation = await FetchOffscreenService.validateMessageData(message.data);
146
+
147
+ jest.spyOn(FetchOffscreenService, "handleErrorResponse").mockImplementation(() => {});
148
+
149
+ const validation = await FetchOffscreenService.validateMessageData(message.data.resource, message.data.options);
150
+
127
151
  expect(validation).toBeFalsy();
128
- expect(spyOnErrorResponse).toHaveBeenCalledWith(message.data.id, expect.any(Error));
152
+ expect(FetchOffscreenService.handleErrorResponse).toHaveBeenCalledWith(expect.any(Error));
129
153
  });
130
154
  });
131
155
 
@@ -139,68 +163,14 @@ describe("FetchOffscreenService", () => {
139
163
  it(`should fail if message options: ${_props.scenario}`, async() => {
140
164
  const message = defaultFetchMessage();
141
165
  message.data.options = _props.options;
142
- const spyOnErrorResponse = jest.spyOn(FetchOffscreenService, "handleErrorResponse");
143
- const validation = await FetchOffscreenService.validateMessageData(message.data);
144
- expect(validation).toBeFalsy();
145
- expect(spyOnErrorResponse).toHaveBeenCalledWith(message.data.id, expect.any(Error));
146
- });
147
- });
148
- });
149
-
150
- describe("::handleSuccessResponse", () => {
151
- it("should send a message through the chrome.runtime", async() => {
152
- expect.assertions(2);
153
-
154
- const message = defaultFetchMessage();
155
-
156
- const fetchResponse = {
157
- headers: new Array([]),
158
- status: 200,
159
- statusText: "OK",
160
- text: async() => "",
161
- ok: true,
162
- url: message.data.resource,
163
- redirected: false,
164
- };
165
- await FetchOffscreenService.handleSuccessResponse(message.data.id, fetchResponse);
166
166
 
167
- const expectedCall = {
168
- target: "service-worker-fetch-offscreen-response-handler",
169
- id: message.data.id,
170
- type: "success",
171
- data: await FetchOffscreenService.serializeResponse(fetchResponse),
172
- };
167
+ jest.spyOn(FetchOffscreenService, "handleErrorResponse").mockImplementation(() => {});
173
168
 
174
- expect(chrome.runtime.sendMessage).toHaveBeenCalledTimes(1);
175
- expect(chrome.runtime.sendMessage).toHaveBeenCalledWith(expectedCall);
176
- });
177
- });
169
+ const validation = await FetchOffscreenService.validateMessageData(message.data.resource, message.data.options);
178
170
 
179
- describe("::handleErrorResponse", () => {
180
- it("should send an error message through the chrome.runtime", async() => {
181
- expect.assertions(2);
182
-
183
- const dataFetch = {
184
- id: uuid(),
185
- };
186
-
187
- const error = new Error("Something went wrong");
188
- error.name = "A fake error name";
189
- const spyOnChromeRuntime = jest.spyOn(chrome.runtime, "sendMessage");
190
- await FetchOffscreenService.handleErrorResponse(dataFetch.id, error);
191
-
192
- const expectedCall = {
193
- target: "service-worker-fetch-offscreen-response-handler",
194
- id: dataFetch.id,
195
- type: "error",
196
- data: {
197
- name: error.name,
198
- message: error.message
199
- }
200
- };
201
-
202
- expect(spyOnChromeRuntime).toHaveBeenCalledTimes(1);
203
- expect(spyOnChromeRuntime).toHaveBeenCalledWith(expectedCall);
171
+ expect(validation).toBeFalsy();
172
+ expect(FetchOffscreenService.handleErrorResponse).toHaveBeenCalledWith(expect.any(Error));
173
+ });
204
174
  });
205
175
  });
206
176
 
@@ -0,0 +1,85 @@
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
+ import FetchOffscreenService, {SEND_MESSAGE_TARGET_FETCH_OFFSCREEN} from "../network/fetchOffscreenService";
16
+ import WriteClipobardOffscreenService, {SEND_MESSAGE_TARGET_CLIPBOARD_WRITE_OFFSCREEN} from "../clipboard/writeClipobardOffscreenService";
17
+ import {assertUuid} from "../../../../all/background_page/utils/assertions";
18
+
19
+ export const SEND_MESSAGE_TARGET_OFFSCREEN_ERROR_RESPONSE_HANDLER = "service-worker-offscreen-error-response-handler";
20
+
21
+ export default class HandleOffscreenRequestService {
22
+ /**
23
+ * Handle fetch request.
24
+ * @param {object} message Browser runtime.onMessage listener message.
25
+ * @returns {Promise<void>}
26
+ */
27
+ static async handleOffscreenRequest(message) {
28
+ HandleOffscreenRequestService._assertOffscreenRequest(message);
29
+
30
+ const REQUEST_HANDLE_MAP = {
31
+ [SEND_MESSAGE_TARGET_FETCH_OFFSCREEN]: FetchOffscreenService.handleFetchRequest,
32
+ [SEND_MESSAGE_TARGET_CLIPBOARD_WRITE_OFFSCREEN]: WriteClipobardOffscreenService.handleClipboardRequest,
33
+ };
34
+
35
+ const requestHandler = REQUEST_HANDLE_MAP[message.target];
36
+ if (!requestHandler) {
37
+ console.debug(`HandleOffscreenRequestService received an unsupported request: "${message.target}".`);
38
+ return;
39
+ }
40
+
41
+ try {
42
+ const result = await requestHandler(message.data);
43
+ HandleOffscreenRequestService._sendResponseBack(message.id, result);
44
+ } catch (error) {
45
+ HandleOffscreenRequestService._sendErrorResponseBack(message.id, error);
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Handle fetch success, and send response to the service worker.
51
+ * @param {string} id The fetch offscreen request id
52
+ * @param {object} data the data to send back to the requester.
53
+ * @returns {Promise<void>}
54
+ */
55
+ static async _sendResponseBack(id, data) {
56
+ await chrome.runtime.sendMessage({
57
+ id: id,
58
+ ...data,
59
+ });
60
+ }
61
+
62
+ /**
63
+ * Handle fetch success, and send response to the service worker.
64
+ * @param {string} id The fetch offscreen request id
65
+ * @param {object} data the data to send back to the requester.
66
+ * @returns {Promise<void>}
67
+ */
68
+ static async _sendErrorResponseBack(id, error) {
69
+ await chrome.runtime.sendMessage({
70
+ id: id,
71
+ target: SEND_MESSAGE_TARGET_OFFSCREEN_ERROR_RESPONSE_HANDLER,
72
+ data: {error: JSON.stringify(error, Object.getOwnPropertyNames(error))},
73
+ });
74
+ }
75
+
76
+ /**
77
+ * Asserts that the given message is a valid offscreen response message.
78
+ * @param {object} message
79
+ * @throws {Error} if message.id is not a valid UUID
80
+ * @private
81
+ */
82
+ static _assertOffscreenRequest(message) {
83
+ assertUuid(message.id, "HandleOffscreenRequestService: message.id should be a valid uuid.");
84
+ }
85
+ }
@@ -0,0 +1,99 @@
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
+ import FetchOffscreenService, {FETCH_OFFSCREEN_DATA_TYPE_JSON} from "../network/fetchOffscreenService";
16
+ import WriteClipobardOffscreenService from "../clipboard/writeClipobardOffscreenService";
17
+ import HandleOffscreenRequestService, {SEND_MESSAGE_TARGET_OFFSCREEN_ERROR_RESPONSE_HANDLER} from "./handleOffscreenRequestService";
18
+ import {v4 as uuidv4} from "uuid";
19
+
20
+ beforeEach(() => {
21
+ jest.clearAllMocks();
22
+ });
23
+
24
+ describe("HandleOffscreenRequestService", () => {
25
+ describe("::handleOffscreenRequest", () => {
26
+ it("should redirect fetch request to FetchOffscreenService", async() => {
27
+ expect.assertions(3);
28
+
29
+ const message = {
30
+ id: uuidv4(),
31
+ target: "fetch-offscreen",
32
+ data: {
33
+ resource: "https://www.passbolt.com",
34
+ options: {
35
+ body: {
36
+ data: "test",
37
+ dataType: FETCH_OFFSCREEN_DATA_TYPE_JSON
38
+ }
39
+ }
40
+ },
41
+ };
42
+
43
+ jest.spyOn(FetchOffscreenService, "handleFetchRequest").mockImplementation(() => {});
44
+ jest.spyOn(WriteClipobardOffscreenService, "handleClipboardRequest");
45
+
46
+ await HandleOffscreenRequestService.handleOffscreenRequest(message);
47
+
48
+ expect(FetchOffscreenService.handleFetchRequest).toHaveBeenCalledTimes(1);
49
+ expect(FetchOffscreenService.handleFetchRequest).toHaveBeenCalledWith(message.data);
50
+ expect(WriteClipobardOffscreenService.handleClipboardRequest).not.toHaveBeenCalled();
51
+ });
52
+
53
+ it("should redirect clipboard write request to WriteClipobardOffscreenService", async() => {
54
+ expect.assertions(3);
55
+
56
+ const message = {
57
+ id: uuidv4(),
58
+ target: "clipboard-write-offscreen",
59
+ data: {
60
+ clipboardContent: "test",
61
+ },
62
+ };
63
+
64
+ jest.spyOn(FetchOffscreenService, "handleFetchRequest");
65
+ jest.spyOn(WriteClipobardOffscreenService, "handleClipboardRequest").mockImplementation(() => {});
66
+
67
+ await HandleOffscreenRequestService.handleOffscreenRequest(message);
68
+
69
+ expect(FetchOffscreenService.handleFetchRequest).not.toHaveBeenCalled();
70
+ expect(WriteClipobardOffscreenService.handleClipboardRequest).toHaveBeenCalledTimes(1);
71
+ expect(WriteClipobardOffscreenService.handleClipboardRequest).toHaveBeenCalledWith(message.data);
72
+ });
73
+
74
+ it("should send back a generic offscreen message if something wrong happens during the offscreen process", async() => {
75
+ expect.assertions(2);
76
+
77
+ const message = {
78
+ id: uuidv4(),
79
+ target: "clipboard-write-offscreen",
80
+ data: {},
81
+ };
82
+
83
+ const error = new Error("Impossible to copy to clipboard");
84
+ jest.spyOn(WriteClipobardOffscreenService, "handleClipboardRequest").mockImplementation(() => { throw error; });
85
+ jest.spyOn(chrome.runtime, "sendMessage").mockImplementation(() => {});
86
+
87
+ await HandleOffscreenRequestService.handleOffscreenRequest(message);
88
+
89
+ const expectedMessage = {
90
+ id: message.id,
91
+ target: SEND_MESSAGE_TARGET_OFFSCREEN_ERROR_RESPONSE_HANDLER,
92
+ data: {error: JSON.stringify(error, Object.getOwnPropertyNames(error))},
93
+ };
94
+
95
+ expect(chrome.runtime.sendMessage).toHaveBeenCalledTimes(1);
96
+ expect(chrome.runtime.sendMessage).toHaveBeenCalledWith(expectedMessage);
97
+ });
98
+ });
99
+ });
@@ -0,0 +1,19 @@
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
+ const {RequestClipboardOffscreenService} = require("../serviceWorker/service/clipboard/requestClipboardOffscreenService");
16
+
17
+ module.exports = {
18
+ writeText: async data => RequestClipboardOffscreenService.writeText(data),
19
+ };
@@ -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 CreateOffscreenDocumentService from "../offscreen/createOffscreenDocumentService";
15
+ import HandleOffscreenResponseService from "../offscreen/handleOffscreenResponseService";
16
+
17
+ const {SEND_MESSAGE_TARGET_CLIPBOARD_WRITE_OFFSCREEN} = require("../../../offscreens/service/clipboard/writeClipobardOffscreenService");
18
+
19
+ export class RequestClipboardOffscreenService {
20
+ /**
21
+ * Run the equivalent of navigator.clipboard::writeText(content) through offscreen.
22
+ * @param {string} clipboardContent the content to copy to clipboard
23
+ * @returns {Promise<void>}
24
+ */
25
+ static async writeText(clipboardContent) {
26
+ await CreateOffscreenDocumentService.createIfNotExistOffscreenDocument();
27
+
28
+ const requestId = crypto.randomUUID();
29
+ const offscreenClipboardData = {clipboardContent};
30
+
31
+ return new Promise((resolve, reject) => {
32
+ // Stack the response listener callbacks.
33
+ HandleOffscreenResponseService.setResponseCallback(requestId, {resolve, reject});
34
+ return RequestClipboardOffscreenService.sendWriteTextOffscreenMessage(requestId, offscreenClipboardData)
35
+ .catch(reject);
36
+ });
37
+ }
38
+
39
+ /**
40
+ * Send message to the offscreen document for emulating a navigator.clipboard::writeText(data) operation.
41
+ * @param {object} offscreenData The data to copy to clipboard.
42
+ * @returns {Promise<*>}
43
+ */
44
+ static async sendWriteTextOffscreenMessage(id, data) {
45
+ return chrome.runtime.sendMessage({
46
+ id: id,
47
+ data: data,
48
+ target: SEND_MESSAGE_TARGET_CLIPBOARD_WRITE_OFFSCREEN,
49
+ });
50
+ }
51
+ }
@@ -0,0 +1,70 @@
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
+ import Validator from "validator";
16
+ import {RequestClipboardOffscreenService} from "./requestClipboardOffscreenService";
17
+ import {v4 as uuidv4} from "uuid";
18
+ import HandleOffscreenResponseService from "../offscreen/handleOffscreenResponseService";
19
+
20
+ beforeEach(() => {
21
+ jest.clearAllMocks();
22
+ });
23
+
24
+ describe("RequestClipboardOffscreenService", () => {
25
+ describe("::sendWriteTextOffscreenMessage", () => {
26
+ it("should send a message to the offscreen document", async() => {
27
+ expect.assertions(1);
28
+ const id = uuidv4();
29
+ const data = {clipboardContent: "test-data"};
30
+ const target = "clipboard-write-offscreen";
31
+
32
+ await RequestClipboardOffscreenService.sendWriteTextOffscreenMessage(id, data);
33
+
34
+ expect(chrome.runtime.sendMessage).toHaveBeenCalledWith({id, data, target});
35
+ });
36
+ });
37
+
38
+ describe("::writeText", () => {
39
+ it("should send a message to the offscreen document and stack the response callback handlers", async() => {
40
+ expect.assertions(5);
41
+
42
+ let sentMessage;
43
+ jest.spyOn(chrome.runtime, "sendMessage").mockImplementationOnce(message => {
44
+ sentMessage = message;
45
+ HandleOffscreenResponseService._offscreenResponsePromisesCallbacks[message.id].resolve();
46
+ });
47
+
48
+ const clipboardContentToWrite = "clipboard-content";
49
+ const requestPromise = RequestClipboardOffscreenService.writeText(clipboardContentToWrite);
50
+
51
+ expect(requestPromise).toBeInstanceOf(Promise);
52
+ await expect(requestPromise).resolves.not.toThrow();
53
+
54
+ expect(Validator.isUUID(sentMessage.id)).toBe(true);
55
+ expect(chrome.runtime.sendMessage).toHaveBeenCalledTimes(1);
56
+ expect(chrome.runtime.sendMessage).toHaveBeenCalledWith({
57
+ id: sentMessage.id,
58
+ data: {clipboardContent: clipboardContentToWrite},
59
+ target: "clipboard-write-offscreen",
60
+ });
61
+ });
62
+
63
+ it("should throw if the message cannot be sent to the offscreen document for unexpected reason", async() => {
64
+ expect.assertions(1);
65
+ jest.spyOn(chrome.runtime, "sendMessage").mockImplementationOnce(() => { throw new Error("Test error"); });
66
+
67
+ await expect(() => RequestClipboardOffscreenService.writeText("test")).rejects.toThrow();
68
+ });
69
+ });
70
+ });
@@ -0,0 +1,25 @@
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
+ export default class ResponseClipboardOffscreenService {
16
+ /**
17
+ * Handle clipboard::writeText offscreen response message.
18
+ * @param {object} message.
19
+ * @param {{resolve: function, reject: function}} promise the offscreen response callback.
20
+ * @return {void}
21
+ */
22
+ static handleClipboardResponse(message, promise) {
23
+ promise.resolve();
24
+ }
25
+ }
@@ -0,0 +1,21 @@
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
+ import {SEND_MESSAGE_TARGET_CLIPBOARD_WRITE_OFFSCREEN_RESPONSE_HANDLER} from "../../../offscreens/service/clipboard/writeClipobardOffscreenService";
16
+
17
+ export const defaultClipboardWriteResponseMessage = (message = {}) => ({
18
+ id: crypto.randomUUID(),
19
+ target: SEND_MESSAGE_TARGET_CLIPBOARD_WRITE_OFFSCREEN_RESPONSE_HANDLER,
20
+ ...message
21
+ });