npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

game-session

v0.1.1

Published

Session modes, run and progress recording in localStorage, a module-level pause flag, a React hook, and pause-menu and settings UI for browser games.

Readme

game-session

CI npm MIT license

Session modes, run and progress recording in localStorage, a module-level pause flag, a React hook, and pause-menu and settings UI for browser games.

A game usually needs the same three things around its actual gameplay: a difficulty posture the player picked, somewhere to keep their progress between visits, and a pause menu that stops the world without losing it. This package is those three things, split so you can take one without the others.

  • game-session: pure logic. Session modes, run results, progress normalization, and a pause flag. No React, no DOM required.
  • game-session/react: a useGameRuntime hook plus plain functions over localStorage.
  • game-session/ui: a presentational pause menu, settings panel, and error boundary.

Full documentation: jbcom.github.io/game-session

Install

npm install game-session

react and lucide-react are optional peer dependencies. You only need them if you import the /react or /ui subpaths; the core entry point has no runtime dependencies at all.

npm install game-session react lucide-react

Compatibility

  • Node.js 22, 24 and 26 are supported (engines.node >=22, CI covers each maintained line); the published output targets ES2022 browsers.
  • React 19 for /react and /ui; lucide-react >=0.400.0 <2 for /ui.
  • Tailwind CSS in the app that renders /ui: the components are styled with utility classes and ship no stylesheet of their own.

The package ships native ESM and CommonJS entry points with format-correct types for both.

Session modes

Three postures, cozy / standard / challenge, each with tuning a game can read instead of inventing its own difficulty constants.

import { getSessionPressureScale, getSessionTuning } from "game-session";

const tuning = getSessionTuning("cozy");
// tuning.targetMinutes        -> [10, 18]
// tuning.mistakeRecoveryCount -> 4, how many mistakes to forgive
// tuning.pressureScale        -> 0.62, multiply your hazard rate by this

spawnHazards(baseRate * getSessionPressureScale("challenge"));

normalizeSessionMode accepts anything (a URL param, a stale stored value, undefined) and always returns a valid mode, so you never have to guard at the call site. DEFAULT_SESSION_TUNING is a starting point, not a straitjacket: games that need per-title tuning build their own Record<string, Record<SessionMode, SessionTuning>> on top of it.

Runs and progress

import { beginGameRun, finishGameRun, readGameProgress } from "game-session/react";

// Starts a run and writes the resume slot.
const { progress, slot } = beginGameRun("puzzle-quest", "standard");

// ...player plays...

const { result } = finishGameRun("puzzle-quest", {
  mode: "standard",
  status: "completed",
  score: 4200,
});

readGameProgress("puzzle-quest");
// -> { slug, bestScore, sessionsStarted, sessionsCompleted, totalPlayMs, ... }

finishGameRun folds the result into progress and clears the resume slot. abandonGameRun(slug) is the same path with status: "abandoned", and returns undefined when there was no run in flight.

Everything is stored under a key prefix you control. The default, DEFAULT_STORAGE_NAMESPACE, is "game-session:v1", which every app on one origin shares, so pass your own namespace to keep two apps from colliding. Reads normalize whatever they find, so a corrupted or older-shaped value degrades to defaults instead of throwing, and a storage failure (quota, disabled storage, server rendering) is swallowed rather than thrown.

Pausing

The pause flag is a module-level primitive with a DOM event behind it, so the part of your game that pauses does not need a reference to the part that owns the menu.

import { isGameRuntimePaused } from "game-session";

function frame(dt: number) {
  if (isGameRuntimePaused()) return;
  update(dt);
}

React

useGameRuntime(slug, options?) keeps settings, progress, and the resume slot in component state and gives you the run lifecycle already bound to that slug: beginRun, saveRun, updateRun, finishRun, abandonRun, clearRun, plus setSettings and setProgress.

import { useState } from "react";
import { setGameRuntimePaused } from "game-session";
import { useGameRuntime } from "game-session/react";
import { GamePauseMenu } from "game-session/ui";

function Game({ onExit }: { onExit: () => void }) {
  const { settings, saveSlot, setSettings, abandonRun } = useGameRuntime("puzzle-quest");
  const [paused, setPaused] = useState(false);

  return (
    <>
      <GameCanvas />
      <GamePauseMenu
        gameTitle="Puzzle Quest"
        open={paused}
        rules={["Match three tiles.", "Clear the board before time runs out."]}
        saveSlot={saveSlot}
        settings={settings}
        onMainMenu={onExit}
        onClose={() => {
          setGameRuntimePaused(false);
          setPaused(false);
        }}
        onRestart={() => restartRun()}
        onQuitRun={() => abandonRun()}
        onSettingsChange={setSettings}
      />
    </>
  );
}

Pause state is deliberately not owned by the hook: setGameRuntimePaused is a module-level flag so your render loop can read it without subscribing to React state. The /ui components are presentational and take their handlers as props; they own no routing and no persistence, so they drop into an existing shell.

API overview

| Export | Entry point | What it does | | --- | --- | --- | | SESSION_MODES, SessionMode | . | The three modes | | getSessionTuning, getSessionPressureScale, getSessionRecoveryScale | . | Per-mode tuning | | normalizeSessionMode | . | Coerce unknown input to a valid mode | | DEFAULT_SESSION_TUNING, DEFAULT_DIFFICULTY_VARIANTS | . | Defaults to build on | | createGameResult, recordGameResult | . | Build and fold in a run result | | createEmptyProgress, normalizeGameProgress, markProgressStarted | . | Progress shaping | | normalizeGameSettings, DEFAULT_GAME_SETTINGS | . | Settings shaping | | createActiveSaveSlot, updateActiveSaveSlot, normalizeGameSaveSlot | . | Save slots | | setGameRuntimePaused, isGameRuntimePaused, clearGameRuntimePaused | . | Pause flag | | useGameRuntime | ./react | The hook tying storage to component state | | beginGameRun, updateGameRun, finishGameRun, abandonGameRun | ./react | Run lifecycle | | readGameSettings, writeGameSettings | ./react | Settings persistence | | readGameProgress, writeGameProgress | ./react | Progress persistence | | readGameSaveSlot, writeGameSaveSlot, clearGameSaveSlot | ./react | Save-slot persistence | | applySettingsToDocument | ./react | Push settings onto the document | | GamePauseMenu, GameSettingsPanel, GameMenuButton | ./ui | Pause and settings UI | | RuntimeResultRecorder | ./ui | Records a result on mount | | GameErrorBoundary | ./ui | Error boundary with a return-to-menu fallback |

Every export is documented with TSDoc, and the signatures live in the API reference.

Documentation

License

MIT. See LICENSE.