Skip to content

Commit

Permalink
Merge branch 'feature/PB-33235_Convert-formData-file-into-a-json-seri…
Browse files Browse the repository at this point in the history
…alisable-in-offscreen' into 'release'

PB-33235 Convert formData file into a json serialisable in offscreen

See merge request passbolt/passbolt-browser-extension!846
  • Loading branch information
Benj1er committed May 7, 2024
2 parents fe56c6d + 4024873 commit 7109c07
Show file tree
Hide file tree
Showing 9 changed files with 353 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
*/
import Entity from "passbolt-styleguide/src/shared/models/entity/abstract/entity";
import EntitySchema from "passbolt-styleguide/src/shared/models/entity/abstract/entitySchema";
import b64ToBlob from "../../../../utils/format/base64";
import Base64Utils from "../../../../utils/format/base64";


const ENTITY_NAME = 'AvatarUpdate';
Expand Down Expand Up @@ -47,7 +47,7 @@ class AvatarUpdateEntity extends Entity {
const filename = avatarBase64UpdateDto.filename;
const fileBase64 = avatarBase64UpdateDto.fileBase64;
const mimeType = avatarBase64UpdateDto.mimeType;
const file = b64ToBlob(fileBase64, mimeType);
const file = Base64Utils.base64ToBlob(fileBase64, mimeType);
const avatarUpdateDto = {file: file, filename: filename, mimeType: mimeType};
return new AvatarUpdateEntity(avatarUpdateDto);
}
Expand Down
72 changes: 50 additions & 22 deletions src/all/background_page/utils/format/base64.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,61 @@
/**
* Transforms a base 64 encoded file content into a file object.
* Useful when we need to transmit a file from the content code to the add-on code.
* @param string b64Data
* @param string contentType
* @param integer sliceSize
* @returns {*}
* Passbolt ~ Open source password manager for teams
* Copyright (c) Passbolt SA (https://www.passbolt.com)
*
* Licensed under GNU Affero General Public License version 3 of the or any later version.
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Passbolt SA (https://www.passbolt.com)
* @license https://opensource.org/licenses/AGPL-3.0 AGPL License
* @link https://www.passbolt.com Passbolt(tm)
* @since 4.8.0
*/
function b64ToBlob(b64Data, contentType, sliceSize) {
contentType = contentType || '';
sliceSize = sliceSize || 512;

const byteCharacters = atob(b64Data);
const byteArrays = [];
/**
* The class that deals with Passbolt to convert base64.
*/
class Base64Utils {
/**
* Transforms a base 64 encoded file content into a file object.
* Useful when we need to transmit a file from the content code to the add-on code.
* @param {string} b64Data The base64 data
* @param {string} contentType The content type
* @param {number} sliceSize The slice size
* @returns {*}
*/
static base64ToBlob(b64Data, contentType = "", sliceSize = 512) {
const byteCharacters = atob(b64Data);
const byteArrays = [];

for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);

const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}

const byteArray = new Uint8Array(byteNumbers);
const byteArray = new Uint8Array(byteNumbers);

byteArrays.push(byteArray);
byteArrays.push(byteArray);
}

return new Blob(byteArrays, {type: contentType});
}

const blob = new Blob(byteArrays, {type: contentType});
return blob;
/**
* Transforms a file object into a base 64 encoded file content.
* @param {Blob} blob
* @returns {Promise<string>}
*/
static blobToBase64(blob) {
return new Promise(resolve => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.readAsDataURL(blob);
});
}
}

