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
@@ -0,0 +1,33 @@
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 ResponseClipboardOffscreenService from "./responseClipboardOffscreenService";
16
+ import {defaultClipboardWriteResponseMessage} from "./responseClipboardOffscreenService.test.data";
17
+ import {defaultCallbacks} from "../network/responseFetchOffscreenService.test.data";
18
+
19
+ describe("ResponseClipboardOffscreenService", () => {
20
+ describe("::handleClipboardResponse", () => {
21
+ it("should resolve the promise from the response callback", () => {
22
+ expect.assertions(1);
23
+
24
+ const id = crypto.randomUUID();
25
+ const callbacks = defaultCallbacks();
26
+
27
+ const message = defaultClipboardWriteResponseMessage({id});
28
+ ResponseClipboardOffscreenService.handleClipboardResponse(message, callbacks);
29
+
30
+ expect(callbacks.resolve).toHaveBeenCalledTimes(1);
31
+ });
32
+ });
33
+ });
@@ -16,27 +16,21 @@ import {
16
16
  FETCH_OFFSCREEN_DATA_TYPE_FORM_DATA,
17
17
  FETCH_OFFSCREEN_DATA_TYPE_JSON
18
18
  } from "../../../offscreens/service/network/fetchOffscreenService";
19
+ import CreateOffscreenDocumentService from "../offscreen/createOffscreenDocumentService";
20
+ import HandleOffscreenResponseService from "../offscreen/handleOffscreenResponseService";
19
21
 
20
22
  const {SEND_MESSAGE_TARGET_FETCH_OFFSCREEN} = require("../../../offscreens/service/network/fetchOffscreenService");
21
23
 
22
24
  export const IS_FETCH_OFFSCREEN_PREFERRED_STORAGE_KEY = "IS_FETCH_OFFSCREEN_PREFERRED_STORAGE_KEY";
23
- const LOCK_CREATE_OFFSCREEN_FETCH_DOCUMENT = "LOCK_CREATE_OFFSCREEN_FETCH_DOCUMENT";
24
- const FETCH_OFFSCREEN_DOCUMENT_REASON = "WORKERS";
25
- const OFFSCREEN_URL = "offscreens/fetch.html";
26
25
 
