Skip to content

Commit 8bae585

Browse files
authored
fix: Correct file name in web-ext submit in Node 24 (mozilla#3466)
1 parent c47b969 commit 8bae585

File tree

2 files changed

+40
-27
lines changed

2 files changed

+40
-27
lines changed

src/util/submit-addon.js

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,29 +14,6 @@ const log = createLogger(import.meta.url);
1414

1515
export const defaultAsyncFsReadFile = fsPromises.readFile;
1616

17-
// Used by fileFromSync method to make sure the form-data entry will
18-
// include a filename derived from the file path.
19-
//
20-
// TODO: Get rid of this hack when we will bump the web-ext nodejs
21-
// version required to nodejs v20 (where the native File constructor
22-
// exists).
23-
export class FileBlob extends Blob {
24-
#name = '';
25-
26-
constructor(fileBits, fileName, options) {
27-
super(fileBits, options);
28-
this.#name = String(fileName);
29-
}
30-
31-
get name() {
32-
return this.#name;
33-
}
34-
35-
get [Symbol.toStringTag]() {
36-
return 'File';
37-
}
38-
}
39-
4017
export class JwtApiAuth {
4118
#apiKey;
4219
#apiSecret;
@@ -116,7 +93,21 @@ export default class Client {
11693
// submitted and fail with the error message:
11794
// "Unsupported file type, please upload a supported file (.crx, .xpi, .zip)."
11895
const fileData = readFileSync(filePath);
119-
return new FileBlob([fileData], basename(filePath));
96+
// eslint-disable-next-line no-shadow -- File is in Node v20.0.0+.
97+
let File = global.File;
98+
// TODO: Use the File global directly without the fallback when we drop
99+
// support for Node versions before v20.
100+
if (typeof File === 'undefined') {
101+
// Even without File being public, its interface and constructor could
102+
// be accessed indirectly from the FormData interface. According to the
103+
// FormData spec (that Node.js implements, via undici), the entry value
104+
// of a FormData is always a scalar value or a File:
105+
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-entry-value
106+
const fd = new FormData();
107+
fd.set('x', new Blob([]));
108+
File = fd.get('x').constructor;
109+
}
110+
return new File([fileData], basename(filePath));
120111
}
121112

122113
nodeFetch(url, { method, headers, body, agent }) {

tests/unit/test-util/test.submit-addon.js

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { createHash } from 'crypto';
22
import { promises as fsPromises, readFileSync } from 'fs';
33
import path from 'path';
4+
// eslint-disable-next-line no-shadow -- TODO: Remove when we require Node v20+.
5+
import { File } from 'node:buffer';
6+
// ^ note: this was introduced in v18.13.0. Because of its unavailability in
7+
// earlier versions, the actual implementation in submit-addon.js retrieves
8+
// the File constructor in a different way, which also works in Node 18.0.0.
9+
// Our CI tests with Node 18.19.0 as the lowest version, so this passes tests.
410

511
// eslint-disable-next-line import/no-extraneous-dependencies
612
import CRC32 from 'crc-32';
@@ -16,9 +22,6 @@ import Client, {
1622
saveIdToFile,
1723
saveUploadUuidToFile,
1824
signAddon,
19-
20-
// eslint-disable-next-line no-shadow -- Not actually available under Node 20. Use global once possible.
21-
FileBlob as File,
2225
} from '../../../src/util/submit-addon.js';
2326
import { withTempDir } from '../../../src/util/temp-dir.js';
2427

@@ -372,6 +375,25 @@ describe('util.submit-addon', () => {
372375
assert.equal(await fileRes.text(), FILE_CONTENT);
373376
assert.equal(String(fileRes), '[object File]');
374377
}));
378+
379+
it('should return a File whose name is preserved in FormData', () =>
380+
withTempDir(async (tmpDir) => {
381+
const client = new Client(clientDefaults);
382+
const FILE_BASENAME = 'testfile.txt';
383+
const FILE_CONTENT = 'somecontent';
384+
const filePath = path.join(tmpDir.path(), FILE_BASENAME);
385+
await fsPromises.writeFile(filePath, FILE_CONTENT);
386+
const fileRes = client.fileFromSync(filePath);
387+
388+
// Regression test for https://github.com/mozilla/web-ext/issues/3418
389+
const fd = new FormData();
390+
fd.set('upload', fileRes);
391+
const fileOut = fd.get('upload');
392+
393+
assert.equal(fileOut.name, FILE_BASENAME);
394+
assert.equal(fileOut.size, FILE_CONTENT.length);
395+
assert.equal(await fileOut.text(), FILE_CONTENT);
396+
}));
375397
});
376398

377399
describe('getPreviousUuidOrUploadXpi', () => {

0 commit comments

Comments
 (0)