Repository navigation
Storage and Saved State
DataService is the persistence layer under RewardSystem. It serializes to JSON and writes through a storage object you can replace.
import { DataService } from 'dopaminejs';
const data = new DataService({
storage: window.sessionStorage, // default: localStorage, or memory if that is unavailable
prefix: 'myapp_' // default: 'dopamine_'
});The player is stored under <prefix>player, so dopamine_player by default.
| Method | Returns | Notes |
|---|---|---|
save(key, data) |
Promise<boolean> |
false on failure. The error is logged, not thrown. |
load(key, defaultValue = null) |
Promise<value> |
The default on a missing key or a parse error. |
clear(key) |
Promise<void> |
Removes the key. |
Any object with getItem, setItem and removeItem works. The methods may return values or promises.
const serverStorage = {
async getItem(key) {
const response = await fetch(`/api/progress/${key}`);
return response.ok ? response.text() : null;
},
async setItem(key, value) {
await fetch(`/api/progress/${key}`, { method: 'PUT', body: value });
},
async removeItem(key) {
await fetch(`/api/progress/${key}`, { method: 'DELETE' });
}
};
const rewards = new RewardSystem(new DataService({ storage: serverStorage }));With promise storage, writes go out one at a time in the order they were made, so an older value cannot land after a newer one. load waits for any write still in flight.
SoundManager reads its mute flag in the constructor, so it needs synchronous storage.
import { resolveStorage, createMemoryStorage } from 'dopaminejs';-
resolveStorage()returnslocalStorageif it exists and a test write succeeds. Otherwise it returns a memory store. This covers servers, workers, Safari private browsing and storage-blocked iframes. -
createMemoryStorage()returns a store backed by aMap. Nothing survives a reload. Useful in tests.
DataService calls resolveStorage() when you pass no storage.
The player lives in the browser, and the player can edit it. On load, RewardSystem checks every field:
-
xpthat is not a finite number of 0 or more becomes 0 -
levelis at least what the XP earns. A higher stored level is kept. - streak counts must be integers of 1 or more, and
lastPlayDatemust look likeYYYY-MM-DD - achievements and stats must be plain records, and keys such as
__proto__are dropped - stat values that are not finite numbers are dropped, except
totalPlaysandhighScore, which become 0 - a save from an older version gets the fields added since
This stops a bad save from crashing the app or reaching Object.prototype. It does not make the numbers honest. If a number matters, compute it on your server. See Security.
A full or blocked storage does not throw. RewardSystem emits save_failed and keeps the state in memory.
rewards.on('save_failed', ({ key }) => showBanner('Progress is not being saved'));await data.clear('player');
await rewards.init(); // loads a fresh default playerStart
Progression
Engine
Packages
Reference