-
-
Notifications
You must be signed in to change notification settings - Fork 449
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Offer option to upload logs to a pastebin
When displaying a game log, a new button to directly upload the log file to a pastebin service is now presented. This button will upload the log file to `https://0x0.st` and copy the resulting sharable URL into the user's clipboard. To alleviate privacy concerns, logs are (1) only kept valid for 24 hours and (2) can be deleted from within Heroic: In the "Settings" -> "Logs" tab specifically, a new button to view uploaded logs was added. It lists all logs which are still valid, the user can then open the sharable URL again or request file deletion Behind the scenes, two new Zustand states have been added: - `useGlobalState`, which is my attempt at slowly migrating the current Global State to Zustand. It holds global information (specifically, whether the log file upload dialog or the upload list dialog are open) - `useUploadedLogFiles`, holding data on uploaded log files. The Backend pushes updates to this using two new Frontend messages
- Loading branch information
Showing
18 changed files
with
651 additions
and
66 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
import { app } from 'electron' | ||
import { readFile } from 'fs/promises' | ||
import path from 'path' | ||
import { z } from 'zod' | ||
|
||
import { TypeCheckedStoreBackend } from '../electron_store' | ||
import { getLogFile } from './logfile' | ||
import { logError, logInfo, LogPrefix } from './logger' | ||
import { sendFrontendMessage } from '../main_window' | ||
|
||
import type { UploadedLogData } from 'common/types' | ||
|
||
const uploadedLogFileStore = new TypeCheckedStoreBackend('uploadedLogs', { | ||
cwd: 'store', | ||
name: 'uploadedLogs', | ||
accessPropertiesByDotNotation: false | ||
}) | ||
|
||
async function sendRequestToApi(formData: FormData, url = 'https://0x0.st') { | ||
return fetch(url, { | ||
body: formData, | ||
method: 'post', | ||
headers: { | ||
'User-Agent': `HeroicGamesLauncher/${app.getVersion()}` | ||
} | ||
}).catch((err) => { | ||
logError([`Failed to send data to 0x0.st:`, err], LogPrefix.LogUploader) | ||
return null | ||
}) | ||
} | ||
|
||
/** | ||
* Uploads the log file of a game / runner / Heroic to https://0x0.st | ||
* @param name See {@link UploadedLogData.name} | ||
* @param appNameOrRunner Used to get the log file path. See {@link getLogFile} | ||
* @returns `false` if an error occurred, otherwise the URL to the uploaded file and {@link UploadedLogData} | ||
*/ | ||
async function uploadLogFile( | ||
name: string, | ||
appNameOrRunner: string | ||
): Promise<false | [string, UploadedLogData]> { | ||
const fileLocation = getLogFile(appNameOrRunner) | ||
const filename = path.basename(fileLocation) | ||
|
||
const fileContents = await readFile(fileLocation) | ||
const fileBlob = new Blob([fileContents]) | ||
|
||
const formData = new FormData() | ||
formData.set('file', fileBlob, filename) | ||
formData.set('expires', '24') | ||
|
||
const response = await sendRequestToApi(formData) | ||
if (!response) return false | ||
|
||
const token = response.headers.get('X-Token') | ||
const responseText = (await response.text()).trim() | ||
const maybeUrl = z.string().url().safeParse(responseText) | ||
if (!response.ok || !token || !maybeUrl.success) { | ||
logError( | ||
[ | ||
`Failed to upload log file, response error code ${response.status} ${response.statusText}, text:`, | ||
responseText | ||
], | ||
LogPrefix.LogUploader | ||
) | ||
return false | ||
} | ||
|
||
const url = maybeUrl.data | ||
const uploadData: UploadedLogData = { | ||
name, | ||
token, | ||
uploadedAt: Date.now() | ||
} | ||
uploadedLogFileStore.set(url, uploadData) | ||
sendFrontendMessage('logFileUploaded', url, uploadData) | ||
logInfo(`Uploaded log file ${name} to ${url}`, LogPrefix.LogUploader) | ||
return [url, uploadData] | ||
} | ||
|
||
/** | ||
* Deletes a log file previously uploaded using {@link uploadLogFile} | ||
* @param url The URL to the uploaded log file | ||
* @returns Whether the file was deleted | ||
*/ | ||
async function deleteUploadedLogFile(url: string): Promise<boolean> { | ||
const logData = uploadedLogFileStore.get_nodefault(url) | ||
if (!logData) return false | ||
|
||
const formData = new FormData() | ||
formData.set('token', logData.token) | ||
formData.set('delete', '') | ||
|
||
const response = await sendRequestToApi(formData, url) | ||
if (!response) return false | ||
|
||
if (!response.ok) { | ||
logError( | ||
[ | ||
`Failed to delete log file upload, response error code ${response.status} ${response.statusText}, text:`, | ||
await response.text() | ||
], | ||
LogPrefix.LogUploader | ||
) | ||
return false | ||
} | ||
|
||
uploadedLogFileStore.delete(url) | ||
sendFrontendMessage('logFileUploadDeleted', url) | ||
logInfo([`Deleted log file ${logData.name} (${url})`], LogPrefix.LogUploader) | ||
|
||
return true | ||
} | ||
|
||
/** | ||
* Returns all log files which are still valid. Also deletes expired | ||
* ones from {@link uploadedLogFileStore} | ||
*/ | ||
async function getUploadedLogFiles(): Promise<Record<string, UploadedLogData>> { | ||
const allStoredLogs = uploadedLogFileStore.raw_store | ||
const validUploadedLogs: Record<string, UploadedLogData> = {} | ||
// Filter and delete expired logs | ||
for (const [key, value] of Object.entries(allStoredLogs)) { | ||
const timeDifferenceMs = Date.now() - value.uploadedAt | ||
const timeDifferenceHours = timeDifferenceMs / 1000 / 60 / 60 | ||
if (timeDifferenceHours >= 24) { | ||
uploadedLogFileStore.delete(key) | ||
} else { | ||
validUploadedLogs[key] = value | ||
} | ||
} | ||
return validUploadedLogs | ||
} | ||
|
||
export { uploadLogFile, deleteUploadedLogFile, getUploadedLogFiles } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.