mirror of
https://github.com/tobspr/shapez.io.git
synced 2025-06-13 13:04:03 +00:00
Moved arrow callback functions to named functions
Separated arrow callback functions into named functions to avoid re-creating the functions at runtime.
This commit is contained in:
parent
a04dff4d10
commit
bc32f2d21f
@ -161,47 +161,49 @@ export class ReadWriteProxy {
|
|||||||
|
|
||||||
// Reads the data asynchronously, fails if verify() fails
|
// Reads the data asynchronously, fails if verify() fails
|
||||||
readAsync() {
|
readAsync() {
|
||||||
// Start read request
|
|
||||||
return (
|
|
||||||
this.app.storage
|
|
||||||
.readFileAsync(this.filename)
|
|
||||||
// Decrypt data (if its encrypted)
|
|
||||||
// @ts-ignore
|
|
||||||
.then(rawData => {
|
|
||||||
if (rawData == null) {
|
|
||||||
// So, the file has not been found, use default data
|
|
||||||
return JSON.stringify(compressObject(this.getDefaultData()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rawData.startsWith(compressionPrefix)) {
|
/**
|
||||||
const decompressed = decompressX64(rawData.substr(compressionPrefix.length));
|
* Decompresses raw data through `decompressX64`
|
||||||
if (!decompressed) {
|
* Fails if decompression failed or
|
||||||
// LZ string decompression failure
|
* data is too small.
|
||||||
return Promise.reject("bad-content / decompression-failed");
|
* @param {string} data
|
||||||
}
|
* @returns {Promise<string>}
|
||||||
if (decompressed.length < 40) {
|
*/
|
||||||
// String too short
|
const decompressRaw = data => {
|
||||||
return Promise.reject("bad-content / payload-too-small");
|
const decompressed = decompressX64(data.substr(compressionPrefix.length));
|
||||||
}
|
if (!decompressed) return Promise.reject("bad-content / decompression-failed");
|
||||||
|
return Promise.resolve(decompressed);
|
||||||
|
};
|
||||||
|
|
||||||
// Compare stored checksum with actual checksum
|
/**
|
||||||
const checksum = decompressed.substring(0, 40);
|
* Verifies the checksum and returns the payload.
|
||||||
const jsonString = decompressed.substr(40);
|
* Fails if the payload is too small to contain the checksum
|
||||||
|
* or the checksums don't match.
|
||||||
|
*
|
||||||
|
* @param {string} data
|
||||||
|
* @returns {Promise<string>}
|
||||||
|
*/
|
||||||
|
const verifyChecksum = data => {
|
||||||
|
if (data.length < 40) return Promise.reject("bad-content / payload-too-small");
|
||||||
|
const checksum = data.substr(0, 40);
|
||||||
|
const payload = data.substr(40);
|
||||||
|
const saltedPayload = payload + salt;
|
||||||
|
const desiredChecksum = checksum.startsWith(CRC_PREFIX) ? computeCrc(saltedPayload) : sha1(saltedPayload);
|
||||||
|
if (desiredChecksum !== checksum) return Promise.reject(`bad-content / checksum-mismatch: ${desiredChecksum} vs ${checksum} `);
|
||||||
|
return Promise.resolve(payload);
|
||||||
|
};
|
||||||
|
|
||||||
const desiredChecksum = checksum.startsWith(CRC_PREFIX)
|
/**
|
||||||
? computeCrc(jsonString + salt)
|
* Tries to parse the JSON data.
|
||||||
: sha1(jsonString + salt);
|
* Logs and fails if the parser failed.
|
||||||
|
*
|
||||||
if (desiredChecksum !== checksum) {
|
* @param {string} jsonString
|
||||||
// Checksum mismatch
|
* @returns {Promise<any>}
|
||||||
return Promise.reject(
|
*/
|
||||||
"bad-content / checksum-mismatch: " + desiredChecksum + " vs " + checksum
|
const tryParseJSON = jsonString => {
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse JSON
|
|
||||||
try {
|
try {
|
||||||
return JSON.parse(jsonString);
|
const parsedData = JSON.parse(jsonString);
|
||||||
|
return Promise.resolve(parsedData);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
logger.error(
|
logger.error(
|
||||||
"Failed to parse file content of",
|
"Failed to parse file content of",
|
||||||
@ -214,88 +216,141 @@ export class ReadWriteProxy {
|
|||||||
);
|
);
|
||||||
throw new Error("invalid-serialized-data");
|
throw new Error("invalid-serialized-data");
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
}
|
/**
|
||||||
|
* Tries to decompress the data.
|
||||||
|
* Fails if data is not compressed
|
||||||
|
* and code is in production.
|
||||||
|
*
|
||||||
|
* @param {string} rawData
|
||||||
|
* @returns {Promise<string>}
|
||||||
|
*/
|
||||||
|
const tryDecompress = rawData => {
|
||||||
|
if (rawData.startsWith(compressionPrefix)) return decompressRaw(rawData);
|
||||||
|
if (!G_IS_DEV) return Promise.reject("bad-content / missing-compression");
|
||||||
|
return Promise.resolve(rawData);
|
||||||
|
};
|
||||||
|
|
||||||
if (!G_IS_DEV) {
|
/**
|
||||||
return Promise.reject("bad-content / missing-compression");
|
* Checks for errors during file read.
|
||||||
}
|
* If the file was not found, it will not fail
|
||||||
return rawData;
|
* and instead return default data.
|
||||||
})
|
*
|
||||||
// Check for errors during read
|
* @param {FILE_NOT_FOUND | string} err
|
||||||
.catch(err => {
|
* @returns {Promise<object>}
|
||||||
|
*/
|
||||||
|
const onReadError = err => {
|
||||||
if (err === FILE_NOT_FOUND) {
|
if (err === FILE_NOT_FOUND) {
|
||||||
logger.log("File not found, using default data");
|
logger.log("File not found, using default data");
|
||||||
|
|
||||||
// File not found or unreadable, assume default file
|
|
||||||
return Promise.resolve(this.getDefaultData());
|
return Promise.resolve(this.getDefaultData());
|
||||||
}
|
}
|
||||||
|
return Promise.reject(`file-error: ${err}`);
|
||||||
|
};
|
||||||
|
|
||||||
return Promise.reject("file-error: " + err);
|
/**
|
||||||
})
|
* Verifies basic structure of the data
|
||||||
|
* through `internalVerifyBasicStructure`.
|
||||||
|
*
|
||||||
|
* @param {any} contents
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
const verifyBasicStructure = contents => {
|
||||||
|
const verifyResult = this.internalVerifyBasicStructure(contents);
|
||||||
|
if (verifyResult.isBad()) return Promise.reject(`verify-failed: ${verifyResult.reason}`);
|
||||||
|
return Promise.resolve(contents);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the version and migrates if required.
|
||||||
|
* Fails if the version required for the data is newer
|
||||||
|
* or migration failed.
|
||||||
|
*
|
||||||
|
* @param {any} contents
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
const checkVersionAndMigrate = contents => {
|
||||||
|
const currentVersion = this.getCurrentVersion();
|
||||||
|
if (contents.version > currentVersion) return Promise.reject("stored-data-is-newer");
|
||||||
|
if (contents.version < currentVersion) {
|
||||||
|
logger.log(
|
||||||
|
`Trying to migrate data object from version ${contents.version} to ${currentVersion}`
|
||||||
|
);
|
||||||
|
const migrationResult = this.migrate(contents); // modify in place
|
||||||
|
if (migrationResult.isBad()) {
|
||||||
|
return Promise.reject(`migration-failed: ${migrationResult.reason}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Promise.resolve(contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies contents once they've been version-matched
|
||||||
|
* and migrated if needed, through `internalVerifyEntry`.
|
||||||
|
* Fails if such method fails.
|
||||||
|
*
|
||||||
|
* @param {any} contents
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
const verifyEntry = contents => {
|
||||||
|
const verifyResult = this.internalVerifyEntry(contents);
|
||||||
|
if (!verifyResult.result) {
|
||||||
|
logger.error(
|
||||||
|
`Read invalid data from ${this.filename}, reason: ${verifyResult.reason}, contents: ${contents}`
|
||||||
|
);
|
||||||
|
return Promise.reject(`invalid-data: ${verifyResult.reason}`);
|
||||||
|
}
|
||||||
|
return Promise.resolve(contents);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the verified data.
|
||||||
|
*
|
||||||
|
* @param {any} contents
|
||||||
|
* @returns {any}
|
||||||
|
*/
|
||||||
|
const storeContents = contents => {
|
||||||
|
this.currentData = contents;
|
||||||
|
logger.log("📄 Read data with version", this.currentData.version, "from", this.filename);
|
||||||
|
return contents;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic handler for any failure.
|
||||||
|
*
|
||||||
|
* @param {any} err
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
const genericOnFail = err => Promise.reject(`Failed to read ${this.filename}: ${err}`);
|
||||||
|
|
||||||
|
// Start read request
|
||||||
|
return (
|
||||||
|
this.app.storage
|
||||||
|
.readFileAsync(this.filename)
|
||||||
|
// Decrypt data (if its encrypted)
|
||||||
|
// @ts-ignore
|
||||||
|
.then(rawData => tryDecompress(rawData).then(verifyChecksum).then(tryParseJSON))
|
||||||
|
|
||||||
|
// Check for errors during read
|
||||||
|
.catch(onReadError)
|
||||||
|
|
||||||
// Decompress
|
// Decompress
|
||||||
.then(decompressObject)
|
.then(decompressObject)
|
||||||
|
|
||||||
// Verify basic structure
|
// Verify basic structure
|
||||||
.then(contents => {
|
.then(verifyBasicStructure)
|
||||||
const result = this.internalVerifyBasicStructure(contents);
|
|
||||||
if (!result.isGood()) {
|
|
||||||
return Promise.reject("verify-failed: " + result.reason);
|
|
||||||
}
|
|
||||||
return contents;
|
|
||||||
})
|
|
||||||
|
|
||||||
// Check version and migrate if required
|
// Check version and migrate if required
|
||||||
.then(contents => {
|
.then(checkVersionAndMigrate)
|
||||||
if (contents.version > this.getCurrentVersion()) {
|
|
||||||
return Promise.reject("stored-data-is-newer");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (contents.version < this.getCurrentVersion()) {
|
|
||||||
logger.log(
|
|
||||||
"Trying to migrate data object from version",
|
|
||||||
contents.version,
|
|
||||||
"to",
|
|
||||||
this.getCurrentVersion()
|
|
||||||
);
|
|
||||||
const migrationResult = this.migrate(contents); // modify in place
|
|
||||||
if (migrationResult.isBad()) {
|
|
||||||
return Promise.reject("migration-failed: " + migrationResult.reason);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return contents;
|
|
||||||
})
|
|
||||||
|
|
||||||
// Verify
|
// Verify
|
||||||
.then(contents => {
|
.then(verifyEntry)
|
||||||
const verifyResult = this.internalVerifyEntry(contents);
|
|
||||||
if (!verifyResult.result) {
|
|
||||||
logger.error(
|
|
||||||
"Read invalid data from",
|
|
||||||
this.filename,
|
|
||||||
"reason:",
|
|
||||||
verifyResult.reason,
|
|
||||||
"contents:",
|
|
||||||
contents
|
|
||||||
);
|
|
||||||
return Promise.reject("invalid-data: " + verifyResult.reason);
|
|
||||||
}
|
|
||||||
return contents;
|
|
||||||
})
|
|
||||||
|
|
||||||
// Store
|
// Store
|
||||||
.then(contents => {
|
.then(storeContents)
|
||||||
this.currentData = contents;
|
|
||||||
logger.log("📄 Read data with version", this.currentData.version, "from", this.filename);
|
|
||||||
return contents;
|
|
||||||
})
|
|
||||||
|
|
||||||
// Catchall
|
// Catchall
|
||||||
.catch(err => {
|
.catch(genericOnFail)
|
||||||
return Promise.reject("Failed to read " + this.filename + ": " + err);
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user