Added catalogue version to make webpages not need to repeatedly fetch

info from API.
This commit is contained in:
sarah committed 2026-08-31 20:01:56 +02:00
1 parent dde47ff5a6
commit ce6dced3ad
6 files changed
+374 -85

No files matched your search

+1
View File
@@ -135,6 +135,7 @@ $PageSection = "Garden";
<script src="/Scripts/Garden/Plants.js"></script> <script src="/Scripts/Garden/Plants.js"></script>
<script src="/Scripts/Garden/Mutations.js"></script> <script src="/Scripts/Garden/Mutations.js"></script>
<script src="/Scripts/Garden/Save.js"></script> <script src="/Scripts/Garden/Save.js"></script>
<script src="/Scripts/Garden/Content.js"></script>
<script src="/Scripts/Garden/Economy.js"></script> <script src="/Scripts/Garden/Economy.js"></script>
<script src="/Scripts/Garden/MutationEngine.js"></script> <script src="/Scripts/Garden/MutationEngine.js"></script>
<script src="/Scripts/Garden/Users.js"></script> <script src="/Scripts/Garden/Users.js"></script>
+1
View File
@@ -293,6 +293,7 @@ $PageTitle = "Profile";
<script src="/Scripts/Garden/Plants.js"></script> <script src="/Scripts/Garden/Plants.js"></script>
<script src="/Scripts/Garden/Mutations.js"></script> <script src="/Scripts/Garden/Mutations.js"></script>
<script src="/Scripts/Garden/Save.js"></script> <script src="/Scripts/Garden/Save.js"></script>
<script src="/Scripts/Garden/Content.js"></script>
<script src="/Scripts/Garden/Users.js"></script> <script src="/Scripts/Garden/Users.js"></script>
<script src="/Scripts/Garden/Profile.js"></script> <script src="/Scripts/Garden/Profile.js"></script>
<script src="/Scripts/Garden/Account.js"></script> <script src="/Scripts/Garden/Account.js"></script>
+1
View File
@@ -61,6 +61,7 @@ $PageSection = "Shop";
<script src="/Scripts/Garden/Plants.js"></script> <script src="/Scripts/Garden/Plants.js"></script>
<script src="/Scripts/Garden/Mutations.js"></script> <script src="/Scripts/Garden/Mutations.js"></script>
<script src="/Scripts/Garden/Save.js"></script> <script src="/Scripts/Garden/Save.js"></script>
<script src="/Scripts/Garden/Content.js"></script>
<script src="/Scripts/Garden/Economy.js"></script> <script src="/Scripts/Garden/Economy.js"></script>
<script src="/Scripts/Garden/Shop.js"></script> <script src="/Scripts/Garden/Shop.js"></script>
</body> </body>
+361
View File
@@ -0,0 +1,361 @@
const GameContentCacheName =
"SarahtoninGardenContent";
const GameContentCacheSchemaVersion = 1;
/*
* Once content has been checked, page loads for the next
* five minutes can use the local cache without any API call.
* After that, only the tiny ContentVersion endpoint is needed
* unless the catalogue has actually changed.
*/
const GameContentVersionCheckInterval =
5 * 60 * 1000;
let GameContentLoadPromise = null;
async function LoadGameContent() {
if (GameContentLoadPromise === null) {
GameContentLoadPromise =
LoadGameContentInternal();
}
try {
return await GameContentLoadPromise;
} catch (Error) {
GameContentLoadPromise = null;
throw Error;
}
}
async function LoadGameContentInternal() {
const Cache =
ReadGameContentCache();
const Now = Date.now();
if (
Cache !== null &&
Now - Cache.CheckedAt <
GameContentVersionCheckInterval
) {
ApplyGameContent(
Cache.Plants,
Cache.Mutations
);
return GetLoadedGameContent();
}
try {
const ContentVersion =
await FetchGameContentVersion();
if (
Cache !== null &&
Cache.ContentVersion ===
ContentVersion
) {
Cache.CheckedAt = Now;
WriteGameContentCache(
Cache
);
ApplyGameContent(
Cache.Plants,
Cache.Mutations
);
return GetLoadedGameContent();
}
return await RefreshGameContent(
ContentVersion
);
} catch (VersionError) {
console.warn(
"Couldn't check Garden content version. " +
"Refreshing the catalogues directly.",
VersionError
);
try {
return await RefreshGameContent(
null
);
} catch (RefreshError) {
if (Cache !== null) {
console.warn(
"Couldn't refresh Garden content. " +
"Using the last cached catalogue.",
RefreshError
);
ApplyGameContent(
Cache.Plants,
Cache.Mutations
);
return GetLoadedGameContent();
}
throw RefreshError;
}
}
}
async function FetchGameContentVersion() {
const Response =
await fetch(
ApiUrl +
"/ContentVersion.php",
{
method: "GET",
headers: {
"Accept":
"application/json"
},
cache: "no-store"
}
);
if (!Response.ok) {
throw new Error(
"Content version API returned HTTP " +
Response.status
);
}
const Result =
await Response.json();
if (
!Result.Success ||
typeof Result.Version !==
"string" ||
Result.Version.length === 0
) {
throw new Error(
Result.Error ??
"Content version API returned invalid data."
);
}
return Result.Version;
}
async function RefreshGameContent(
ContentVersion
) {
const [
FreshPlants,
FreshMutations
] =
await Promise.all([
FetchPlantsFromApi(),
FetchMutationsFromApi()
]);
const Cache = {
SchemaVersion:
GameContentCacheSchemaVersion,
ContentVersion:
ContentVersion,
CheckedAt:
Date.now(),
Plants:
FreshPlants,
Mutations:
FreshMutations
};
WriteGameContentCache(
Cache
);
ApplyGameContent(
FreshPlants,
FreshMutations
);
return GetLoadedGameContent();
}
function ApplyGameContent(
LoadedPlants,
LoadedMutations
) {
Plants = LoadedPlants;
MutationSets = LoadedMutations;
}
function GetLoadedGameContent() {
return {
Plants: Plants,
Mutations: MutationSets
};
}
function ReadGameContentCache() {
let RawCache;
try {
RawCache =
localStorage.getItem(
GameContentCacheName
);
} catch (Error) {
console.warn(
"Couldn't read the Garden content cache.",
Error
);
return null;
}
if (RawCache === null) {
return null;
}
try {
const Cache =
JSON.parse(
RawCache
);
if (
!IsValidGameContentCache(
Cache
)
) {
RemoveGameContentCache();
return null;
}
return Cache;
} catch (Error) {
RemoveGameContentCache();
return null;
}
}
function IsValidGameContentCache(
Cache
) {
return (
Cache !== null &&
typeof Cache === "object" &&
!Array.isArray(Cache) &&
Cache.SchemaVersion ===
GameContentCacheSchemaVersion &&
Number.isFinite(
Cache.CheckedAt
) &&
Cache.CheckedAt >= 0 &&
(
Cache.ContentVersion ===
null ||
typeof Cache.ContentVersion ===
"string"
) &&
IsGameContentObject(
Cache.Plants
) &&
IsGameContentObject(
Cache.Mutations
)
);
}
function IsGameContentObject(
Value
) {
return (
Value !== null &&
typeof Value === "object" &&
!Array.isArray(Value)
);
}
function WriteGameContentCache(
Cache
) {
try {
localStorage.setItem(
GameContentCacheName,
JSON.stringify(
Cache
)
);
} catch (Error) {
console.warn(
"Couldn't write the Garden content cache.",
Error
);
}
}
function RemoveGameContentCache() {
try {
localStorage.removeItem(
GameContentCacheName
);
} catch (Error) {
console.warn(
"Couldn't clear the Garden content cache.",
Error
);
}
}
function ClearGameContentCache() {
RemoveGameContentCache();
GameContentLoadPromise = null;
}
+5 -56
View File
@@ -1,30 +1,7 @@
let MutationSets = {}; let MutationSets = {};
let MutationsLoadPromise = null;
let GameContentLoadPromise = null;
async function FetchMutationsFromApi() {
async function LoadMutations() {
if (MutationsLoadPromise !== null) {
return MutationsLoadPromise;
}
MutationsLoadPromise =
LoadMutationsFromApi();
try {
return await MutationsLoadPromise;
} catch (Error) {
MutationsLoadPromise = null;
throw Error;
}
}
async function LoadMutationsFromApi() {
const Response = const Response =
await fetch( await fetch(
ApiUrl + "/Mutations.php", ApiUrl + "/Mutations.php",
@@ -34,7 +11,9 @@ async function LoadMutationsFromApi() {
headers: { headers: {
"Accept": "Accept":
"application/json" "application/json"
} },
cache: "no-store"
} }
); );
@@ -67,35 +46,5 @@ async function LoadMutationsFromApi() {
} }
MutationSets = return Result.Mutations;
Result.Mutations;
return MutationSets;
}
async function LoadGameContent() {
if (GameContentLoadPromise === null) {
GameContentLoadPromise =
Promise.all([
LoadPlants(),
LoadMutations()
]);
}
try {
await GameContentLoadPromise;
} catch (Error) {
GameContentLoadPromise = null;
throw Error;
}
return {
Plants: Plants,
Mutations: MutationSets
};
} }
+5 -29
View File
@@ -1,29 +1,7 @@
let Plants = {}; let Plants = {};
let PlantsLoadPromise = null;
async function FetchPlantsFromApi() {
async function LoadPlants() {
if (PlantsLoadPromise !== null) {
return PlantsLoadPromise;
}
PlantsLoadPromise =
LoadPlantsFromApi();
try {
return await PlantsLoadPromise;
} catch (Error) {
PlantsLoadPromise = null;
throw Error;
}
}
async function LoadPlantsFromApi() {
const Response = const Response =
await fetch( await fetch(
ApiUrl + "/Plants.php", ApiUrl + "/Plants.php",
@@ -33,7 +11,9 @@ async function LoadPlantsFromApi() {
headers: { headers: {
"Accept": "Accept":
"application/json" "application/json"
} },
cache: "no-store"
} }
); );
@@ -66,9 +46,5 @@ async function LoadPlantsFromApi() {
} }
Plants = return Result.Plants;
Result.Plants;
return Plants;
} }