{"id":44787547,"url":"https://github.com/faluciano/react-native-couch-kit","last_synced_at":"2026-03-02T05:23:07.409Z","repository":{"id":335967912,"uuid":"1146236770","full_name":"faluciano/react-native-couch-kit","owner":"faluciano","description":"React native libraries for hosting websocket games on TV hardware with controller client packages","archived":false,"fork":false,"pushed_at":"2026-02-26T03:42:03.000Z","size":559,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-26T08:07:14.025Z","etag":null,"topics":["android-tv","couch-kit","embedded-server","fire-tv","library","local-multiplayer","party-game","react","react-native","real-time","tv-game","websocket"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/@couch-kit/host","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/faluciano.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-30T20:01:08.000Z","updated_at":"2026-02-26T03:42:07.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/faluciano/react-native-couch-kit","commit_stats":null,"previous_names":["faluciano/react-native-party-kit","faluciano/react-native-couch-kit"],"tags_count":105,"template":false,"template_full_name":null,"purl":"pkg:github/faluciano/react-native-couch-kit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faluciano%2Freact-native-couch-kit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faluciano%2Freact-native-couch-kit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faluciano%2Freact-native-couch-kit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faluciano%2Freact-native-couch-kit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/faluciano","download_url":"https://codeload.github.com/faluciano/react-native-couch-kit/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faluciano%2Freact-native-couch-kit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29992286,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-02T01:47:34.672Z","status":"online","status_checked_at":"2026-03-02T02:00:07.342Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android-tv","couch-kit","embedded-server","fire-tv","library","local-multiplayer","party-game","react","react-native","real-time","tv-game","websocket"],"created_at":"2026-02-16T10:31:28.284Z","updated_at":"2026-03-02T05:23:07.378Z","avatar_url":"https://github.com/faluciano.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🎮 Couch Kit\n\nTurn an Android TV / Fire TV into a local party-game console and use phones as web controllers.\n\n[![CI](https://github.com/faluciano/react-native-couch-kit/actions/workflows/ci.yml/badge.svg)](https://github.com/faluciano/react-native-couch-kit/actions/workflows/ci.yml)\n[![Release](https://github.com/faluciano/react-native-couch-kit/actions/workflows/release.yml/badge.svg)](https://github.com/faluciano/react-native-couch-kit/actions/workflows/release.yml)\n![License](https://img.shields.io/badge/license-MIT-blue.svg)\n![TypeScript](https://img.shields.io/badge/TypeScript-Strict-green.svg)\n\n[![@couch-kit/host](https://img.shields.io/npm/dt/@couch-kit/host?label=%40couch-kit%2Fhost)](https://www.npmjs.com/package/@couch-kit/host)\n[![@couch-kit/client](https://img.shields.io/npm/dt/@couch-kit/client?label=%40couch-kit%2Fclient)](https://www.npmjs.com/package/@couch-kit/client)\n[![@couch-kit/core](https://img.shields.io/npm/dt/@couch-kit/core?label=%40couch-kit%2Fcore)](https://www.npmjs.com/package/@couch-kit/core)\n[![@couch-kit/cli](https://img.shields.io/npm/dt/@couch-kit/cli?label=%40couch-kit%2Fcli)](https://www.npmjs.com/package/@couch-kit/cli)\n\n---\n\n## ✨ Features\n\n- **Local-first:** TV runs HTTP (controller) + WebSocket (game) on your LAN.\n- **TV-as-server:** Single source of truth lives on the TV.\n- **Shared reducer:** One reducer shared between host + controller.\n- **Time sync + preloading:** Helpers for timing-sensitive games and heavy assets.\n- **Session recovery:** Players automatically get their state back after refreshing or reconnecting.\n- **Dev workflow:** Iterate on the controller without constantly rebuilding the TV app.\n\n## How It Works\n\n- TV runs a static file server (default `:8080`) and a WebSocket game server (default `:8082`).\n- Phones open the controller page, connect via WebSocket, send actions, and receive state updates.\n\n## Prerequisites / Supported\n\n- **Devices:** Android TV / Fire TV (host). Phones run any modern mobile browser (client).\n- **Network:** TV + phones on the same LAN/Wi-Fi. This is not an internet relay.\n- **Ports:** `8080` (HTTP) and `8082` (WebSocket) reachable on the LAN (configurable).\n- **Native deps:** `@couch-kit/host` requires Expo modules and React Native native modules; it is not a pure-JS package.\n\n## Non-goals\n\n- Internet matchmaking / relay servers\n- Anti-cheat, account systems, payments\n- Hard security guarantees on untrusted networks\n\n---\n\n## 🚀 Usage Guide (Published Library)\n\n\u003e **Starter Project:** The fastest way to get started is to clone the [Buzz](https://github.com/faluciano/buzz-tv-party-game) starter project — a fully working buzzer game that demonstrates the complete `@couch-kit` setup (shared reducer, TV host, phone controller, build pipeline). Use it as a starting point for your own game.\n\nThis guide assumes you are using the published `@couch-kit/*` packages from npm.\n\n### 1. Installation\n\nCreate a new project and install the library:\n\n```bash\n# Initialize a new repository\nmkdir my-party-game\ncd my-party-game\nbun init\n\n# For the TV App (Host)\nbun add @couch-kit/host @couch-kit/core\n\n# Install required peer dependencies\nnpx expo install expo-file-system expo-network\nbun add react-native-tcp-socket react-native-nitro-modules\n```\n\nIf you are setting up the Web Controller manually (instead of using the CLI in Step 4):\n\n```bash\n# For the Web Controller (Client)\nbun add @couch-kit/client @couch-kit/core\n```\n\n### 2. The Game Logic (Shared)\n\nDefine your game state and actions in a shared file (e.g., `shared/types.ts`). This ensures both your TV Host and Web Controller agree on the rules.\n\n```typescript\nimport { IGameState, IAction } from \"@couch-kit/core\";\n\nexport interface GameState extends IGameState {\n  score: number;\n}\n\n// Only define your own game actions.\n// System actions (HYDRATE, PLAYER_JOINED, PLAYER_LEFT, PLAYER_RECONNECTED,\n// PLAYER_REMOVED) are handled automatically by createGameReducer.\nexport type GameAction = { type: \"BUZZ\" } | { type: \"RESET\" };\n\nexport const initialState: GameState = {\n  status: \"lobby\",\n  players: {}, // Managed automatically\n  score: 0,\n};\n\n// Your reducer only handles your own actions.\n// Player tracking and state hydration are handled by the framework.\nexport const gameReducer = (\n  state: GameState,\n  action: GameAction,\n): GameState =\u003e {\n  switch (action.type) {\n    case \"BUZZ\":\n      return { ...state, score: state.score + 1 };\n    case \"RESET\":\n      return { ...state, score: 0 };\n    default:\n      return state;\n  }\n};\n```\n\n### 3. The Host (TV App)\n\nIn your React Native TV app (using `react-native-tvos` or Expo with TV config):\n\n```tsx\nimport { GameHostProvider, useGameHost } from \"@couch-kit/host\";\nimport { gameReducer, initialState } from \"./shared/types\";\nimport { Text, View } from \"react-native\";\n\nexport default function App() {\n  return (\n    \u003cGameHostProvider config={{ reducer: gameReducer, initialState }}\u003e\n      \u003cGameScreen /\u003e\n    \u003c/GameHostProvider\u003e\n  );\n}\n```\n\n\u003e **Tip:** On Android, APK-bundled assets live inside a zip archive and cannot be served directly. Use the `staticDir` config option to point to a writable filesystem path where you've extracted the `www/` assets at runtime. See the [Buzz starter](https://github.com/faluciano/buzz-tv-party-game) for a working example with `useExtractAssets()`.\n\n```tsx\nfunction GameScreen() {\n  const { state, serverUrl, serverError } = useGameHost();\n\n  return (\n    \u003cView\u003e\n      {serverError \u0026\u0026 \u003cText\u003eServer error: {String(serverError.message)}\u003c/Text\u003e}\n      \u003cText\u003eOpen on phone: {serverUrl}\u003c/Text\u003e\n      \u003cText\u003eScore: {state.score}\u003c/Text\u003e\n    \u003c/View\u003e\n  );\n}\n```\n\n### 4. The Client (Web Controller)\n\nScaffold a web controller for players to run on their phones:\n\n```bash\nbunx couch-kit init web-controller\n```\n\nIn `web-controller/src/App.tsx`:\n\n```tsx\nimport { useGameClient } from \"@couch-kit/client\";\nimport { gameReducer, initialState } from \"../../shared/types\";\n\nexport default function Controller() {\n  const { state, sendAction } = useGameClient({\n    reducer: gameReducer,\n    initialState,\n  });\n\n  return (\n    \u003cbutton onClick={() =\u003e sendAction({ type: \"BUZZ\" })}\u003e\n      BUZZ! (Score: {state.score})\n    \u003c/button\u003e\n  );\n}\n```\n\n## Contracts (Read This Once)\n\n- **System actions are automatic:** The framework uses internal action types (`__HYDRATE__`, `__PLAYER_JOINED__`, `__PLAYER_LEFT__`, `__PLAYER_RECONNECTED__`, `__PLAYER_REMOVED__`) under the hood. These are handled automatically by `createGameReducer` -- you do **not** need to handle them in your reducer.\n- **State updates:** The host broadcasts full state snapshots. The client applies them automatically via hydration.\n- **Session recovery is automatic:** When a player refreshes or reconnects, the library restores their previous player data automatically. Player IDs are stable across reconnections — the same device always gets the same `playerId`. Disconnected players are cleaned up after a timeout (default: 5 minutes).\n- **Dev-mode WebSocket:** if the controller is served from your laptop (Vite), `useGameClient()` will try to connect WS to the laptop by default. In dev, pass `url: \"ws://TV_IP:8082\"`.\n\n## Dev Workflow (Controller on Laptop)\n\nOn the TV host:\n\n```tsx\n\u003cGameHostProvider\n  config={{\n    reducer: gameReducer,\n    initialState,\n    devMode: true,\n    devServerUrl: \"http://192.168.1.50:5173\",\n  }}\n\u003e\n  \u003cGameScreen /\u003e\n\u003c/GameHostProvider\u003e\n```\n\nOn the controller (served from the laptop), explicitly point WS to the TV:\n\n```ts\nuseGameClient({\n  reducer: gameReducer,\n  initialState,\n  url: \"ws://192.168.1.99:8082\", // TV IP\n});\n```\n\n---\n\n## 🛠️ Contributing / Local Development\n\nIf you want to contribute to `couch-kit` or test changes locally before they are published, follow these steps.\n\n### 1. Setup the Monorepo\n\nClone the repository and install dependencies:\n\n```bash\ngit clone \u003cthis-repo\u003e\ncd couch-kit\nbun install\n```\n\n### 2. Building the Libraries\n\nThe packages (`host`, `client`, `core`, `cli`) are located in `packages/*`. You can build them all at once:\n\n```bash\nbun run build\n```\n\nOr individually:\n\n```bash\nbun run --filter @couch-kit/host build\n```\n\n### 3. Running Tests\n\nRun all tests across all packages:\n\n```bash\nbun run test\n```\n\nRun linting and type checking:\n\n```bash\nbun run lint\nbun run typecheck\n```\n\n### 4. Code Style\n\nThe project uses [Prettier](https://prettier.io/) for formatting (configured in `.prettierrc`) and [ESLint](https://eslint.org/) for linting.\n\n### 5. Testing in a Real App (Yalc)\n\nTo test your local changes in a real React Native app, we recommend using `yalc`. It simulates a published package by copying build artifacts directly into your project, avoiding common Metro Bundler symlink issues.\n\n**First, publish local versions:**\n\n```bash\n# In the root of couch-kit\nbun global add yalc\nbun run build\n\n# Publish each package to local yalc registry\ncd packages/core \u0026\u0026 yalc publish\ncd ../client \u0026\u0026 yalc publish\ncd ../host \u0026\u0026 yalc publish\n```\n\n**Then, link them in your consumer app:**\n\n```bash\ncd ../my-party-game\nyalc add @couch-kit/core @couch-kit/client @couch-kit/host\nbun install\n```\n\n\u003e **Note:** We do not use the `--link` flag. Keeping the default `file:` protocol ensures files are copied _inside_ your project root, which allows Metro Bundler to watch them correctly without extra configuration.\n\n**Iterating:**\n\nWhen you make changes to the library:\n\n1. Run `bun run build` in the library repo.\n2. Run `yalc push` in the modified package folder (e.g., `packages/host`).\n3. Your game app should hot-reload automatically.\n\n**Troubleshooting:**\n\n- **Duplicate React / Invalid Hook Call:** Ensure your library packages treat `react` as a `peerDependency` and do not bundle it. `yalc` handles this correctly by default.\n- **Changes not showing up?** If you add new files or exports, Metro might get stuck. Stop the bundler and run:\n  ```bash\n  bun start --reset-cache\n  ```\n\n---\n\n## 📦 Architecture\n\n| Package                 | Purpose                                                                                    |\n| ----------------------- | ------------------------------------------------------------------------------------------ |\n| **`@couch-kit/host`**   | Runs on the TV. Manages WebSocket server, serves static files, and holds the \"True\" state. |\n| **`@couch-kit/client`** | Runs on the phone browser. Connects to the host and renders the controller UI.             |\n| **`@couch-kit/core`**   | Shared TypeScript types and protocol definitions.                                          |\n| **`@couch-kit/cli`**    | CLI tools to scaffold, bundle, and simulate web controllers                                |\n\n## 🔄 Release Flow\n\nThis repo uses [Changesets](https://github.com/changesets/changesets) for versioning and publishing.\n\n### How it works\n\n1. **Every PR** must include a changeset (`bun changeset` to create one)\n2. On merge to `main`, the release workflow either:\n   - Creates a **\"Version Packages\"** PR if there are pending changesets\n   - **Publishes to npm** if the version PR was merged (no pending changesets)\n3. After publishing, **issues are automatically created** in consumer app repos ([domino](https://github.com/faluciano/domino-party-game), [buzz](https://github.com/faluciano/buzz-tv-party-game)) assigned to Copilot to update dependencies\n\n### Breaking changes\n\nWhen publishing a **major version bump**, the consumer apps will need code changes. The auto-created issues flag this, and CI in each app repo (typecheck + build) will catch any breakage before the update is merged.\n\n## 📚 Documentation\n\n- [Host Documentation](./packages/host/README.md)\n- [Client Documentation](./packages/client/README.md)\n- [Core Documentation](./packages/core/README.md)\n- [CLI Documentation](./packages/cli/README.md)\n\n## Troubleshooting\n\n- Phone can’t open the controller page: confirm TV and phone are on the same Wi‑Fi; verify `serverUrl` is not null.\n- Phone opens page but actions do nothing: check that your reducer handles your custom action types and the host isn’t erroring.\n- Dev mode WS fails: pass `url: \"ws://TV_IP:8082\"` to `useGameClient()`.\n- Connection is flaky: enable `debug` in host/client and watch logs; keep the TV from sleeping.\n\n## Security Notes\n\n- The controller URL is reachable to anyone on the same LAN. Don’t run this on untrusted Wi‑Fi.\n- `JOIN` requires a `secret` field — a persistent session token stored in the client's `localStorage`. The library uses it internally for session recovery. The raw secret is never broadcast to other clients; only a derived public `playerId` is shared in game state.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffaluciano%2Freact-native-couch-kit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffaluciano%2Freact-native-couch-kit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffaluciano%2Freact-native-couch-kit/lists"}