27
26
  export class RequestFetchOffscreenService {
28
27
  /**
29
28
  * Preferred strategy cache.
30
29
  * @type {boolean|null}
30
+ * @private
31
31
  */
32
32
  static isFetchOffscreenPreferredCache = null;
33
33
 
34
- /**
35
- * The stack of requests promises callbacks using the request id as reference.
36
- * @type {object}
37
- */
38
- static offscreenRequestsPromisesCallbacks = {};
39
-
40
34
  /**
41
35
  * Fetch external service through fetch offscreen document.
42
36
  * @param {string} resource The fetch url resource, similar to the native fetch resource parameter.
@@ -54,6 +48,7 @@ export class RequestFetchOffscreenService {
54
48
  /**
55
49
  * Check if the fetch offscreen strategy is preferred.
56
50
  * @returns {Promise<boolean>}
51
+ * @private
57
52
  */
58
53
  static async isFetchOffscreenPreferred() {
59
54
  if (RequestFetchOffscreenService.isFetchOffscreenPreferredCache === null) {
@@ -69,6 +64,7 @@ export class RequestFetchOffscreenService {
69
64
  * @param {string} resource The fetch url resource, similar to the native fetch resource parameter.
70
65
  * @param {object} options The fetch options, similar to the native fetch option parameter.
71
66
  * @returns {Promise<Response>}
67
+ * @private
72
68
  */
73
69
  static async fetchNative(resource, options) {
74
70
  try {
@@ -89,53 +85,30 @@ export class RequestFetchOffscreenService {
89
85
  * @param {string} resource The fetch url resource, similar to the native fetch resource parameter.
90
86
  * @param {object} options The fetch options, similar to the native fetch option parameter.
91
87
  * @returns {Promise<Response>}
88
+ * @private
92
89
  */
93
90
  static async fetchOffscreen(resource, options) {
94
- // Create offscreen document if it does not already exist.
95
- await navigator.locks.request(
96
- LOCK_CREATE_OFFSCREEN_FETCH_DOCUMENT,
97
- RequestFetchOffscreenService.createIfNotExistOffscreenDocument);
91
+ await CreateOffscreenDocumentService.createIfNotExistOffscreenDocument();
98
92
 
99
- const offscreenFetchId = crypto.randomUUID();
100
- const offscreenFetchData = await RequestFetchOffscreenService.buildOffscreenData(offscreenFetchId, resource, options);
93
+ const requestId = crypto.randomUUID();
94
+ const offscreenFetchData = await RequestFetchOffscreenService.buildOffscreenData(resource, options);
101
95
 
102
96
  return new Promise((resolve, reject) => {
103
97
  // Stack the response listener callbacks.
104
- RequestFetchOffscreenService.offscreenRequestsPromisesCallbacks[offscreenFetchId] = {resolve, reject};
105
- return RequestFetchOffscreenService.sendOffscreenMessage(offscreenFetchData)
98
+ HandleOffscreenResponseService.setResponseCallback(requestId, {resolve, reject});
99
+ return RequestFetchOffscreenService.sendOffscreenMessage(requestId, offscreenFetchData)
106
100
  .catch(reject);
107
101
  });
108
102
  }
109
103
 
110
- /**
111
- * Create fetch offscreen document if it does not exist yet.
112
- * @returns {Promise<void>}
113
- */
114
- static async createIfNotExistOffscreenDocument() {
115
- const existingContexts = await chrome.runtime.getContexts({
116
- contextTypes: ["OFFSCREEN_DOCUMENT"],
117
- documentUrls: [chrome.runtime.getURL(OFFSCREEN_URL)]
118
- });
119
-
120
- if (existingContexts.length > 0) {
121
- return;
122
- }
123
-
124
- await chrome.offscreen.createDocument({
125
- url: OFFSCREEN_URL,
126
- reasons: [FETCH_OFFSCREEN_DOCUMENT_REASON],
127
- justification: "Used to perform fetch to services such as the passbolt API serving invalid certificate.",
128
- });
129
- }
130
-
131
104
  /**
132
105
  * Build offscreen message data.
133
- * @param {string} id The identifier of the offscreen fetch request.
134
106
  * @param {string} resource The fetch url resource, similar to the native fetch resource parameter.
135
- * @param {object} fetchOptions The fetch options, similar to the native fetch option parameter.
136
- * @returns {object}
107
+ * @param {object} [fetchOptions = {}] The fetch options, similar to the native fetch option parameter.
108
+ * @returns {Promise<object>}
109
+ * @private
137
110
  */
138
- static async buildOffscreenData(id, resource, fetchOptions = {}) {
111
+ static async buildOffscreenData(resource, fetchOptions = {}) {
139
112
  const options = JSON.parse(JSON.stringify(fetchOptions));
140
113
 
141
114
  // Format FormData fetch options to allow its serialization.
@@ -152,27 +125,29 @@ export class RequestFetchOffscreenService {
152
125
  };
153
126
  }
154
127
 
155
- return {id, resource, options};
128
+ return {resource, options};
156
129
  }
157
130
 
158
131
  /**
159
132
  * Send message to the offscreen fetch document.
160
- * @param {object} offscreenData The offscreen message data.
161
- * @param {string} offscreenData.id The identifier of the offscreen fetch request.
133
+ * @param {string} id the identified of the request
162
134
  * @param {string} offscreenData.resource The fetch url resource, similar to the native fetch resource parameter.
163
- * @param {object} offscreenData.fetchOptions The fetch options, similar to the native fetch option parameter.
164
- * @returns {Promise<*>}
135
+ * @param {object} offscreenData.options The fetch options, similar to the native fetch option parameter.
136
+ * @returns {Promise<void>}
137
+ * @private
165
138
  */
166
- static async sendOffscreenMessage(offscreenData) {
139
+ static async sendOffscreenMessage(id, data) {
167
140
  return chrome.runtime.sendMessage({
141
+ id: id,
142
+ data: data,
168
143
  target: SEND_MESSAGE_TARGET_FETCH_OFFSCREEN,
169
- data: offscreenData
170
144
  });
171
145
  }
172
146
 
173
147
  /**
174
148
  * Mark the fetch offscreen strategy as preferred.
175
- * return {Promise<void>}
149
+ * @returns {Promise<void>}
150
+ * @private
176
151
  */
177
152
  static async markFetchOffscreenStrategyAsPreferred() {
178
153
  RequestFetchOffscreenService.isFetchOffscreenPreferredCache = true;
@@ -25,6 +25,8 @@ import {
25
25
  } from "../../../offscreens/service/network/fetchOffscreenService";
26
26
  import {fetchOptionsWithBodyFormData, fetchOptionWithBodyData} from "./requestFetchOffscreenService.test.data";
27
27
  import FormDataUtils from "../../../../all/background_page/utils/format/formDataUtils";
28
+ import {v4 as uuidv4} from "uuid";
29
+ import HandleOffscreenResponseService from "../offscreen/handleOffscreenResponseService";
28
30
 
29
31
  beforeEach(() => {
30
32
  enableFetchMocks();
@@ -79,54 +81,25 @@ describe("RequestFetchOffscreenService", () => {
79
81
  });
80
82
  });
81
83
 
82
- describe("::createIfNotExistOffscreenDocument", () => {
83
- it("should create the offscreen document if it does not exist yet ", async() => {
84
- expect.assertions(2);
85
- jest.spyOn(chrome.runtime, "getContexts").mockImplementationOnce(() => []);
86
- await RequestFetchOffscreenService.createIfNotExistOffscreenDocument();
87
-
88
- const expectedGetContextsData = {
89
- contextTypes: ["OFFSCREEN_DOCUMENT"],
90
- documentUrls: ["chrome-extension://didegimhafipceonhjepacocaffmoppf/offscreens/fetch.html"]
91
- };
92
- const expectedCreateDocumentData = {
93
- url: "offscreens/fetch.html",
94
- reasons: ["WORKERS"],
95
- justification: "Used to perform fetch to services such as the passbolt API serving invalid certificate."
96
- };
97
- expect(chrome.runtime.getContexts).toHaveBeenCalledWith(expectedGetContextsData);
98
- expect(chrome.offscreen.createDocument).toHaveBeenCalledWith(expectedCreateDocumentData);
99
- });
100
-
101
- it("should not create the offscreen document if it already exist ", async() => {
102
- expect.assertions(1);
103
- jest.spyOn(chrome.runtime, "getContexts").mockImplementationOnce(() => ["shallow-offscreen-document-mock"]);
104
- await RequestFetchOffscreenService.createIfNotExistOffscreenDocument();
105
- expect(chrome.offscreen.createDocument).not.toHaveBeenCalled();
106
- });
107
- });
108
-
109
84
  describe("::buildOffscreenData", () => {
110
85
  it("should build data to send to the offscreen document", async() => {
111
86
  expect.assertions(1);
112
- const id = crypto.randomUUID();
113
87
  const resource = "https://test.passbolt.com/passbolt-unit-test/test.json";
114
88
  const options = fetchOptionWithBodyData();
115
- const offscreenData = await RequestFetchOffscreenService.buildOffscreenData(id, resource, options);
89
+ const offscreenData = await RequestFetchOffscreenService.buildOffscreenData(resource, options);
116
90
  options.body = {
117
91
  data: options.body,
118
92
  dataType: FETCH_OFFSCREEN_DATA_TYPE_JSON
119
93
  };
120
94
  // Ensure body remains a form data after serialization.
121
- expect(offscreenData).toEqual({id, resource, options});
95
+ expect(offscreenData).toEqual({resource, options});
122
96
  });
123
97
 
124
98
  it("should ensure given fetch options body will not be altered", async() => {
125
99
  expect.assertions(2);
126
- const id = crypto.randomUUID();
127
100
  const resource = "https://test.passbolt.com/passbolt-unit-test/test.json";
128
101
  const fetchOptions = fetchOptionsWithBodyFormData();
129
- const offscreenData = await RequestFetchOffscreenService.buildOffscreenData(id, resource, fetchOptions);
102
+ const offscreenData = await RequestFetchOffscreenService.buildOffscreenData(resource, fetchOptions);
130
103
  // Ensure body remains a form data after serialization.
131
104
  expect(offscreenData.options.body.data).toBeInstanceOf(Array);
132
105
  expect(offscreenData.options.body.dataType).toStrictEqual(FETCH_OFFSCREEN_DATA_TYPE_FORM_DATA);
@@ -134,14 +107,12 @@ describe("RequestFetchOffscreenService", () => {
134
107
 
135
108
  it("should transform FormData body into serialized encoded url parameters", async() => {
136
109
  expect.assertions(1);
137
- const id = crypto.randomUUID();
138
110
  const resource = "https://test.passbolt.com/passbolt-unit-test/test.json";
139
111
  const options = fetchOptionsWithBodyFormData();
140
112
 
141
- const offscreenData = await RequestFetchOffscreenService.buildOffscreenData(id, resource, options);
113
+ const offscreenData = await RequestFetchOffscreenService.buildOffscreenData(resource, options);
142
114
  // eslint-disable-next-line object-shorthand
143
115
  const expectedOffscreenMessageData = {
144
- id,
145
116
  resource,
146
117
  options: {
147
118
  ...options,
@@ -161,21 +132,27 @@ describe("RequestFetchOffscreenService", () => {
161
132
  describe("::sendOffscreenMessage", () => {
162
133
  it("should send a message to the offscreen document", async() => {
163
134
  expect.assertions(1);
135
+ const id = uuidv4();
164
136
  const data = {prop1: "value1"};
165
- await RequestFetchOffscreenService.sendOffscreenMessage(data);
166
137
  const target = SEND_MESSAGE_TARGET_FETCH_OFFSCREEN;
167
- expect(chrome.runtime.sendMessage).toHaveBeenCalledWith({target, data});
138
+
139
+ await RequestFetchOffscreenService.sendOffscreenMessage(id, data);
140
+
141
+ expect(chrome.runtime.sendMessage).toHaveBeenCalledWith({id, target, data});
168
142
  });
169
143
  });
170
144
 
171
145
  describe("::fetchOffscreen", () => {
172
146
  it("should send a message to the offscreen document and stack the response callback handlers", async() => {
173
147
  expect.assertions(4);
148
+
174
149
  const resource = "https://test.passbolt.com/passbolt-unit-test/test.json";
175
150
  const options = fetchOptionsWithBodyFormData();
151
+
176
152
  jest.spyOn(chrome.runtime, "sendMessage").mockImplementationOnce(message => {
177
- expect(Validator.isUUID(message.data.id)).toBe(true);
153
+ expect(Validator.isUUID(message.id)).toBe(true);
178
154
  const expectedMessageData = {
155
+ id: message.id,
179
156
  target: SEND_MESSAGE_TARGET_FETCH_OFFSCREEN,
180
157
  data: {
181
158
  ...message.data,
@@ -195,7 +172,7 @@ describe("RequestFetchOffscreenService", () => {
195
172
  },
196
173
  };
197
174
  expect(message).toEqual(expectedMessageData);
198
- RequestFetchOffscreenService.offscreenRequestsPromisesCallbacks[message.data.id].resolve();
175
+ HandleOffscreenResponseService._offscreenResponsePromisesCallbacks[message.id].resolve();
199
176
  });
200
177
  const requestPromise = RequestFetchOffscreenService.fetchOffscreen(resource, options);
201
178
  expect(requestPromise).toBeInstanceOf(Promise);
@@ -12,85 +12,54 @@
12
12
  * @since 4.7.0
13
13
  */
14
14
 
15
- import {assertUuid} from "../../../../all/background_page/utils/assertions";
16
15
  import {
17
16
  FETCH_OFFSCREEN_RESPONSE_TYPE_ERROR,
18
17
  FETCH_OFFSCREEN_RESPONSE_TYPE_SUCCESS,
19
- SEND_MESSAGE_TARGET_FETCH_OFFSCREEN_RESPONSE_HANDLER,
20
- SEND_MESSAGE_TARGET_FETCH_OFFSCREEN_POLLING_HANDLER,
21
18
  } from "../../../offscreens/service/network/fetchOffscreenService";
22
- import {RequestFetchOffscreenService} from "./requestFetchOffscreenService";
19
+
20
+ const FETCH_OFFSCREEN_RESPONSE_TYPES = [FETCH_OFFSCREEN_RESPONSE_TYPE_SUCCESS, FETCH_OFFSCREEN_RESPONSE_TYPE_ERROR];
23
21
 
24
22
  export default class ResponseFetchOffscreenService {
25
23
  /**
26
24
  * Handle fetch offscreen response message.
27
25
  * @param {object} message The message itself.
26
+ * @param {{resolve: function, reject: function}} message The message itself.
28
27
  * @return {void}
29
28
  */
30
- static handleFetchResponse(message) {
31
- // This is a polling message for long request to keep the service worker alive.
32
- if (message.target === SEND_MESSAGE_TARGET_FETCH_OFFSCREEN_POLLING_HANDLER) {
33
- console.debug("ResponseFetchOffscreenService: polled");
34
- return;
35
- }
36
-
37
- // Return early if this message isn't meant for the offscreen document.
38
- if (message.target !== SEND_MESSAGE_TARGET_FETCH_OFFSCREEN_RESPONSE_HANDLER) {
39
- console.debug("ResponseFetchOffscreenService: received message not specific to the service worker fetch offscreen response handler.");
40
- return;
41
- }
42
-
29
+ static handleFetchResponse(message, callbacks) {
43
30
  ResponseFetchOffscreenService.assertMessage(message);
44
- const {id, type, data} = message;
45
- const offscreenRequestPromiseCallbacks = ResponseFetchOffscreenService.consumeRequestPromiseCallbacksOrFail(id);
31
+
32
+ const {type, data} = message;
46
33
 
47
34
  if (type === FETCH_OFFSCREEN_RESPONSE_TYPE_SUCCESS) {
48
- offscreenRequestPromiseCallbacks.resolve(ResponseFetchOffscreenService.buildFetchResponse(data));
49
- } else if (type === FETCH_OFFSCREEN_RESPONSE_TYPE_ERROR) {
50
- offscreenRequestPromiseCallbacks.reject(new Error(data.message));
35
+ callbacks.resolve(ResponseFetchOffscreenService.buildFetchResponse(data));
36
+ } else {
37
+ callbacks.reject(new Error(data.message));
51
38
  }
52
39
  }
53
40
 
54
41
  /**
55
42
  * Assert message data.
56
43
  * @param {object} message The message.
57
- * @returns {void}
58
- * @throws {Error} If the message id is not a valid uuid.
59
- * @throws {Error} If the message data is not an object.
60
- * @throws {Error} If the message type is not valid.
44
+ * @throws {Error} If the message.data is not an object.
45
+ * @throws {Error} If the message.type is not valid.
46
+ * @private
61
47
  */
62
48
  static assertMessage(message) {
63
- const FETCH_OFFSCREEN_RESPONSE_TYPES = [FETCH_OFFSCREEN_RESPONSE_TYPE_SUCCESS, FETCH_OFFSCREEN_RESPONSE_TYPE_ERROR];
64
-
65
49
  if (!FETCH_OFFSCREEN_RESPONSE_TYPES.includes(message?.type)) {
66
50
  throw new Error(`ResponseFetchOffscreenService: message.type should be one of the following ${FETCH_OFFSCREEN_RESPONSE_TYPES.join(", ")}.`);
67
51
  }
68
- assertUuid(message?.id, "ResponseFetchOffscreenService: message.id should be a valid uuid.");
52
+
69
53
  if (!(message?.data instanceof Object)) {
70
54
  throw new Error("ResponseFetchOffscreenService: message.data should be an object.");
71
55
  }
72
56
  }
73
57
 
74
- /**
75
- * Consume the offscreen request promise callbacks or fail.
76
- * @param {string} id The identifier of the offscreen fetch request.
77
- * @returns {object}
78
- * @throws {Error} If no request promise callbacks can be found for the given offscreen fetch request id.
79
- */
80
- static consumeRequestPromiseCallbacksOrFail(id) {
81
- const offscreenRequestPromiseCallback = RequestFetchOffscreenService.offscreenRequestsPromisesCallbacks[id];
82
- if (!offscreenRequestPromiseCallback) {
83
- throw new Error("ResponseFetchOffscreenService: No request promise callbacks found for the given offscreen fetch request id.");
84
- }
85
- delete RequestFetchOffscreenService.offscreenRequestsPromisesCallbacks[id];
86
-
87
- return offscreenRequestPromiseCallback;
88
- }
89
-
90
58
  /**
91
59
  * Build native fetch response object based on offscreen message response data.
92
60
  * @param {object} data The fetch offscreen message response data.
93
61
  * @returns {Response}
62
+ * @private
94
63
  */
95
64
  static buildFetchResponse(data) {
96
65
  return new Response(data.text, {
@@ -109,25 +109,6 @@ describe("ResponseFetchOffscreenService", () => {
109
109
  });
110
110
  });
111
111
 
112
- describe("::consumeRequestPromiseCallbacksOrFail", () => {
113
- it("should consume the response handler associated to the given id", () => {
114
- expect.assertions(3);
115
- const id = crypto.randomUUID();
116
- const callbacks = defaultCallbacks();
117
- RequestFetchOffscreenService.offscreenRequestsPromisesCallbacks[id] = callbacks;
118
- const consumedCallbacks = ResponseFetchOffscreenService.consumeRequestPromiseCallbacksOrFail(id);
119
- expect(consumedCallbacks).not.toBeNull();
120
- expect(consumedCallbacks).toEqual(callbacks);
121
- expect(Object.keys(RequestFetchOffscreenService.offscreenRequestsPromisesCallbacks).length).toEqual(0);
122
- });
123
-
124
- it("should throw if no associated callbacks found for the given id", () => {
125
- expect.assertions(1);
126
- const id = crypto.randomUUID();
127
- expect(() => ResponseFetchOffscreenService.consumeRequestPromiseCallbacksOrFail(id)).toThrow();
128
- });
129
- });
130
-
131
112
  describe("::buildFetchResponse", () => {
132
113
  it("should build the fetch response object based on the offscreen message data", async() => {
133
114
  expect.assertions(8);
@@ -149,9 +130,9 @@ describe("ResponseFetchOffscreenService", () => {
149
130
  expect.assertions(1);
150
131
  const id = crypto.randomUUID();
151
132
  const callbacks = defaultCallbacks();
152
- RequestFetchOffscreenService.offscreenRequestsPromisesCallbacks[id] = callbacks;
153
133
  const message = defaultResponseMessage({id});
154
- ResponseFetchOffscreenService.handleFetchResponse(message);
134
+
135
+ ResponseFetchOffscreenService.handleFetchResponse(message, callbacks);
155
136
  expect(callbacks.resolve).toHaveBeenCalledWith(expect.any(Response));
156
137
  });
157
138
 
@@ -159,23 +140,10 @@ describe("ResponseFetchOffscreenService", () => {
159
140
  expect.assertions(1);
160
141
  const id = crypto.randomUUID();
161
142
  const callbacks = defaultCallbacks();
162
- RequestFetchOffscreenService.offscreenRequestsPromisesCallbacks[id] = callbacks;
163
- // eslint-disable-next-line object-shorthand
164
- const message = defaultResponseMessage({id, type: FETCH_OFFSCREEN_RESPONSE_TYPE_ERROR});
165
- ResponseFetchOffscreenService.handleFetchResponse(message);
166
- expect(callbacks.reject).toHaveBeenCalledWith(expect.any(Error));
167
- });
143
+ const message = defaultResponseMessage({id: id, type: FETCH_OFFSCREEN_RESPONSE_TYPE_ERROR});
168
144
 
169
- it("should ignore message having the wrong target", () => {
170
- expect.assertions(2);
171
- const id = crypto.randomUUID();
172
- const callbacks = defaultCallbacks();
173
- RequestFetchOffscreenService.offscreenRequestsPromisesCallbacks[id] = callbacks;
174
- // eslint-disable-next-line object-shorthand
175
- const message = defaultResponseMessage({id, target: "other-target"});
176
- ResponseFetchOffscreenService.handleFetchResponse(message);
177
- expect(callbacks.resolve).not.toHaveBeenCalled();
178
- expect(callbacks.reject).not.toHaveBeenCalled();
145
+ ResponseFetchOffscreenService.handleFetchResponse(message, callbacks);
146
+ expect(callbacks.reject).toHaveBeenCalledWith(expect.any(Error));
179
147
  });
180
148
  });
181
149
  });
@@ -0,0 +1,43 @@
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
+ const CLIPBOARD_OFFSCREEN_DOCUMENT_REASON = "CLIPBOARD";
15
+ const FETCH_OFFSCREEN_DOCUMENT_REASON = "WORKERS";
16
+ const OFFSCREEN_URL = "offscreens/offscreen.html";
17
+ const LOCK_CREATE_OFFSCREEN_DOCUMENT = "LOCK_CREATE_OFFSCREEN_DOCUMENT";
18
+
19
+ export default class CreateOffscreenDocumentService {
20
+ /**
21
+ * Create clipboard offscreen document if it does not exist yet.
22
+ * @returns {Promise<void>}
23
+ */
24
+ static async createIfNotExistOffscreenDocument() {
25
+ // Create offscreen document if it does not already exist.
26
+ await navigator.locks.request(LOCK_CREATE_OFFSCREEN_DOCUMENT, async() => {
27
+ const existingContexts = await chrome.runtime.getContexts({
28
+ contextTypes: ["OFFSCREEN_DOCUMENT"],
29
+ documentUrls: [chrome.runtime.getURL(OFFSCREEN_URL)]
30
+ });
31
+
32
+ if (existingContexts.length > 0) {
33
+ return;
34
+ }
35
+
36
+ await chrome.offscreen.createDocument({
37
+ url: OFFSCREEN_URL,
38
+ reasons: [FETCH_OFFSCREEN_DOCUMENT_REASON, CLIPBOARD_OFFSCREEN_DOCUMENT_REASON],
39
+ justification: "1. Read/write clipboard as clipboard API is unavailable in MV3 service workers 2. Perform requests to self hosted Passbolt API serving invalid certificate.",
40
+ });
41
+ });
42
+ }
43
+ }
@@ -0,0 +1,48 @@
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 CreateOffscreenDocumentService from "./createOffscreenDocumentService";
16
+
17
+ beforeEach(() => {
18
+ jest.clearAllMocks();
19
+ });
20
+
21
+ describe("CreateOffscreenDocumentService", () => {
22
+ describe("::createIfNotExistOffscreenDocument", () => {
23
+ it("should create the offscreen document if it does not exist yet ", async() => {
24
+ expect.assertions(2);
25
+ jest.spyOn(chrome.runtime, "getContexts").mockImplementationOnce(() => []);
26
+ await CreateOffscreenDocumentService.createIfNotExistOffscreenDocument();
27
+
28
+ const expectedGetContextsData = {
29
+ contextTypes: ["OFFSCREEN_DOCUMENT"],
30
+ documentUrls: ["chrome-extension://didegimhafipceonhjepacocaffmoppf/offscreens/offscreen.html"]
31
+ };
32
+ const expectedCreateDocumentData = {
33
+ url: "offscreens/offscreen.html",
34
+ reasons: ["WORKERS", "CLIPBOARD"],
35
+ justification: "1. Read/write clipboard as clipboard API is unavailable in MV3 service workers 2. Perform requests to self hosted Passbolt API serving invalid certificate.",
36
+ };
37
+ expect(chrome.runtime.getContexts).toHaveBeenCalledWith(expectedGetContextsData);
38
+ expect(chrome.offscreen.createDocument).toHaveBeenCalledWith(expectedCreateDocumentData);
39
+ });
40
+
41
+ it("should not create the offscreen document if it already exist ", async() => {
42
+ expect.assertions(1);
43
+ jest.spyOn(chrome.runtime, "getContexts").mockImplementationOnce(() => ["shallow-offscreen-document-mock"]);
44
+ await CreateOffscreenDocumentService.createIfNotExistOffscreenDocument();
45
+ expect(chrome.offscreen.createDocument).not.toHaveBeenCalled();
46
+ });
47
+ });
48
+ });