Merge branch 'master' into master

This commit is contained in:
MathieuG-P
2025-04-06 14:32:03 +02:00
committed by GitHub
6 changed files with 106 additions and 136 deletions
+9
View File
@@ -840,5 +840,14 @@
"ReleaseImg": "https://clan.fastly.steamstatic.com/images/32055887/648716b53c17412cd269567ce67d3867514b6acd.png",
"ReleaseDate": "1740672033",
"year": "2025"
},
{
"BSVersion": "1.40.4",
"BSManifest": "3323243802033113443",
"OculusBinaryId": "8889412401158460",
"ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/510702041542166484",
"ReleaseImg": "https://clan.fastly.steamstatic.com/images/32055887/c27dac7f96e53672d020d97838cec1d5d28e3180.png",
"ReleaseDate": "1743515622",
"year": "2025"
}
]
+21 -21
View File
@@ -27,10 +27,10 @@
"electron-updater": "^6.3.9",
"fast-deep-equal": "^3.1.3",
"format-duration": "^3.0.2",
"framer-motion": "^12.5.0",
"framer-motion": "^12.6.3",
"fs-extra": "^11.3.0",
"global-agent": "^3.0.0",
"got": "^14.4.6",
"got": "^14.4.7",
"history": "^5.3.0",
"is-elevated": "^4.0.0",
"md5-file": "^5.0.0",
@@ -105,7 +105,7 @@
"css-loader": "^6.10.0",
"css-minimizer-webpack-plugin": "^6.0.0",
"detect-port": "^1.5.1",
"electron": "^35.0.1",
"electron": "^35.1.4",
"electron-builder": "^25.1.8",
"electron-devtools-installer": "^4.0.0",
"electronmon": "^2.0.3",
@@ -11870,9 +11870,9 @@
}
},
"node_modules/electron": {
"version": "35.0.1",
"resolved": "https://registry.npmjs.org/electron/-/electron-35.0.1.tgz",
"integrity": "sha512-iQonj6lnPhqfqha2KXx6LzV1dnu6UPTCWK+b7f9Zvg828umGemi22DKbcJ3/q+Opn7iUVTWyqp9z1JQqkIi6OA==",
"version": "35.1.4",
"resolved": "https://registry.npmjs.org/electron/-/electron-35.1.4.tgz",
"integrity": "sha512-8HjE2wqxY//T09Of8k1eTpK/NeTG2FkTyRD+fyKXmec4wZVscGgZcmWFC0HYN4ktyHAjtplpxdFXjtqRnvzBMg==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -14433,13 +14433,13 @@
}
},
"node_modules/framer-motion": {
"version": "12.5.0",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.5.0.tgz",
"integrity": "sha512-buPlioFbH9/W7rDzYh1C09AuZHAk2D1xTA1BlounJ2Rb9aRg84OXexP0GLd+R83v0khURdMX7b5MKnGTaSg5iA==",
"version": "12.6.3",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.6.3.tgz",
"integrity": "sha512-2hsqknz23aloK85bzMc9nSR2/JP+fValQ459ZTVElFQ0xgwR2YqNjYSuDZdFBPOwVCt4Q9jgyTt6hg6sVOALzw==",
"license": "MIT",
"dependencies": {
"motion-dom": "^12.5.0",
"motion-utils": "^12.5.0",
"motion-dom": "^12.6.3",
"motion-utils": "^12.6.3",
"tslib": "^2.4.0"
},
"peerDependencies": {
@@ -14869,9 +14869,9 @@
}
},
"node_modules/got": {
"version": "14.4.6",
"resolved": "https://registry.npmjs.org/got/-/got-14.4.6.tgz",
"integrity": "sha512-rnhwfM/PhMNJ1i17k3DuDqgj0cKx3IHxBKVv/WX1uDKqrhi2Gv3l7rhPThR/Cc6uU++dD97W9c8Y0qyw9x0jag==",
"version": "14.4.7",
"resolved": "https://registry.npmjs.org/got/-/got-14.4.7.tgz",
"integrity": "sha512-DI8zV1231tqiGzOiOzQWDhsBmncFW7oQDH6Zgy6pDPrqJuVZMtoSgPLLsBZQj8Jg4JFfwoOsDA8NGtLQLnIx2g==",
"license": "MIT",
"dependencies": {
"@sindresorhus/is": "^7.0.1",
@@ -19233,18 +19233,18 @@
}
},
"node_modules/motion-dom": {
"version": "12.5.0",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.5.0.tgz",
"integrity": "sha512-uH2PETDh7m+Hjd1UQQ56yHqwn83SAwNjimNPE/kC+Kds0t4Yh7+29rfo5wezVFpPOv57U4IuWved5d1x0kNhbQ==",
"version": "12.6.3",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.6.3.tgz",
"integrity": "sha512-gRY08RjcnzgFYLemUZ1lo/e9RkBxR+6d4BRvoeZDSeArG4XQXERSPapKl3LNQRu22Sndjf1h+iavgY0O4NrYqA==",
"license": "MIT",
"dependencies": {
"motion-utils": "^12.5.0"
"motion-utils": "^12.6.3"
}
},
"node_modules/motion-utils": {
"version": "12.5.0",
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.5.0.tgz",
"integrity": "sha512-+hFFzvimn0sBMP9iPxBa9OtRX35ZQ3py0UHnb8U29VD+d8lQ8zH3dTygJWqK7av2v6yhg7scj9iZuvTS0f4+SA==",
"version": "12.6.3",
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.6.3.tgz",
"integrity": "sha512-R/b3Ia2VxtTNZ4LTEO5pKYau1OUNHOuUfxuP0WFCTDYdHkeTBR9UtxR1cc8mDmKr8PEhmmfnTKGz3rSMjNRoRg==",
"license": "MIT"
},
"node_modules/mrmime": {
+3 -3
View File
@@ -108,7 +108,7 @@
"css-loader": "^6.10.0",
"css-minimizer-webpack-plugin": "^6.0.0",
"detect-port": "^1.5.1",
"electron": "^35.0.1",
"electron": "^35.1.4",
"electron-builder": "^25.1.8",
"electron-devtools-installer": "^4.0.0",
"electronmon": "^2.0.3",
@@ -170,10 +170,10 @@
"electron-updater": "^6.3.9",
"fast-deep-equal": "^3.1.3",
"format-duration": "^3.0.2",
"framer-motion": "^12.5.0",
"framer-motion": "^12.6.3",
"fs-extra": "^11.3.0",
"global-agent": "^3.0.0",
"got": "^14.4.6",
"got": "^14.4.7",
"history": "^5.3.0",
"is-elevated": "^4.0.0",
"md5-file": "^5.0.0",
@@ -369,10 +369,12 @@ export class LocalMapsManagerService {
for (const folder of mapsFolders) {
log.info(">", folder);
const regex = new RegExp(`^${escapeRegExp(folder)}\\/`);
const entriesNames = isRoot
? null
: [ new RegExp(`^${escapeRegExp(folder)}\\/`) ];
const exported = await zip.extract(destination, {
entriesNames: [regex],
entriesNames,
abortToken: abortController
});
@@ -428,9 +430,10 @@ export class LocalMapsManagerService {
throw new Error("Cannot download map, no hash found");
}
log.info("Downloading map", map.name, map.id);
const zipUrl = map.versions.at(0).downloadURL;
log.info("Downloading map", map.name, map.id, zipUrl);
const mapFolderName = sanitize(`${map.id} (${map.metadata.songName} - ${map.metadata.levelAuthorName})`);
const mapsFolder = await this.getMapsFolderPath(version);
@@ -117,7 +117,7 @@ export class BsModsManagerService {
}
private async downloadZip(zipUrl: string): Promise<BsmZipExtractor> {
zipUrl = path.join(this.beatModsApi.MODS_REPO_URL, zipUrl);
zipUrl = new URL(zipUrl, this.beatModsApi.MODS_REPO_URL).href;
log.info("Download mod zip", zipUrl);
+65 -107
View File
@@ -9,12 +9,10 @@ import { tryit } from 'shared/helpers/error.helpers';
import path from 'path';
import { pipeline } from 'stream/promises';
import sanitize from 'sanitize-filename';
import internal from 'stream';
import { app } from 'electron';
export class RequestService {
private static instance: RequestService;
private preferredFamily: number | undefined = undefined;
private readonly baseHeaders = {
'User-Agent': `BSManager/${app.getVersion()} (Electron/${process.versions.electron} Chrome/${process.versions.chrome} Node/${process.versions.node})`,
}
@@ -32,23 +30,14 @@ export class RequestService {
public async getJSON<T = unknown>(url: string): Promise<{ data: T; headers: IncomingHttpHeaders }> {
const familiesToTry = this.preferredFamily ? [this.preferredFamily, this.preferredFamily === 4 ? 6 : 4] : [4, 6];
for (const family of familiesToTry) {
try {
// @ts-ignore (ESM is not well supported in this project, We need to move out electron-react-boilerplate, and use Vite)
const res = await got(url, { dnsLookupIpVersion: family, responseType: 'json', headers: this.baseHeaders });
this.preferredFamily = family;
return { data: res.body as T, headers: res.headers };
} catch (err) {
log.warn(`IPv${family} request failed, trying next one... URL: ${url}`, err);
}
try {
// @ts-ignore (ESM is not well supported in this project, We need to move out electron-react-boilerplate, and use Vite)
const res = await got(url, { responseType: 'json', headers: this.baseHeaders });
return { data: res.body as T, headers: res.headers };
} catch (err) {
log.error(`Failed to get JSON from URL: ${url}`, err);
throw err;
}
log.error(`IPv4 and IPv6 requests failed for URL: ${url}`);
this.preferredFamily = undefined;
throw new Error(`IPv4 and IPv6 requests failed for URL: ${url}`);
}
public downloadFile(
@@ -58,63 +47,47 @@ export class RequestService {
): Observable<Progression<string>> {
return new Observable<Progression<string>>((subscriber) => {
const progress: Progression<string> = { current: 0, total: 0 };
const familiesToTry = this.preferredFamily ? [this.preferredFamily, this.preferredFamily === 4 ? 6 : 4] : [4, 6];
let attempt = 0;
let stream: got.GotEmitter & internal.Duplex;
let file: WriteStream | undefined;
const tryNextFamily = () => {
if (attempt >= familiesToTry.length) {
subscriber.error(new Error(`Download failed over IPv4 and IPv6 for URL: ${url}`));
return;
// @ts-ignore (ESM is not well supported in this project, We need to move out electron-react-boilerplate, and use Vite)
const stream = got.stream(url, { headers: this.baseHeaders });
stream.on('response', (response) => {
const filename = opt?.preferContentDisposition ? this.getFilenameFromContentDisposition(response.headers['content-disposition']) : null;
if (filename) {
dest = path.join(path.dirname(dest), sanitize(filename));
}
const family = familiesToTry[attempt++];
let file: WriteStream | undefined;
progress.data = dest;
file = createWriteStream(dest);
// @ts-ignore (ESM is not well supported in this project, We need to move out electron-react-boilerplate, and use Vite)
stream = got.stream(url, { dnsLookupIpVersion: family, headers: this.baseHeaders });
stream.on('response', (response) => {
this.preferredFamily = family;
const filename = opt?.preferContentDisposition ? this.getFilenameFromContentDisposition(response.headers['content-disposition']) : null;
if (filename) {
dest = path.join(path.dirname(dest), sanitize(filename));
}
progress.data = dest;
file = createWriteStream(dest);
pipeline(stream, file).catch(err => {
file?.destroy();
tryit(() => deleteFileSync(dest));
subscriber.error(err);
});
});
stream.on('downloadProgress', ({ transferred, total }) => {
progress.current = transferred;
progress.total = total;
subscriber.next(progress);
});
stream.on('error', err => {
log.warn(`Download failed over IPv${family} for URL: ${url}`, err);
stream.destroy();
pipeline(stream, file).catch(err => {
file?.destroy();
tryNextFamily();
tryit(() => deleteFileSync(dest));
subscriber.error(err);
});
});
stream.on('end', () => {
file?.end();
subscriber.next(progress);
subscriber.complete();
});
};
stream.on('downloadProgress', ({ transferred, total }) => {
progress.current = transferred;
progress.total = total;
subscriber.next(progress);
});
tryNextFamily();
stream.on('error', err => {
log.error(`Download failed for URL: ${url}`, err);
stream.destroy();
file?.destroy();
});
stream.on('end', () => {
file?.end();
subscriber.next(progress);
subscriber.complete();
});
return () => {
stream?.destroy();
@@ -129,6 +102,7 @@ export class RequestService {
url: string,
options?: got.GotOptions<null>
): Observable<Progression<Buffer, IncomingMessage>> {
return new Observable<Progression<Buffer, IncomingMessage>>((subscriber) => {
const progress: Progression<Buffer, IncomingMessage> = {
current: 0,
@@ -137,54 +111,38 @@ export class RequestService {
};
const headers = { ...this.baseHeaders, ...(options?.headers ?? {}) };
const familiesToTry = this.preferredFamily ? [this.preferredFamily, this.preferredFamily === 4 ? 6 : 4] : [4, 6];
let attempt = 0;
let stream: got.GotEmitter & internal.Duplex;
// @ts-ignore (ESM is not well supported in this project, We need to move out electron-react-boilerplate, and use Vite)
const stream = got.stream(url, { ...(options ?? {}), headers });
const tryNextFamily = () => {
if (attempt >= familiesToTry.length) {
subscriber.error(new Error(`Download failed over IPv4 and IPv6 for URL: ${url}`));
return;
}
let data = Buffer.alloc(0);
let response: IncomingMessage;
const family = familiesToTry[attempt++];
// @ts-ignore (ESM is not well supported in this project, We need to move out electron-react-boilerplate, and use Vite)
stream = got.stream(url, { dnsLookupIpVersion: family, ...(options ?? {}), headers });
stream.once('response', (res) => {
response = res;
});
let data = Buffer.alloc(0);
let response: IncomingMessage;
stream.on('data', (chunk: Buffer) => {
data = Buffer.concat([data, chunk]);
});
stream.once('response', (res) => {
this.preferredFamily = family;
response = res;
});
stream.on('downloadProgress', ({ transferred, total }) => {
progress.current = transferred;
progress.total = total;
subscriber.next(progress);
});
stream.on('data', (chunk: Buffer) => {
data = Buffer.concat([data, chunk]);
});
stream.on('error', err => {
log.error(`Download failed for URL: ${url}`, err);
stream.destroy();
});
stream.on('downloadProgress', ({ transferred, total }) => {
progress.current = transferred;
progress.total = total;
subscriber.next(progress);
});
stream.on('error', err => {
log.warn(`Download failed over IPv${family} for URL: ${url}`, err);
stream.destroy();
tryNextFamily();
});
stream.on('end', () => {
progress.data = data;
progress.extra = response;
subscriber.next(progress);
subscriber.complete();
});
};
tryNextFamily();
stream.on('end', () => {
progress.data = data;
progress.extra = response;
subscriber.next(progress);
subscriber.complete();
});
return () => {
stream?.destroy();