Android build v1.0.50
This commit is contained in:
+66
-3
@@ -1,4 +1,5 @@
|
||||
import starterProfile from './offline-starter-profile.json'
|
||||
import { bundledCatalogHash } from './offline-catalog-meta'
|
||||
import type {
|
||||
Account,
|
||||
AuthSession,
|
||||
@@ -82,6 +83,12 @@ type OnlineCache = {
|
||||
dirty: boolean
|
||||
}
|
||||
|
||||
type CatalogCache = {
|
||||
version: 1
|
||||
hash: string
|
||||
profile: CharacterProfile
|
||||
}
|
||||
|
||||
export type CloudSyncStatus = {
|
||||
available: boolean
|
||||
dirty: boolean
|
||||
@@ -102,6 +109,8 @@ type LocalSaveStore = {
|
||||
const modeKey = 'chronicle.repositoryMode'
|
||||
const offlineSaveKey = 'chronicle.offlineSave.v1'
|
||||
const onlineCacheKey = 'chronicle.onlineCache.v1'
|
||||
const catalogCacheKey = 'chronicle.catalog.v1'
|
||||
const catalogBundleKey = 'chronicle.catalog.bundleHash.v1'
|
||||
const authTokenKey = 'chronicle.authToken.v1'
|
||||
const offlineAccount = { id: -1, username: 'Offline' }
|
||||
const ABILITY_SLOT_COUNT = 6
|
||||
@@ -281,8 +290,42 @@ function clearOnlineCache() {
|
||||
localStorage.removeItem(onlineCacheKey)
|
||||
}
|
||||
|
||||
function bundledCatalog(): CatalogCache {
|
||||
return {
|
||||
version: 1,
|
||||
hash: bundledCatalogHash,
|
||||
profile: starterProfile as CharacterProfile,
|
||||
}
|
||||
}
|
||||
|
||||
function readCatalogCache(): CatalogCache | null {
|
||||
if (localStorage.getItem(catalogBundleKey) !== bundledCatalogHash) {
|
||||
localStorage.removeItem(catalogCacheKey)
|
||||
localStorage.setItem(catalogBundleKey, bundledCatalogHash)
|
||||
return null
|
||||
}
|
||||
const serialized = localStorage.getItem(catalogCacheKey)
|
||||
if (!serialized) return null
|
||||
try {
|
||||
const raw = JSON.parse(serialized) as CatalogCache
|
||||
if (raw.version !== 1 || typeof raw.hash !== 'string' || !raw.profile) return null
|
||||
return raw
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function writeCatalogCache(cache: CatalogCache) {
|
||||
localStorage.setItem(catalogBundleKey, bundledCatalogHash)
|
||||
localStorage.setItem(catalogCacheKey, JSON.stringify(cache))
|
||||
}
|
||||
|
||||
function activeCatalog(): CatalogCache {
|
||||
return readCatalogCache() ?? bundledCatalog()
|
||||
}
|
||||
|
||||
function buildProfile(save: OfflineSave): CharacterProfile {
|
||||
const static_ = clone(starterProfile) as CharacterProfile
|
||||
const static_ = clone(activeCatalog().profile)
|
||||
const cd = save.characters[save.activeClassId]
|
||||
const gameClass = static_.classes.find((c) => c.id === save.activeClassId)!
|
||||
|
||||
@@ -657,6 +700,23 @@ function isNetworkError(reason: unknown): reason is NetworkError {
|
||||
return reason instanceof Error && Boolean((reason as NetworkError).network)
|
||||
}
|
||||
|
||||
async function loadServerCatalog(): Promise<CatalogCache> {
|
||||
return requestJson('/api/catalog')
|
||||
}
|
||||
|
||||
async function refreshCatalogFromServer(): Promise<CatalogCache | null> {
|
||||
try {
|
||||
const catalog = await loadServerCatalog()
|
||||
if (catalog.version !== 1 || !catalog.hash || !catalog.profile) return null
|
||||
if (catalog.hash !== activeCatalog().hash || !readCatalogCache()) {
|
||||
writeCatalogCache(catalog)
|
||||
}
|
||||
return catalog
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function cachedOnlineSession(): AuthSession | null {
|
||||
const cache = readOnlineCache()
|
||||
if (!cache) return null
|
||||
@@ -698,6 +758,7 @@ async function pushServerSyncSave(save: OfflineSave): Promise<{ profile: Charact
|
||||
async function finalizeOnlineSession(session: AuthSession): Promise<AuthSession> {
|
||||
const cache = readOnlineCache()
|
||||
if (session.token) writeAuthToken(session.token)
|
||||
await refreshCatalogFromServer()
|
||||
if (!session.account || !session.profile) {
|
||||
if (session.account && cache?.account.id === session.account.id) {
|
||||
return {
|
||||
@@ -848,7 +909,7 @@ const serverRepository: GameRepository = {
|
||||
}
|
||||
|
||||
function emptyCharacterData(classId: number): CharacterData {
|
||||
const static_ = clone(starterProfile) as CharacterProfile
|
||||
const static_ = clone(activeCatalog().profile)
|
||||
const gc = static_.classes.find((c) => c.id === classId)!
|
||||
const talentRanks: Record<string, number> = {}
|
||||
for (const t of gc.talents) talentRanks[String(t.id)] = 0
|
||||
@@ -1544,7 +1605,9 @@ export async function syncCloudSave(): Promise<CharacterProfile> {
|
||||
if (!cache) {
|
||||
throw new Error('No signed-in save is available for cloud sync.')
|
||||
}
|
||||
await refreshCatalogFromServer()
|
||||
const synced = await pushServerSyncSave(cache.save)
|
||||
await refreshCatalogFromServer()
|
||||
writeOnlineCache({
|
||||
version: 1,
|
||||
account: cache.account,
|
||||
@@ -1552,7 +1615,7 @@ export async function syncCloudSave(): Promise<CharacterProfile> {
|
||||
dirty: false,
|
||||
})
|
||||
writeMode('online')
|
||||
return synced.profile
|
||||
return buildProfile(synced.save)
|
||||
}
|
||||
|
||||
export function selectOnlineMode() {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export const bundledCatalogHash = '07506c52bab428c439f09a9b82e39e6eff2b243fb972d664da48852059bcd937'
|
||||
Reference in New Issue
Block a user