{"name":"@playlive/tiltify-phoenix","dist-tags":{"latest":"0.2.2"},"versions":{"0.2.0":{"name":"@playlive/tiltify-phoenix","version":"0.2.0","description":"Tiltify Phoenix Channels realtime client. Drop-in replacement for the legacy @playlive/tiltify-tools/tiltify-websocket.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./client":{"import":"./client.js","types":"./client.d.ts"},"./events":{"import":"./events.js","types":"./events.d.ts"}},"dependencies":{"@playlive/tiltify-core":"^0.1.1"},"peerDependencies":{"phoenix":"^1.8.1"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-5GagQTRY1dosuBxW2jJGAUuPIc+vMCmegEJpW944H/C6hheqPBnBhCZrZx1PsIOmJLJfVFDlIQd7inVMdC3q9A==","shasum":"fd2026da996d6aafd4001a0dc1da341fdbb2df0f","readme":"# @playlive/tiltify-phoenix\n\nTiltify Phoenix Channels realtime client. Drop-in replacement for the\nlegacy `@playlive/tiltify-tools/tiltify-websocket` module — same\nsurface, same wire-level behaviour, same callback semantics.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/tiltify-phoenix\n# Peer dep (caller installs)\nbun add phoenix\n# REST client used for fact lookups\nbun add @playlive/tiltify-core\n```\n\n`@playlive/tiltify-core` is a regular dependency and ships\nautomatically — listed above only because callers usually want to\nimport the `tiltify` singleton themselves for proxy / auth setup\nbefore constructing a `TiltifyWebSocket`.\n\n## Quick start\n\n```ts\nimport { TiltifyWebSocket } from \"@playlive/tiltify-phoenix\";\nimport { tiltify } from \"@playlive/tiltify-core\";\n\n// Configure the REST singleton your fact lookups will use:\ntiltify.SetProxyUrl(\"https://tiltify-proxy.prod.experience.stjude.org\");\ntiltify.UseProxy();\n\nconst ws = new TiltifyWebSocket(\n  (isInitial) => console.log(\"open\", { isInitial }),\n  (err)       => console.error(\"ws error\", err),\n  (isDisc)    => console.log(\"close\", { isDisconnecting: isDisc }),\n);\n\nws.addEventListener((factID, event, type, payload) => {\n  console.log(\"event\", { factID, event, type, payload });\n});\n\n// High-level: resolve slug → start listening to campaign + parents.\nawait ws.startListeningToCampaignBySlug(\"@playliver\", \"campaign-slug\");\n\n// …on unmount:\nws.disconnect();\n```\n\n## Subpath exports\n\n| Subpath  | Description                                                                          |\n| -------- | ------------------------------------------------------------------------------------ |\n| `.`      | Default barrel — `TiltifyWebSocket`, `EVENTS_TO_WATCH`, `EventType`, `FactType`, `DEFAULT_TILTIFY_SOCKET_URL`, callback types, `PACKAGE_NAME`, `KNOWN_URLS`. |\n| `/client` | `TiltifyWebSocket` class + its callback types only. Pulls the `phoenix` peer dep.   |\n| `/events` | `EVENTS_TO_WATCH`, `EventType`, `FactType`. Zero runtime deps — safe for type-only imports in route handlers / dispatch tables. |\n\n## API reference\n\nRun `bun run docs:build` inside this package to emit the TypeDoc\nsite at `dist/docs/`. The aggregate site (every package merged) is\nbuilt via `bun run docs:site` at the workspace root.\n\nTop-level exports:\n\n- `TiltifyWebSocket` — Phoenix Channel wrapper. Lifecycle, channel\n  ack, per-event firehose, name cache.\n- `DEFAULT_TILTIFY_SOCKET_URL` — `wss://websockets.tiltify.com/socket`.\n- `EVENTS_TO_WATCH` — every event suffix this client knows about.\n- `EventType` — the union of the above.\n- `FactType` — `\"campaign\" | \"fundraising-event\" | \"cause\"`.\n- `OnOpenCallback`, `OnErrorCallback`, `OnCloseCallback`,\n  `ChannelListener` — constructor + listener types.\n- `PACKAGE_NAME`, `KNOWN_URLS` — package metadata / §6.1 disclosure.\n\n## Migration from `@playlive/tiltify-tools/tiltify-websocket`\n\nThe surface is identical at the call site. Only the import\nspecifier (and the underlying REST singleton) changed:\n\n```diff\n- import {\n-   EVENTS_TO_WATCH,\n-   type EventType,\n-   type FactType,\n-   TiltifyWebSocket,\n- } from \"@playlive/tiltify-tools/tiltify-websocket\";\n+ import {\n+   EVENTS_TO_WATCH,\n+   type EventType,\n+   type FactType,\n+   TiltifyWebSocket,\n+ } from \"@playlive/tiltify-phoenix\";\n```\n\nBehaviour deltas:\n\n- REST lookups now use the `@playlive/tiltify-core` `tiltify`\n  singleton instead of `@playlive/tiltify-lib`. Both expose the\n  same `Tiltify` class surface (`FindCampaign`, `GetCampaign`,\n  `GetFundraiser`, `GetCause`,\n  `GetFundraisingEventsSupportingCampaigns`), so consumer code\n  doesn't change. If you were configuring `@playlive/tiltify-lib`'s\n  `tiltify` singleton at boot, switch to configuring\n  `@playlive/tiltify-core`'s singleton.\n- The socket URL was hard-coded inside the legacy class. It's now\n  exposed as `DEFAULT_TILTIFY_SOCKET_URL` and overridable via the\n  constructor's 4th argument (same as before — the override is just\n  documented now).\n\nSee [`MIGRATION.md`](../../../MIGRATION.md#-playlivetiltify-tools--playlivetiltify-phoenix-phase-19)\nfor the full symbol map.\n\n## Twitch Extension URL disclosure\n\nPer PRD §6.1, every Extension-eligible package declares the hosts /\nabsolute URLs it can reach over the network as `KNOWN_URLS`:\n\n- `wss://websockets.tiltify.com` — Phoenix Channels gateway.\n\nREST hosts (`https://v5api.tiltify.com`, proxy URLs) are disclosed\nby `@playlive/tiltify-core` and inherited transitively.\n\n## Versioning\n\nFollows the workspace's `0.1.x` line. The actual realtime surface\nshipped in `0.2.0`; `0.1.0` was an empty placeholder. See\n[`CHANGELOG.md`](./CHANGELOG.md) for per-version detail.\n\n## Upstream spec\n\nTiltify's Phoenix Channels gateway (`wss://websockets.tiltify.com/socket`)\nis not covered by the v5 OpenAPI snapshots in [`specs/tiltify/`](../../../specs/tiltify/).\nThe channel topic format (`<fact-type>:<fact-id>`) and event suffix list\n(`EVENTS_TO_WATCH` — `donation_updated`, `fact_updated`, etc.) are\nreverse-engineered from observed wire traffic and frozen in\n[`src/events.ts`](./src/events.ts).\n\nFact lookups (slug → ID, parent fundraising event resolution) delegate to\nthe Tiltify REST API via [`@playlive/tiltify-core`](../core/), which IS\nspec-backed. Re-sync those specs via the `/charity:sync` skill.\n\nWhen Tiltify publishes new realtime event suffixes, append them to\n`EVENTS_TO_WATCH` in `src/events.ts` and bump the minor version.\n\n## Examples\n\nA full overlay-style scenario (slug resolution → channel join → donation\nfirehose → name cache) lives inline in the Quick start above. End-to-end\nharness integration with `dev/greenroom` is tracked under a future phase.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../../CONTRIBUTING.md). For adding new realtime\nsurface (new event suffix, new fact type), edit `src/events.ts` first, then\nteach `TiltifyWebSocket.startListeningToFact` how to resolve the parent\nchain in `src/client.ts`. Every new event suffix needs a unit test in\n`tests/unit/client.test.ts`.\n\n## License\n\nMIT — see [LICENSE](../../../LICENSE). Distributed via Play Live CodeArtifact\n(PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/tiltify-phoenix/-/tiltify-phoenix-0.2.0.tgz","shasum":"fd2026da996d6aafd4001a0dc1da341fdbb2df0f","integrity":"sha512-5GagQTRY1dosuBxW2jJGAUuPIc+vMCmegEJpW944H/C6hheqPBnBhCZrZx1PsIOmJLJfVFDlIQd7inVMdC3q9A=="}},"0.1.0":{"name":"@playlive/tiltify-phoenix","version":"0.1.0","description":"Tiltify Phoenix Channels realtime client.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"}},"peerDependencies":{},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-K8rengfrULwUkYT3m9LF6FgCAUo5TZ29YlltWpEYF7lp10NKoJtdqnZVqC+f6z4Lrv4im9ZbrzmlMKPBrc1PFg==","shasum":"ad9a9b4a5c7a7c65ddf44f598647a0fde6c64381","readme":"# @playlive/tiltify-phoenix\n\nTiltify Phoenix Channels realtime client.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/tiltify-phoenix\n```\n\nNo peer dependencies.\n\n\n## Quick start\n\n```ts\nimport { PACKAGE_NAME } from \"@playlive/tiltify-phoenix\";\n\nconsole.log(PACKAGE_NAME);\n```\n\n## Subpath exports\n\n| Subpath | Description                              |\n| ------- | ---------------------------------------- |\n| .       | Default barrel — see API reference below. |\n\n## API reference\n\nRun `bun run docs:build` inside this package to emit the TypeDoc\nsite at `dist/docs/`. The aggregate site (every package merged) is\nbuilt via `bun run docs:site` at the workspace root.\n\nTop-level exports:\n\n- PACKAGE_NAME — package metadata constant.\n- KNOWN_URLS — Twitch Extension URL disclosure.\n\n## Upstream spec\n\nTiltify v5 OpenAPI spec snapshots live at [specs/tiltify/](../../../specs/tiltify/). See [docs/charity-spec-sync.md](../../../docs/charity-spec-sync.md).\n\n## Twitch Extension URL disclosure\n\nThe KNOWN_URLS export enumerates every absolute URL or host this\npackage can fetch. See [docs/twitch-extension-checklist.md](../../../docs/twitch-extension-checklist.md).\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/tiltify-phoenix\";\nconsole.log(KNOWN_URLS);\n```\n\nKeep this list and the source export in sync — the Extension\nsubmission form requires the disclosure list verbatim.\n\n## Examples\n\nExamples land in `examples/` once the package has real exports.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../../CONTRIBUTING.md). For adding new API\nsurface, use the scaffolding skills in [.agents/skills/](../../../.agents/skills/).\n\n## License\n\nMIT — see [LICENSE](../../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/tiltify-phoenix/-/tiltify-phoenix-0.1.0.tgz","shasum":"ad9a9b4a5c7a7c65ddf44f598647a0fde6c64381","integrity":"sha512-K8rengfrULwUkYT3m9LF6FgCAUo5TZ29YlltWpEYF7lp10NKoJtdqnZVqC+f6z4Lrv4im9ZbrzmlMKPBrc1PFg=="}},"0.2.1":{"name":"@playlive/tiltify-phoenix","version":"0.2.1","description":"Tiltify Phoenix Channels realtime client. Drop-in replacement for the legacy @playlive/tiltify-tools/tiltify-websocket.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./client":{"import":"./client.js","types":"./client.d.ts"},"./events":{"import":"./events.js","types":"./events.d.ts"}},"dependencies":{"@playlive/tiltify-core":"^0.4.18"},"peerDependencies":{"phoenix":"^1.8.1"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-CgtxCPVDkXkVKL5vhJckZoguIhDMXWHYfs9+2DEuIQ1TtWU0RkL8DZ4iK37MCD8tLLW1RH7g5G5DDl9jycfb0w==","shasum":"5d6c3c505812758073d90f077500f9652026529e","readme":"# @playlive/tiltify-phoenix\n\nTiltify Phoenix Channels realtime client. Drop-in replacement for the legacy\n`@playlive/tiltify-tools/tiltify-websocket` module — same surface, same\nwire-level behavior, same callback semantics.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/tiltify-phoenix\n# Peer dependency — the caller installs it\nbun add phoenix\n# TypeScript consumers also want the ambient types\nbun add -d @types/phoenix\n```\n\n`phoenix` (`^1.8.1`) is the only entry in `peerDependencies`.\n`@playlive/tiltify-core` is a regular **dependency** and installs\nautomatically — but you will usually want to import its `tiltify` singleton\nyourself to configure auth/logging before constructing a `TiltifyWebSocket`\n(see the caveat under [Quick start](#quick-start)).\n\n## Quick start\n\n```ts\nimport { tiltify } from \"@playlive/tiltify-core\";\nimport { EVENTS_TO_WATCH, TiltifyWebSocket } from \"@playlive/tiltify-phoenix\";\n\n// Configure the REST singleton used for the slug → fact-id lookups.\n// NOTE: `startListeningToCampaignBySlug` calls `tiltify.UseProxy()` itself,\n// which resets routing to the **prod** St. Jude proxy. To target qa/dev,\n// resolve the campaign yourself and call `startListeningToFact` instead.\ntiltify.UseProxy(\"prod\");\ntiltify.SetPublicMode();\n\nconst ws = new TiltifyWebSocket(\n  (isInitial) => console.log(\"open\", { isInitial }),\n  (err)       => console.error(\"ws error\", err),\n  (isDisc)    => console.log(\"close\", { isDisconnecting: isDisc }),\n);\n\nws.addEventListener((factID, event, type, payload) => {\n  console.log(\"event\", { factID, event, type, payload });\n});\n\n// High-level: resolve slug → start listening to campaign + parent facts.\nawait ws.startListeningToCampaignBySlug(\"@playliver\", \"campaign-slug\", EVENTS_TO_WATCH);\n\n// …on unmount:\nws.disconnect();\n```\n\n## Subpath exports\n\n| Subpath                             | Description                                                                                                                                                     |\n| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `@playlive/tiltify-phoenix`         | Default barrel — `TiltifyWebSocket`, `DEFAULT_TILTIFY_SOCKET_URL`, `EVENTS_TO_WATCH`, `EventType`, `FactType`, the callback types, `PACKAGE_NAME`, `KNOWN_URLS`. |\n| `@playlive/tiltify-phoenix/client`  | `TiltifyWebSocket`, `DEFAULT_TILTIFY_SOCKET_URL`, and the callback types only. Pulls the `phoenix` peer dep.                                                     |\n| `@playlive/tiltify-phoenix/events`  | `EVENTS_TO_WATCH`, `EventType`, `FactType`. Zero runtime deps — safe for type-only imports in route handlers / dispatch tables.                                  |\n\nThe default barrel is a curated re-export (not `export *`), so these three\nlists are exactly the public surface.\n\n## API reference\n\nRun `bun run docs:build` inside this package to emit the TypeDoc site at\n`dist/docs/`. The aggregate site (every package merged) is built via\n`bun run docs:site` at the workspace root.\n\nTop-level exports:\n\n| Export                                                              | Source     | Kind  | Notes                                                                       |\n| -------------------------------------------------------------------- | ---------- | ----- | --------------------------------------------------------------------------- |\n| `TiltifyWebSocket`                                                   | `./client` | class | Phoenix Channel wrapper. Lifecycle, channel ack, per-event firehose, name cache. |\n| `DEFAULT_TILTIFY_SOCKET_URL`                                         | `./client` | const | `\"wss://websockets.tiltify.com/socket\"`.                                     |\n| `OnOpenCallback`, `OnErrorCallback`, `OnCloseCallback`               | `./client` | type  | The three constructor callbacks.                                             |\n| `ChannelListener`                                                    | `./client` | type  | `{ onOk, onError, onTimeout }` per-channel join acks.                        |\n| `EVENTS_TO_WATCH`                                                    | `./events` | const | `EventType[]` — every event suffix this client knows about. Order is stable. |\n| `EventType`                                                          | `./events` | type  | `\"donation\"`, `\"donation_updated\"`, `\"match\"`, `\"fact\"`, `\"reward\"`, `\"milestone\"`, `\"challenge\"`, `\"poll\"`, `\"poll_option\"`. |\n| `FactType`                                                           | `./events` | type  | `\"campaign\"`, `\"fundraising-event\"`, `\"cause\"`.                              |\n| `PACKAGE_NAME`, `KNOWN_URLS`                                         | `./`       | const | Package metadata / PRD §6.1 disclosure.                                      |\n\n### `TiltifyWebSocket`\n\nThe constructor connects immediately:\n`new TiltifyWebSocket(onOpen, onError, onClose, address?)` — `address` defaults\nto `DEFAULT_TILTIFY_SOCKET_URL`.\n\n| Method                                                        | Notes                                                                                      |\n| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |\n| `connect()`                                                   | Re-arm + re-open. The next `onOpen` reports `isInitialConnection === true`.                 |\n| `addEventListener(listener)`                                  | Firehose: `(factID, event, type, payload) => void` for every event on every joined fact.     |\n| `on(event, callback)`                                         | Filtered form: `(factID, type, payload) => void` for one `EventType`.                        |\n| `addChannelListener(listener)`                                | Per-channel `ok` / `error` / `timeout` acks. **Register before** you start listening.        |\n| `startListeningToCampaignBySlug(teamUserSlug, campaignSlug, events?)` | Resolves the slug pair via REST, then joins the campaign + its parent fundraising event. Throws if the campaign is not found. |\n| `startListeningToFact(factID, events, type, lookup?)`         | Low-level join. Idempotent per `factID`. `lookup` (default `true`) fetches the display name + parent facts. |\n| `disconnect()`                                                | Leaves every channel and closes the socket; next `onClose` reports `isDisconnecting === true`. |\n| `getCampaignName(id)` / `getFundraisingEventName(id)` / `getCauseName(id)` | Cached display names, or `undefined`.                                            |\n| `getFactNameForType(factID, type)`                            | Dispatches to whichever of the three caches matches `type`.                                  |\n\n`events` defaults to `EVENTS_TO_WATCH` on both `startListening*` methods, but\non `startListeningToFact` it sits *before* the required `type` argument — pass\nit explicitly there.\n\n## Migration from `@playlive/tiltify-tools/tiltify-websocket`\n\nThe surface is identical at the call site. Only the import specifier (and the\nunderlying REST singleton) changed:\n\n```diff\n- import {\n-   EVENTS_TO_WATCH,\n-   type EventType,\n-   type FactType,\n-   TiltifyWebSocket,\n- } from \"@playlive/tiltify-tools/tiltify-websocket\";\n+ import {\n+   EVENTS_TO_WATCH,\n+   type EventType,\n+   type FactType,\n+   TiltifyWebSocket,\n+ } from \"@playlive/tiltify-phoenix\";\n```\n\nBehavior deltas:\n\n- REST lookups now use the [`@playlive/tiltify-core`](../core/) `tiltify`\n  singleton instead of `@playlive/tiltify-lib`. Both expose the same `Tiltify`\n  class surface (`FindCampaign`, `GetCampaign`, `GetFundraiser`, `GetCause`,\n  `GetFundraisingEventsSupportingCampaigns`), so consumer code doesn't change.\n  If you were configuring `@playlive/tiltify-lib`'s `tiltify` singleton at\n  boot, switch to configuring `@playlive/tiltify-core`'s singleton.\n- The socket URL was hard-coded inside the legacy class. It's now exposed as\n  `DEFAULT_TILTIFY_SOCKET_URL` and overridable via the constructor's 4th\n  argument (same as before — the override is just documented now).\n\nSee [`MIGRATION.md`](../../../MIGRATION.md#-playlivetiltify-tools--playlivetiltify-phoenix-phase-19)\nfor the full symbol map.\n\n## Twitch Extension URL disclosure\n\nPer PRD §6.1, every Extension-eligible package declares the hosts / absolute\nURLs it can reach over the network as `KNOWN_URLS`. See\n[`docs/twitch-extension-checklist.md`](../../../docs/twitch-extension-checklist.md).\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/tiltify-phoenix\";\nconsole.log(KNOWN_URLS);\n// [\"wss://websockets.tiltify.com\"]\n```\n\nREST hosts (`https://v5api.tiltify.com`, the St. Jude proxy URLs) are disclosed\nby [`@playlive/tiltify-core`](../core/) and inherited transitively — the\nExtension form needs the union of both lists.\n\n## Versioning\n\nFollows the workspace's `0.x` line. The actual realtime surface shipped in\n`0.2.0`; `0.1.0` was an empty placeholder. See [`CHANGELOG.md`](./CHANGELOG.md)\nfor per-version detail.\n\n## Upstream spec\n\nTiltify's Phoenix Channels gateway (`wss://websockets.tiltify.com/socket`) is\nnot covered by the v5 OpenAPI snapshots in\n[`specs/tiltify/`](../../../specs/tiltify/). The channel topic format\n(`fact.<fact-id>.<event>`) and the event suffix list (`EVENTS_TO_WATCH`) are\nreverse-engineered from observed wire traffic and frozen in\n[`src/events.ts`](./src/events.ts).\n\nFact lookups (slug → ID, parent fundraising event resolution) delegate to the\nTiltify REST API via [`@playlive/tiltify-core`](../core/), which IS spec-backed.\nRe-sync those specs via the `/charity:sync` skill or `bun run sync-charity-specs`\nat the workspace root.\n\nWhen Tiltify publishes new realtime event suffixes, append them to\n`_EVENTS_TO_WATCH` in `src/events.ts` (the exported `EVENTS_TO_WATCH` is a\nspread copy of that const tuple, and `EventType` is derived from it) and bump\nthe minor version.\n\n## Examples\n\n### Overlay lifecycle: donation firehose with named facts\n\nThe pattern `playlive-console-v2`'s `LiveEventsProvider` uses. Register channel\nand event listeners **before** starting to listen — `addChannelListener` only\nfires for channels opened after it was registered.\n\n```ts\nimport { tiltify } from \"@playlive/tiltify-core\";\nimport {\n  type EventType,\n  type FactType,\n  TiltifyWebSocket,\n} from \"@playlive/tiltify-phoenix\";\n\ninterface DonationPayload {\n  id: string;\n  amount: { value: string; currency: string };\n  donor_name: string;\n  donor_comment: string | null;\n}\n\nexport function startDonationFeed(teamUserSlug: string, campaignSlug: string) {\n  tiltify.UseProxy(\"prod\");\n  tiltify.SetPublicMode();\n\n  const ws = new TiltifyWebSocket(\n    (isInitial) => {\n      // Phoenix auto-reconnects; only the first open is \"initial\".\n      if (!isInitial) console.info(\"tiltify socket reconnected\");\n    },\n    (err) => console.error(\"tiltify socket error\", err),\n    (isDisconnecting) => {\n      if (!isDisconnecting) console.warn(\"tiltify socket dropped unexpectedly\");\n    },\n  );\n\n  // Per-channel join acks — useful for surfacing \"subscribed\" state in a UI.\n  ws.addChannelListener({\n    onOk: (factID, event, type) => console.debug(\"joined\", { factID, event, type }),\n    onError: (factID, event, type, error) =>\n      console.error(\"join failed\", { factID, event, type, error }),\n    onTimeout: (factID, event, type) => console.warn(\"join timed out\", { factID, event, type }),\n  });\n\n  // Filtered subscription for the one event this feed cares about.\n  ws.on(\"donation\", (factID, type: FactType, payload) => {\n    const donation = payload as DonationPayload;\n    const factName = ws.getFactNameForType(factID, type) ?? factID;\n    console.info(`💸 ${donation.donor_name} → ${factName}: ${donation.amount.value}`);\n  });\n\n  // Unfiltered firehose for logging / dev tooling.\n  ws.addEventListener((factID, event: EventType, type: FactType) => {\n    console.debug(\"tiltify event\", { factID, event, type });\n  });\n\n  // Resolves the slug pair via REST, joins the campaign channel set, and —\n  // when the campaign belongs to one — the parent fundraising event's too.\n  const ready = ws\n    .startListeningToCampaignBySlug(teamUserSlug, campaignSlug)\n    .catch((err: unknown) => {\n      // \"Could not find campaign with the provided slugs\" on a 404;\n      // TiltifyLibError for transport failures.\n      console.error(\"could not subscribe\", err);\n      ws.disconnect();\n      throw err;\n    });\n\n  return { ws, ready, stop: () => ws.disconnect() };\n}\n```\n\n### Narrow the event set and skip the REST round-trip\n\nWhen you already know the fact id (from\n`@playlive/tiltify-core`'s `FindCampaign`, or\n`@playlive/tiltify-graphql`'s `getFactByVanityAndSlug`), call\n`startListeningToFact` directly with `lookup: false`. The channel subscription\nhappens immediately; `getCampaignName()` stays `undefined` until you populate\nit yourself.\n\n```ts\nimport { TiltifyWebSocket } from \"@playlive/tiltify-phoenix\";\n\nconst ws = new TiltifyWebSocket(\n  () => {},\n  console.error,\n  () => {},\n  // Point at a local GreenRoom simulator instead of Tiltify.\n  \"ws://localhost:4000/socket\",\n);\n\n// Only milestone + poll traffic, no name lookup.\nawait ws.startListeningToFact(\n  \"9c1d0b7a-8f6e-4a2b-9c33-1f0e6a5b7c21\",\n  [\"milestone\", \"poll\", \"poll_option\"],\n  \"fundraising-event\",\n  false,\n);\n\nws.on(\"milestone\", (_factID, _type, payload) => {\n  console.log(\"milestone changed\", payload);\n});\n```\n\n`startListeningToFact` is idempotent per `factID`, so calling it again for a\nfact you already joined is a no-op — even if the `events` list differs. Join\nthe widest event set you need the first time.\n\nEnd-to-end scenarios that drive this client against a scripted Tiltify\nsimulator live in [`dev/greenroom`](../../../dev/greenroom/); see\n[`docs/greenroom-cookbook.md`](../../../docs/greenroom-cookbook.md).\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../../CONTRIBUTING.md). For adding new realtime\nsurface (new event suffix, new fact type), edit\n[`src/events.ts`](./src/events.ts) first, then teach\n`TiltifyWebSocket.startListeningToFact` how to resolve the parent chain in\n[`src/client.ts`](./src/client.ts). Every new event suffix needs a unit test in\n[`tests/unit/client.test.ts`](./tests/unit/client.test.ts).\n\n## License\n\nMIT — see [LICENSE](../../../LICENSE). Distributed via Play Live CodeArtifact\n(PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/tiltify-phoenix/-/tiltify-phoenix-0.2.1.tgz","shasum":"5d6c3c505812758073d90f077500f9652026529e","integrity":"sha512-CgtxCPVDkXkVKL5vhJckZoguIhDMXWHYfs9+2DEuIQ1TtWU0RkL8DZ4iK37MCD8tLLW1RH7g5G5DDl9jycfb0w=="}},"0.2.2":{"name":"@playlive/tiltify-phoenix","version":"0.2.2","description":"Tiltify Phoenix Channels realtime client. Drop-in replacement for the legacy @playlive/tiltify-tools/tiltify-websocket.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./client":{"import":"./client.js","types":"./client.d.ts"},"./events":{"import":"./events.js","types":"./events.d.ts"}},"dependencies":{"@playlive/tiltify-core":"^0.4.19"},"peerDependencies":{"phoenix":"^1.8.1"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-DL2uTOwlBlEY8aviPo8EOHYYvNerlYR6Zk2WIODrwAX0habIcS/3hFMyy6bpAx21+MdV1fvWmr+lRgqa77eZRg==","shasum":"63ffb961a37f988804b271a067b6c558229286c8","readme":"# @playlive/tiltify-phoenix\n\nTiltify Phoenix Channels realtime client. Drop-in replacement for the legacy\n`@playlive/tiltify-tools/tiltify-websocket` module — same surface, same\nwire-level behavior, same callback semantics.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/tiltify-phoenix\n# Peer dependency — the caller installs it\nbun add phoenix\n# TypeScript consumers also want the ambient types\nbun add -d @types/phoenix\n```\n\n`phoenix` (`^1.8.1`) is the only entry in `peerDependencies`.\n`@playlive/tiltify-core` is a regular **dependency** and installs\nautomatically — but you will usually want to import its `tiltify` singleton\nyourself to configure auth/logging before constructing a `TiltifyWebSocket`\n(see the caveat under [Quick start](#quick-start)).\n\n## Quick start\n\n```ts\nimport { tiltify } from \"@playlive/tiltify-core\";\nimport { EVENTS_TO_WATCH, TiltifyWebSocket } from \"@playlive/tiltify-phoenix\";\n\n// Configure the REST singleton used for the slug → fact-id lookups.\n// NOTE: `startListeningToCampaignBySlug` calls `tiltify.UseProxy()` itself,\n// which resets routing to the **prod** St. Jude proxy. To target qa/dev,\n// resolve the campaign yourself and call `startListeningToFact` instead.\ntiltify.UseProxy(\"prod\");\ntiltify.SetPublicMode();\n\nconst ws = new TiltifyWebSocket(\n  (isInitial) => console.log(\"open\", { isInitial }),\n  (err)       => console.error(\"ws error\", err),\n  (isDisc)    => console.log(\"close\", { isDisconnecting: isDisc }),\n);\n\nws.addEventListener((factID, event, type, payload) => {\n  console.log(\"event\", { factID, event, type, payload });\n});\n\n// High-level: resolve slug → start listening to campaign + parent facts.\nawait ws.startListeningToCampaignBySlug(\"@playliver\", \"campaign-slug\", EVENTS_TO_WATCH);\n\n// …on unmount:\nws.disconnect();\n```\n\n## Subpath exports\n\n| Subpath                             | Description                                                                                                                                                     |\n| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `@playlive/tiltify-phoenix`         | Default barrel — `TiltifyWebSocket`, `DEFAULT_TILTIFY_SOCKET_URL`, `EVENTS_TO_WATCH`, `EventType`, `FactType`, the callback types, `PACKAGE_NAME`, `KNOWN_URLS`. |\n| `@playlive/tiltify-phoenix/client`  | `TiltifyWebSocket`, `DEFAULT_TILTIFY_SOCKET_URL`, and the callback types only. Pulls the `phoenix` peer dep.                                                     |\n| `@playlive/tiltify-phoenix/events`  | `EVENTS_TO_WATCH`, `EventType`, `FactType`. Zero runtime deps — safe for type-only imports in route handlers / dispatch tables.                                  |\n\nThe default barrel is a curated re-export (not `export *`), so these three\nlists are exactly the public surface.\n\n## API reference\n\nFull generated API documentation:\n<https://packages.playlive.experience.stjude.org/p/@playlive/tiltify-phoenix/docs/>\n\nTop-level exports:\n\n| Export                                                              | Source     | Kind  | Notes                                                                       |\n| -------------------------------------------------------------------- | ---------- | ----- | --------------------------------------------------------------------------- |\n| `TiltifyWebSocket`                                                   | `./client` | class | Phoenix Channel wrapper. Lifecycle, channel ack, per-event firehose, name cache. |\n| `DEFAULT_TILTIFY_SOCKET_URL`                                         | `./client` | const | `\"wss://websockets.tiltify.com/socket\"`.                                     |\n| `OnOpenCallback`, `OnErrorCallback`, `OnCloseCallback`               | `./client` | type  | The three constructor callbacks.                                             |\n| `ChannelListener`                                                    | `./client` | type  | `{ onOk, onError, onTimeout }` per-channel join acks.                        |\n| `EVENTS_TO_WATCH`                                                    | `./events` | const | `EventType[]` — every event suffix this client knows about. Order is stable. |\n| `EventType`                                                          | `./events` | type  | `\"donation\"`, `\"donation_updated\"`, `\"match\"`, `\"fact\"`, `\"reward\"`, `\"milestone\"`, `\"challenge\"`, `\"poll\"`, `\"poll_option\"`. |\n| `FactType`                                                           | `./events` | type  | `\"campaign\"`, `\"fundraising-event\"`, `\"cause\"`.                              |\n| `PACKAGE_NAME`, `KNOWN_URLS`                                         | `./`       | const | Package metadata / Twitch Extension URL disclosure.                          |\n\n### `TiltifyWebSocket`\n\nThe constructor connects immediately:\n`new TiltifyWebSocket(onOpen, onError, onClose, address?)` — `address` defaults\nto `DEFAULT_TILTIFY_SOCKET_URL`.\n\n| Method                                                        | Notes                                                                                      |\n| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |\n| `connect()`                                                   | Re-arm + re-open. The next `onOpen` reports `isInitialConnection === true`.                 |\n| `addEventListener(listener)`                                  | Firehose: `(factID, event, type, payload) => void` for every event on every joined fact.     |\n| `on(event, callback)`                                         | Filtered form: `(factID, type, payload) => void` for one `EventType`.                        |\n| `addChannelListener(listener)`                                | Per-channel `ok` / `error` / `timeout` acks. **Register before** you start listening.        |\n| `startListeningToCampaignBySlug(teamUserSlug, campaignSlug, events?)` | Resolves the slug pair via REST, then joins the campaign + its parent fundraising event. Throws if the campaign is not found. |\n| `startListeningToFact(factID, events, type, lookup?)`         | Low-level join. Idempotent per `factID`. `lookup` (default `true`) fetches the display name + parent facts. |\n| `disconnect()`                                                | Leaves every channel and closes the socket; next `onClose` reports `isDisconnecting === true`. |\n| `getCampaignName(id)` / `getFundraisingEventName(id)` / `getCauseName(id)` | Cached display names, or `undefined`.                                            |\n| `getFactNameForType(factID, type)`                            | Dispatches to whichever of the three caches matches `type`.                                  |\n\n`events` defaults to `EVENTS_TO_WATCH` on both `startListening*` methods, but\non `startListeningToFact` it sits *before* the required `type` argument — pass\nit explicitly there.\n\n## Migration from `@playlive/tiltify-tools/tiltify-websocket`\n\nThe surface is identical at the call site. Only the import specifier (and the\nunderlying REST singleton) changed:\n\n```diff\n- import {\n-   EVENTS_TO_WATCH,\n-   type EventType,\n-   type FactType,\n-   TiltifyWebSocket,\n- } from \"@playlive/tiltify-tools/tiltify-websocket\";\n+ import {\n+   EVENTS_TO_WATCH,\n+   type EventType,\n+   type FactType,\n+   TiltifyWebSocket,\n+ } from \"@playlive/tiltify-phoenix\";\n```\n\nBehavior deltas:\n\n- REST lookups now use the [`@playlive/tiltify-core`](../core/) `tiltify`\n  singleton instead of `@playlive/tiltify-lib`. Both expose the same `Tiltify`\n  class surface (`FindCampaign`, `GetCampaign`, `GetFundraiser`, `GetCause`,\n  `GetFundraisingEventsSupportingCampaigns`), so consumer code doesn't change.\n  If you were configuring `@playlive/tiltify-lib`'s `tiltify` singleton at\n  boot, switch to configuring `@playlive/tiltify-core`'s singleton.\n- The socket URL was hard-coded inside the legacy class. It's now exposed as\n  `DEFAULT_TILTIFY_SOCKET_URL` and overridable via the constructor's 4th\n  argument (same as before — the override is just documented now).\n\n## Twitch Extension URL disclosure\n\nEvery Extension-eligible package declares the hosts / absolute URLs it can reach\nover the network as `KNOWN_URLS` — the list a Twitch Extension submission must\ndisclose verbatim.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/tiltify-phoenix\";\nconsole.log(KNOWN_URLS);\n// [\"wss://websockets.tiltify.com\"]\n```\n\nREST hosts (`https://v5api.tiltify.com`, the St. Jude proxy URLs) are disclosed\nby [`@playlive/tiltify-core`](../core/) and inherited transitively — the\nExtension form needs the union of both lists.\n\n## Versioning\n\nFollows the workspace's `0.x` line. The actual realtime surface shipped in\n`0.2.0`; `0.1.0` was an empty placeholder. See [`CHANGELOG.md`](./CHANGELOG.md)\nfor per-version detail.\n\n## Upstream spec\n\nThis package targets Tiltify's **Phoenix Channels realtime gateway** at\n`wss://websockets.tiltify.com/socket` (`DEFAULT_TILTIFY_SOCKET_URL`). Tiltify\ndoes not publish a specification for it: the channel topic format\n(`fact.<fact-id>.<event>`) and the event suffix list (`EVENTS_TO_WATCH`) are\nderived from observed wire traffic and frozen in this package, so pin the\nversion if you depend on an exact event set.\n\nFact lookups (slug → ID, parent fundraising-event resolution) delegate to the\ndocumented **Tiltify v5 REST API** via [`@playlive/tiltify-core`](../core/);\nupstream reference documentation for that surface is at\n<https://developers.tiltify.com>.\n\nWhen Tiltify publishes new realtime event suffixes they are appended to\n`EVENTS_TO_WATCH` (and to the derived `EventType` union) in a minor release.\n\n## Examples\n\n### Overlay lifecycle: donation firehose with named facts\n\nThe pattern a live-events provider in an overlay app wants. Register channel\nand event listeners **before** starting to listen — `addChannelListener` only\nfires for channels opened after it was registered.\n\n```ts\nimport { tiltify } from \"@playlive/tiltify-core\";\nimport {\n  type EventType,\n  type FactType,\n  TiltifyWebSocket,\n} from \"@playlive/tiltify-phoenix\";\n\ninterface DonationPayload {\n  id: string;\n  amount: { value: string; currency: string };\n  donor_name: string;\n  donor_comment: string | null;\n}\n\nexport function startDonationFeed(teamUserSlug: string, campaignSlug: string) {\n  tiltify.UseProxy(\"prod\");\n  tiltify.SetPublicMode();\n\n  const ws = new TiltifyWebSocket(\n    (isInitial) => {\n      // Phoenix auto-reconnects; only the first open is \"initial\".\n      if (!isInitial) console.info(\"tiltify socket reconnected\");\n    },\n    (err) => console.error(\"tiltify socket error\", err),\n    (isDisconnecting) => {\n      if (!isDisconnecting) console.warn(\"tiltify socket dropped unexpectedly\");\n    },\n  );\n\n  // Per-channel join acks — useful for surfacing \"subscribed\" state in a UI.\n  ws.addChannelListener({\n    onOk: (factID, event, type) => console.debug(\"joined\", { factID, event, type }),\n    onError: (factID, event, type, error) =>\n      console.error(\"join failed\", { factID, event, type, error }),\n    onTimeout: (factID, event, type) => console.warn(\"join timed out\", { factID, event, type }),\n  });\n\n  // Filtered subscription for the one event this feed cares about.\n  ws.on(\"donation\", (factID, type: FactType, payload) => {\n    const donation = payload as DonationPayload;\n    const factName = ws.getFactNameForType(factID, type) ?? factID;\n    console.info(`💸 ${donation.donor_name} → ${factName}: ${donation.amount.value}`);\n  });\n\n  // Unfiltered firehose for logging / dev tooling.\n  ws.addEventListener((factID, event: EventType, type: FactType) => {\n    console.debug(\"tiltify event\", { factID, event, type });\n  });\n\n  // Resolves the slug pair via REST, joins the campaign channel set, and —\n  // when the campaign belongs to one — the parent fundraising event's too.\n  const ready = ws\n    .startListeningToCampaignBySlug(teamUserSlug, campaignSlug)\n    .catch((err: unknown) => {\n      // \"Could not find campaign with the provided slugs\" on a 404;\n      // TiltifyLibError for transport failures.\n      console.error(\"could not subscribe\", err);\n      ws.disconnect();\n      throw err;\n    });\n\n  return { ws, ready, stop: () => ws.disconnect() };\n}\n```\n\n### Narrow the event set and skip the REST round-trip\n\nWhen you already know the fact id (from\n`@playlive/tiltify-core`'s `FindCampaign`, or\n`@playlive/tiltify-graphql`'s `getFactByVanityAndSlug`), call\n`startListeningToFact` directly with `lookup: false`. The channel subscription\nhappens immediately; `getCampaignName()` stays `undefined` until you populate\nit yourself.\n\n```ts\nimport { TiltifyWebSocket } from \"@playlive/tiltify-phoenix\";\n\nconst ws = new TiltifyWebSocket(\n  () => {},\n  console.error,\n  () => {},\n  // Point at a local Phoenix simulator instead of Tiltify.\n  \"ws://localhost:4000/socket\",\n);\n\n// Only milestone + poll traffic, no name lookup.\nawait ws.startListeningToFact(\n  \"9c1d0b7a-8f6e-4a2b-9c33-1f0e6a5b7c21\",\n  [\"milestone\", \"poll\", \"poll_option\"],\n  \"fundraising-event\",\n  false,\n);\n\nws.on(\"milestone\", (_factID, _type, payload) => {\n  console.log(\"milestone changed\", payload);\n});\n```\n\n`startListeningToFact` is idempotent per `factID`, so calling it again for a\nfact you already joined is a no-op — even if the `events` list differs. Join\nthe widest event set you need the first time.\n\n## License\n\nMIT © St. Jude Children's Research Hospital\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/tiltify-phoenix/-/tiltify-phoenix-0.2.2.tgz","shasum":"63ffb961a37f988804b271a067b6c558229286c8","integrity":"sha512-DL2uTOwlBlEY8aviPo8EOHYYvNerlYR6Zk2WIODrwAX0habIcS/3hFMyy6bpAx21+MdV1fvWmr+lRgqa77eZRg=="}}},"time":{"0.2.0":"2026-08-26T18:10:11.303Z","modified":"2026-08-26T20:07:06.146Z","0.1.0":"2026-08-26T18:16:10.608Z","0.2.1":"2026-08-26T19:42:44.519Z","0.2.2":"2026-08-26T20:07:06.146Z"}}