export default b64ToBlob;
export default Base64Utils;
93 changes: 93 additions & 0 deletions src/all/background_page/utils/format/formDataUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* Passbolt ~ Open source password manager for teams
* Copyright (c) Passbolt SA (https://www.passbolt.com)
*
* Licensed under GNU Affero General Public License version 3 of the or any later version.
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Passbolt SA (https://www.passbolt.com)
* @license https://opensource.org/licenses/AGPL-3.0 AGPL License
* @link https://www.passbolt.com Passbolt(tm)
* @since 4.8.0
*/

import Base64Utils from "./base64";

/**
* The class that deals with Passbolt to convert formData.
*/
class FormDataUtils {
/**
* Transform a form data to an array of object
* @param {FormData} formData The form data
* @return {Promise<Array<Object>>}
*/
static async formDataToArray(formData) {
const formDataSerialized = [];
for (const [key, value] of formData.entries()) {
const formDataObject = {
key: key
};
// BLOB in FormData is transformed into a File
if (value instanceof File) {
formDataObject.value = await Base64Utils.blobToBase64(value);
formDataObject.name = value.name;
formDataObject.type = FormDataUtils.TYPE_FILE;
} else {
formDataObject.value = value;
formDataObject.type = FormDataUtils.TYPE_SCALAR;
}
formDataSerialized.push(formDataObject);
}
return formDataSerialized;
}

/**
* Transform an array of object to a form data
* @param {Array<Object>} array
* @return {FormData}
*/
static arrayToFormData(array) {
const formData = new FormData();
array.forEach(data => {
if (data.type === FormDataUtils.TYPE_SCALAR) {
formData.append(data.key, data.value);
} else {
const base64UrlSplit = data.value.split(',');
const blobBase64 = base64UrlSplit[1];
const mimeType = base64UrlSplit[0].split(':')[1].split(';')[0];
const blob = Base64Utils.base64ToBlob(blobBase64, mimeType);
formData.append(data.key, blob, data.name);
}
});
return formData;
}

/**
* Get the type scalar
* @return {string}
*/
static get TYPE_SCALAR() {
return "SCALAR";
}

/**
* Get the type file
* @return {string}
*/
static get TYPE_FILE() {
return "FILE";
}

/**
* Get the type blob
* @return {string}
* @constructor
*/
static get TYPE_BLOB() {
return "FILE";
}
}

export default FormDataUtils;
49 changes: 49 additions & 0 deletions src/all/background_page/utils/format/formDataUtils.test.data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Passbolt ~ Open source password manager for teams
* Copyright (c) Passbolt SA (https://www.passbolt.com)
*
* Licensed under GNU Affero General Public License version 3 of the or any later version.
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Passbolt SA (https://www.passbolt.com)
* @license https://opensource.org/licenses/AGPL-3.0 AGPL License
* @link https://www.passbolt.com Passbolt(tm)
* @since 4.8.0
*/

export const formDataString = () => {
const formDataBody = new FormData();
formDataBody.append("prop1", "value 1");
formDataBody.append("prop1", "value 2");
return formDataBody;
};

export const formDataFile = () => {
const formDataBody = new FormData();
const file1 = new File(['test'], "file 1", {type: 'image/png'});
const file2 = new File(['test'], "file 2", {type: 'image/png'});
formDataBody.append("file", file1, "file 1");
formDataBody.append("file", file2, "file 2");
return formDataBody;
};

export const formDataBlob = () => {
const formDataBody = new FormData();
const blob1 = new Blob(['test'], {type: 'text/plain'});
const blob2 = new Blob(['test'], {type: 'text/plain'});
formDataBody.append("blob", blob1, "blob 1");
formDataBody.append("blob", blob2, "blob 2");
return formDataBody;
};

export const formDataMixed = () => {
const formDataBody = new FormData();
formDataBody.append("prop1", "value 1");
const file = new File(['test'], "file 1", {type: 'image/png'});
formDataBody.append("file", file, "file 1");
const blob = new Blob(['test'], {type: 'text/plain'});
formDataBody.append("blob", blob, "blob 1");
return formDataBody;
};

105 changes: 105 additions & 0 deletions src/all/background_page/utils/format/formDataUtils.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/**
* Passbolt ~ Open source password manager for teams
* Copyright (c) Passbolt SA (https://www.passbolt.com)
*
* Licensed under GNU Affero General Public License version 3 of the or any later version.
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Passbolt SA (https://www.passbolt.com)
* @license https://opensource.org/licenses/AGPL-3.0 AGPL License
* @link https://www.passbolt.com Passbolt(tm)
* @since 4.8.0
*/
import {formDataMixed, formDataString} from "./formDataUtils.test.data";
import FormDataUtils from "./formDataUtils";
import {formDataBlob, formDataFile} from "./formDataUtils.test.data";

describe("FormDataUtils", () => {
beforeEach(() => {
jest.resetModules();
});

describe("FormDataUtils::formDataToArray", () => {
it("Should create an array of scalar object", async() => {
expect.assertions(1);
// data mocked
const formData = formDataString();
// process
const arrayObject = await FormDataUtils.formDataToArray(formData);
// expectations
const expectedArray = [
{key: "prop1", value: "value 1", type: FormDataUtils.TYPE_SCALAR},
{key: "prop1", value: "value 2", type: FormDataUtils.TYPE_SCALAR}
];
expect(arrayObject).toStrictEqual(expectedArray);
});

it("Should create an array of file object", async() => {
expect.assertions(1);
// data mocked
const formData = formDataFile();
// process
const arrayObject = await FormDataUtils.formDataToArray(formData);
// expectations
const expectedArray = [
{key: "file", value: "data:image/png;base64,dGVzdA==", name: "file 1", type: FormDataUtils.TYPE_FILE},
{key: "file", value: "data:image/png;base64,dGVzdA==", name: "file 2", type: FormDataUtils.TYPE_FILE}
];
expect(arrayObject).toStrictEqual(expectedArray);
});

it("Should create an array of blob object", async() => {
expect.assertions(1);
// data mocked
const formData = formDataBlob();
// process
const arrayObject = await FormDataUtils.formDataToArray(formData);
// expectations
const expectedArray = [
{key: "blob", value: "data:text/plain;base64,dGVzdA==", name: "blob 1", type: FormDataUtils.TYPE_BLOB},
{key: "blob", value: "data:text/plain;base64,dGVzdA==", name: "blob 2", type: FormDataUtils.TYPE_BLOB}
];
expect(arrayObject).toStrictEqual(expectedArray);
});

it("Should create an array of mixed object", async() => {
expect.assertions(1);
// data mocked
const formData = formDataMixed();
// process
const arrayObject = await FormDataUtils.formDataToArray(formData);
// expectations
const expectedArray = [
{key: "prop1", value: "value 1", type: FormDataUtils.TYPE_SCALAR},
{key: "file", value: "data:image/png;base64,dGVzdA==", name: "file 1", type: FormDataUtils.TYPE_FILE},
{key: "blob", value: "data:text/plain;base64,dGVzdA==", name: "blob 1", type: FormDataUtils.TYPE_BLOB}
];
expect(arrayObject).toStrictEqual(expectedArray);
});
});

describe("FormDataUtils::arrayToFormData", () => {
it("should form the same formData string from the origin", async() => {
expect.assertions(1);
// data mocked
const formData = formDataString();
// process
const arrayObject = await FormDataUtils.formDataToArray(formData);
const formDataReceived = FormDataUtils.arrayToFormData(arrayObject);
// expectations
expect(formData).toStrictEqual(formDataReceived);
});

it("should form the same formData mixed from the origin", async() => {
expect.assertions(1);
// data mocked
const formData = formDataMixed();
// process
const arrayObject = await FormDataUtils.formDataToArray(formData);
const formDataReceived = FormDataUtils.arrayToFormData(arrayObject);
// expectations
expect(formData).toStrictEqual(formDataReceived);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@
*/

import Validator from "validator";
import FormDataUtils from "../../../../all/background_page/utils/format/formDataUtils";

export const SEND_MESSAGE_TARGET_FETCH_OFFSCREEN = "fetch-offscreen";
export const SEND_MESSAGE_TARGET_FETCH_OFFSCREEN_RESPONSE_HANDLER = "service-worker-fetch-offscreen-response-handler";
export const SEND_MESSAGE_TARGET_FETCH_OFFSCREEN_POLLING_HANDLER = "service-worker-fetch-offscreen-polling-handler";
export const FETCH_OFFSCREEN_RESPONSE_TYPE_SUCCESS = "success";
export const FETCH_OFFSCREEN_RESPONSE_TYPE_ERROR = "error";
export const FETCH_OFFSCREEN_DATA_TYPE_JSON = "JSON";
export const FETCH_OFFSCREEN_DATA_TYPE_FORM_DATA = "FORM_DATA";
const POLLING_COUNTER_UPDATE_LOCK = "POLLING_COUNTER_UPDATE_LOCK";
const POLLING_PERIOD = 25_000;

Expand Down Expand Up @@ -54,7 +57,8 @@ export default class FetchOffscreenService {
return;
}
const {id, resource, options} = message?.data || {};

// Update the body to fit the data type to send (JSON or FORM DATA)
options.body = options.body.dataType === FETCH_OFFSCREEN_DATA_TYPE_JSON ? options.body.data : FormDataUtils.arrayToFormData(options.body.data);
await FetchOffscreenService.increaseAwaitingRequests();
try {
const response = await fetch(resource, options);
Expand Down
Loading

0 comments on commit 7109c07

Please sign in to comment.