npm install @playlive/tiltify-core
Tiltify v5 REST client (public + private + cause) with native fetch. Singleton
ported from @playlive/tiltify-lib; the public surface is preserved verbatim so
consumers can migrate by changing the import specifier alone.
bun add @playlive/tiltify-core
No peer dependencies and no runtime dependencies — peerDependencies in
package.json is empty and the client uses the platform's fetch.
import { tiltify } from "@playlive/tiltify-core";
// Server-side, client-credential OAuth (reads TILTIFY_CLIENT_ID / TILTIFY_SECRET):
tiltify.UseClientAuth();
const events = await tiltify.GetCurrentEvents();
// Frontend / browser usage via the St. Jude proxy (no credentials shipped):
tiltify.UseProxy("prod");
tiltify.SetPublicMode();
const campaign = await tiltify.FindCampaign("philckd", "philckd-x-st-jude-play-live-2023");
// Auto-fallback prod → qa → dev on transient errors:
tiltify.UseProxyWithFallback();
// User OAuth — caller performs the code-exchange and pushes the token in:
tiltify.UseUserAuth();
tiltify.SetCredentials(clientID, clientSecret);
tiltify.OverrideAuthToken(`Bearer ${accessToken}`, Date.now() + expiresInMs, refreshToken);
const me = await tiltify.GetCurrentUser();
| Subpath | Description |
|---|---|
@playlive/tiltify-core |
Default barrel — re-exports every module below. |
@playlive/tiltify-core/client |
Tiltify class + tiltify singleton. |
@playlive/tiltify-core/types |
Type-only barrel (campaign / donation / user / cause / auction / webhook / common). |
@playlive/tiltify-core/constants |
URL, header, and version constants (BASE_URL, proxy URLs, SJ_CAUSE_ID, …). |
@playlive/tiltify-core/errors |
TiltifyLibError, TiltifyErrorCode, TiltifyLibErrorOptions, normalizeEnvelopeError. |
@playlive/tiltify-core/backoff |
Pure Retry-After / exponential-backoff helpers. No REST deps. |
@playlive/tiltify-core/urls |
Pure tiltify.com URL parse/build helpers. No REST deps. |
@playlive/tiltify-core/cause-classifier |
isCampaignForCause / filterCampaignsByCause (performs REST lookups). |
/backoff and /urls are dependency-free — import them when you want the
helpers without pulling the client (and its globalThis.fetch touch) into a
bundle. auth, logging, and proxy have no dedicated subpath; import those
symbols from the default barrel.
Full generated API documentation: https://packages.playlive.experience.stjude.org/p/@playlive/tiltify-core/docs/
Top-level exports:
| Export | Source | Kind | Notes |
|---|---|---|---|
Tiltify |
./client |
class | Prefer the singleton. Tiltify.Instance is globalThis-pinned. |
tiltify |
./client |
const | The singleton (Tiltify.Instance). |
TILTIFY_AUTH_MODE |
./auth |
enum | DISABLED / CLIENT / USER. |
TILTIFY_AUTH_STATUS |
./auth |
enum | MISSING / VALID / NEEDS_REFRESH. |
TiltifyOAuthScope |
./auth |
type | "public" / "cause" / "webhooks:write". |
TiltifyLibError |
./errors |
class | Throwable; branch via err.code. .fromTiltifyError(), .toJSON(). |
TiltifyErrorCode |
./errors |
type | Union of error categories ("NOT_FOUND", "TIMEOUT", …). |
TiltifyLibErrorOptions |
./errors |
type | Structured throw-site context. |
normalizeEnvelopeError |
./errors |
function | String / object / "HTTP 4xx" body normalizer. |
LogLevel, LogFn |
./logging |
type | Pluggable logger primitives. |
LOG_LEVEL_RANK, defaultLogger |
./logging |
const | Level ranks + the built-in console.* sink. |
TiltifyProxyEnv |
./proxy |
type | "dev" / "qa" / "prod". |
PROXY_URLS, PROXY_FALLBACK_ORDER |
./proxy |
const | Env → URL map; ordered fallback envs. |
isProxyEnv, joinURL, shouldFallbackOnError |
./proxy |
function | Type guard, slash-safe URL join, transient-error predicate. |
BASE_URL, OAUTH_TOKEN_URL |
./constants |
const | Tiltify v5 API + token endpoints. |
PROD_PROXY_URL, QA_PROXY_URL, DEV_PROXY_URL, PROXY_FALLBACK_CHAIN |
./constants |
const | Hosted St. Jude proxy URLs + their ordered chain. |
SJ_CAUSE_ID, ALWAYS_INCLUDE_SJ_FUNDRAISING_EVENT_IDS |
./constants |
const | St. Jude cause UUID + legacy event-ID allow-set. |
SOURCE_HEADER_NAME, SOURCE_HEADER_VALUE |
./constants |
const | Proxy request-tagging header (overridable via SetSourceTag). |
CLIENT_VERSION, CLIENT_VERSION_HEADER_NAME, CLIENT_VERSION_HEADER_VALUE |
./constants |
const | Build stamp; proxy-only telemetry header. "dev" when run from source. |
USER_AGENT_HEADER_NAME, USER_AGENT_HEADER_VALUE |
./constants |
const | Default outbound UA (overridable via SetUserAgent). |
parseRetryAfter, computeBackoffMs, nextRetrySleepMs |
./backoff |
function | RFC 7231 Retry-After parsing + jittered exponential backoff. |
ComputeBackoffOptions |
./backoff |
type | Tunables for the two backoff helpers. |
DEFAULT_MAX_BACKOFF_MS, DEFAULT_BASE_BACKOFF_MS, DEFAULT_JITTER_MS |
./backoff |
const | 30_000 / 500 / 250. |
normalizeTiltifyUrl, extractSlugsFromTiltifyUrl, isTeamCampaignUrl, buildTiltifyUrl |
./urls |
function | Absolute + relative @user / +team URL handling. |
ExtractedSlugs |
./urls |
type | { fundraiserType, fundraiserSlug, campaignSlug }. |
isCampaignForCause, filterCampaignsByCause |
./cause-classifier |
function | Direct + fundraising-event-mediated cause attribution. |
FundraisingEventLookup, CauseClassifierOptions |
./cause-classifier |
type | Injection seam + options bag for the classifier. |
KNOCK_KNOCK_JOKES, getRandomJoke |
./knock-knock-jokes |
const/fn | Appended to the outbound User-Agent. Yes, really. |
Tiltify* types |
./types |
type | Every public Tiltify resource shape. |
KNOWN_URLS |
./ |
const | Twitch Extension URL disclosure list. |
PACKAGE_NAME |
./ |
const | Identifier for runtime version-pinning. |
This package targets the Tiltify v5 REST API (https://v5api.tiltify.com)
across all three published scopes — public, private, and cause. Upstream
reference documentation, OAuth scope descriptions, and per-endpoint payload
shapes live at https://developers.tiltify.com.
Public methods on Tiltify that map 1:1 to an upstream operation carry an
@see tag naming the upstream operationId, so you can jump from a method in
the generated API docs straight to the matching entry in Tiltify's reference.
The KNOWN_URLS export enumerates every absolute URL or host this package can
fetch — the list a Twitch Extension submission must disclose verbatim.
import { KNOWN_URLS } from "@playlive/tiltify-core";
console.log(KNOWN_URLS);
// [
// "https://v5api.tiltify.com",
// "https://tiltify-proxy.prod.experience.stjude.org",
// "https://tiltify-proxy.qa.experience.stjude.org",
// "https://tiltify-proxy.dev.experience.stjude.org",
// ]
Keep this list and the source export in sync — the Extension submission form requires the disclosure list verbatim.
@playlive/tiltify-lib@playlive/tiltify-core is a drop-in replacement: the Tiltify class, the
tiltify singleton, every method signature, every exported type, every
constant, and every TiltifyLibError code are preserved unchanged. Only the
import paths move:
- import { tiltify, TiltifyLibError, BASE_URL } from "@playlive/tiltify-lib";
+ import { tiltify, TiltifyLibError, BASE_URL } from "@playlive/tiltify-core";
The build is ESM-only (the legacy package shipped dual ESM+CJS); CommonJS
consumers need a dynamic import().
Configure the singleton once at boot, resolve a tiltify.com URL to its slugs,
then fan out to the per-campaign resources. Every method throws
TiltifyLibError, so a single catch covers the whole batch — branch on
err.code rather than parsing messages.
import {
extractSlugsFromTiltifyUrl,
TiltifyLibError,
tiltify,
type TiltifyCampaign,
type TiltifyDonation,
type TiltifyLeaderboardEntry,
type TiltifyMilestone,
type TiltifyTeamCampaign,
} from "@playlive/tiltify-core";
// ── Boot-time configuration (do this once) ────────────────────────────────
tiltify.UseProxyWithFallback("playlive-overlay"); // prod → qa → dev, tagged
tiltify.SetPublicMode();
tiltify.SetLogLevel("warn");
tiltify.SetTiltifyTimeout(8_000);
tiltify.SetMaxRetryDuration(20_000); // cap total retry wall-time
export interface OverlayPayload {
campaign: TiltifyCampaign | TiltifyTeamCampaign;
nextMilestone: TiltifyMilestone | null;
topDonors: TiltifyLeaderboardEntry[];
recentDonations: TiltifyDonation[];
}
export async function loadOverlay(campaignUrl: string): Promise<OverlayPayload | null> {
const slugs = extractSlugsFromTiltifyUrl(campaignUrl);
if (!slugs) {
throw new TiltifyLibError(`Not a Tiltify campaign URL: ${campaignUrl}`, {
code: "INVALID_ARGUMENT",
fields: ["campaignUrl"],
});
}
const isTeam = slugs.fundraiserType === "team";
try {
// FindCampaign returns null on 404 rather than throwing.
const campaign = await tiltify.FindCampaign(
slugs.fundraiserSlug,
slugs.campaignSlug,
isTeam,
);
if (!campaign) return null;
const [milestones, topDonors, recentDonations] = await Promise.all([
tiltify.GetMilestones(campaign.id, isTeam),
tiltify.GetLeaderboard(campaign.id, "all", isTeam, 10),
tiltify.GetCampaignDonations(campaign.id, isTeam, 25),
]);
const raised = Number(campaign.amount_raised?.value ?? 0);
const nextMilestone =
milestones
.filter((m) => m.active && Number(m.amount.value) > raised)
.sort((a, b) => Number(a.amount.value) - Number(b.amount.value))[0] ?? null;
return { campaign, nextMilestone, topDonors, recentDonations };
} catch (err) {
if (err instanceof TiltifyLibError) {
// Structured context: code / status / endpoint / cursor / fields.
if (err.code === "NOT_FOUND") return null;
if (err.code === "TIMEOUT" || (err.status ?? 0) >= 500) {
console.warn("tiltify upstream degraded", err.toJSON());
return null;
}
}
throw err;
}
}
A campaign belongs to a cause either directly (cause_id matches) or
indirectly (its fundraising event's cause_id matches).
filterCampaignsByCause resolves both, memoizing each unique
fundraising_event_id so a 500-campaign list issues one lookup per event.
import {
ALWAYS_INCLUDE_SJ_FUNDRAISING_EVENT_IDS,
filterCampaignsByCause,
SJ_CAUSE_ID,
tiltify,
} from "@playlive/tiltify-core";
tiltify.UseClientAuth();
tiltify.SetCredentials(process.env.TILTIFY_CLIENT_ID!, process.env.TILTIFY_SECRET!);
const { data: events } = await tiltify.GetCurrentEvents();
const supporting = await tiltify.GetFundraisingEventsSupportingCampaigns(events[0]!.id, 100);
const stJudeOnly = await filterCampaignsByCause(supporting, SJ_CAUSE_ID, {
// Legacy Play Live events whose upstream cause attribution drifted.
alwaysIncludeEventIds: ALWAYS_INCLUDE_SJ_FUNDRAISING_EVENT_IDS,
});
console.log(`${stJudeOnly.length}/${supporting.length} campaigns are for St. Jude`);
@playlive/tiltify-core/backoff is pure and dependency-free — the same policy
PerformRequest uses internally.
import { nextRetrySleepMs } from "@playlive/tiltify-core/backoff";
// Server said "Retry-After: 2" → 2000ms, capped at DEFAULT_MAX_BACKOFF_MS.
nextRetrySleepMs("2", 0, Date.now()); // 2000
// No header → exponential + jitter for the 3rd retry (attempt is 0-based).
nextRetrySleepMs(null, 2, Date.now(), { baseMs: 500, jitterMs: 0 }); // 2000
Realtime traffic for the same campaign is available over Tiltify's Phoenix
Channels gateway via @playlive/tiltify-phoenix, which uses this
client for its slug → fact-id lookups.
MIT © St. Jude Children's Research Hospital