Added deletion of users and export of data. GDPR..

This commit is contained in:
sarah committed 2026-09-04 18:02:15 +02:00
1 parent 1d8587744c
commit 2ea195475e
4 files changed
+335

No files matched your search

+34
View File
@@ -231,6 +231,40 @@ $PageSection = "Guide";
has it can load the same Garden account and mess stuff up.
</p>
</section>
<section
id="Privacy"
class="GuideSection"
>
<h2>Privacy and your data</h2>
<p>
The Garden API stores the data needed to keep your
account synchronized, including your Garden save,
username, profile colour, Dew totals, and the time
the server save was last updated. The server identifies
the account using a one-way hash derived from your
Account Key rather than storing the Account Key itself.
</p>
<p>
This browser stores your Account Key and a local copy
of your Garden save so the same account can keep working
between visits. If you set a username, some profile
information and statistics can also appear on the
public leaderboard.
</p>
<p>
The <a href="/Pages/Profile.html#Account">Profile</a>
page lets you export a JSON copy of the account data
stored on the server, or permanently delete the account.
Deleting removes the server account and clears the
Account Key and local save from the browser where you
perform the deletion. Other devices have their own local
browser storage and cannot be cleared remotely.
</p>
</section>
</main>
<script src="/Scripts/Garden/PlantImages.js"></script>
+42
View File
@@ -320,6 +320,48 @@ $PageTitle = "Profile";
></p>
</div>
</details>
<h3>Account data</h3>
<p>
Download a JSON copy of the Garden account
data currently stored on the server. The
Account Key is not included in this export.
</p>
<div class="ProfileButtonRow">
<button
id="ExportAccountDataButton"
type="button"
>
Export account data
</button>
</div>
<h3>Delete account</h3>
<p class="ProfileDangerText">
Permanently delete this Garden account from
the server and clear its Account Key and save
from this browser. This cannot be undone.
</p>
<div class="ProfileButtonRow">
<button
id="DeleteAccountButton"
class="ProfileDangerButton"
type="button"
>
Delete account
</button>
</div>
<p
id="AccountDataMessage"
class="ProfileMessage"
></p>
</section>
+245
View File
@@ -14,6 +14,16 @@ function StartAccount() {
"ExportAccountKeyButton"
);
const ExportDataButton =
document.getElementById(
"ExportAccountDataButton"
);
const DeleteAccountButton =
document.getElementById(
"DeleteAccountButton"
);
const ImportForm =
document.getElementById(
"ImportAccountForm"
@@ -45,6 +55,16 @@ function StartAccount() {
ExportAccountKey
);
ExportDataButton.addEventListener(
"click",
ExportAccountData
);
DeleteAccountButton.addEventListener(
"click",
DeleteAccount
);
ImportForm.addEventListener(
"submit",
SubmitAccountImport
@@ -272,6 +292,231 @@ function ExportAccountKey() {
}
async function ExportAccountData() {
const Message =
document.getElementById(
"AccountDataMessage"
);
const Button =
document.getElementById(
"ExportAccountDataButton"
);
Message.textContent =
"Preparing account data...";
Button.disabled = true;
try {
const Result =
await RequestAccountData(
"Export"
);
if (
Result.Exists !== true ||
Result.Data === undefined
) {
Message.textContent =
"No server account data exists to export.";
return;
}
const File =
new Blob(
[
JSON.stringify(
Result.Data,
null,
4
) + "\n"
],
{
type:
"application/json;charset=utf-8"
}
);
const FileUrl =
URL.createObjectURL(
File
);
const Link =
document.createElement(
"a"
);
Link.href = FileUrl;
Link.download =
"SarahtoninGardenAccountData.json";
document.body.appendChild(
Link
);
Link.click();
Link.remove();
URL.revokeObjectURL(
FileUrl
);
Message.textContent =
"Account data exported.";
} catch (Error) {
console.error(
"Couldn't export account data:",
Error
);
Message.textContent =
"Couldn't export account data right now.";
} finally {
Button.disabled = false;
}
}
async function DeleteAccount() {
const Message =
document.getElementById(
"AccountDataMessage"
);
const Button =
document.getElementById(
"DeleteAccountButton"
);
const Confirmation =
window.prompt(
"Permanently delete this Garden account?\n\n" +
"The server save and this browser's local " +
"Account Key/save will be removed. This cannot " +
"be undone.\n\n" +
"Type DELETE to continue."
);
if (Confirmation !== "DELETE") {
Message.textContent =
Confirmation === null
? "Account deletion cancelled."
: "Account deletion cancelled: type DELETE exactly.";
return;
}
Message.textContent =
"Deleting account...";
Button.disabled = true;
try {
await RequestAccountData(
"Delete",
{
Confirm: "DELETE"
}
);
localStorage.removeItem(
LocalSaveName
);
localStorage.removeItem(
SaveKeyName
);
Message.textContent =
"Account deleted. Returning home...";
window.location.replace(
"/"
);
} catch (Error) {
console.error(
"Couldn't delete account:",
Error
);
Message.textContent =
"Couldn't delete the account. Nothing was cleared locally.";
Button.disabled = false;
}
}
async function RequestAccountData(
Action,
ExtraData = {}
) {
const Response =
await fetch(
ApiUrl + "/AccountData.php",
{
method: "POST",
headers: {
"Content-Type":
"application/json"
},
body: JSON.stringify({
SaveKey: GetSaveKey(),
Action: Action,
...ExtraData
})
}
);
let Result;
try {
Result =
await Response.json();
} catch (Error) {
throw new Error(
"Account data API returned an invalid response."
);
}
if (
!Response.ok ||
Result.Success !== true
) {
throw new Error(
Result.Error ??
(
"Account data API returned HTTP " +
Response.status
)
);
}
return Result;
}
async function SubmitAccountImport(
Event
) {
+14
View File
@@ -973,6 +973,20 @@ input, button, textarea, select {
color: var(--Subtext1);
}
.ProfileDangerText {
color: var(--Red);
}
.ProfileSection .ProfileDangerButton {
color: var(--Red);
border-color: var(--Red);
}
.ProfileSection .ProfileDangerButton:hover {
color: var(--Red);
border-color: var(--Red);
}
.ProfileStatistics {
display: flex;
flex-direction: column;