Skip to content

Commit 7ef703d

Browse files
chrisleclaude
andcommitted
Optimize channel data fetching with fast fetch() path
- Try direct fetch() API call first (instant, no Puppeteer) - Fall back to Puppeteer only if Cloudflare protection detected (403) - Dramatically improves connection speed in Docker environments - Reduces resource usage and startup time Previously, all channel data fetches required launching Puppeteer browser, which was slow in Docker. Now fetch() is tried first, only falling back to Puppeteer when necessary to bypass Cloudflare. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 7ec1564 commit 7ef703d

File tree

1 file changed

+52
-2
lines changed

1 file changed

+52
-2
lines changed

src/apis/private/channelData.ts

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type { LaunchOptions, Browser } from "puppeteer";
77
import { setupPuppeteer } from "./utils";
88

99
/**
10-
* Fetch channel data from Kick.com API using Puppeteer
10+
* Fetch channel data from Kick.com API using fetch (fast) or Puppeteer fallback (slow)
1111
* @param channel - Channel name to fetch data for
1212
* @param puppeteerOptions - Optional Puppeteer launch options
1313
* @returns Promise resolving to channel info or null if not found
@@ -17,6 +17,56 @@ export const getChannelData = async (
1717
channel: string,
1818
puppeteerOptions?: LaunchOptions,
1919
): Promise<KickChannelInfo | null> => {
20+
// Try direct fetch first (fast path, no Puppeteer)
21+
try {
22+
const response = await fetch(`https://kick.com/api/v2/channels/${channel}`, {
23+
headers: {
24+
'Accept': 'application/json',
25+
'User-Agent': 'Mozilla/5.0 (compatible; kick-js/1.0)'
26+
}
27+
});
28+
29+
if (response.status === 404) {
30+
throw new Error(`Channel '${channel}' does not exist on Kick.com`);
31+
}
32+
33+
if (response.status === 403) {
34+
// Cloudflare protection - fall back to Puppeteer
35+
console.debug(`Cloudflare protection detected, falling back to Puppeteer for ${channel}`);
36+
return await getChannelDataWithPuppeteer(channel, puppeteerOptions);
37+
}
38+
39+
if (response.ok) {
40+
const data = await response.json() as KickChannelInfo;
41+
return data;
42+
}
43+
44+
// Other HTTP errors - fall back to Puppeteer
45+
console.debug(`HTTP ${response.status}, falling back to Puppeteer for ${channel}`);
46+
return await getChannelDataWithPuppeteer(channel, puppeteerOptions);
47+
} catch (error: unknown) {
48+
const errorMessage = error instanceof Error ? error.message : String(error);
49+
// If it's a known error, throw it
50+
if (
51+
errorMessage.includes("Channel not found") ||
52+
errorMessage.includes("does not exist") ||
53+
errorMessage.includes("Request forbidden")
54+
) {
55+
throw error;
56+
}
57+
// Otherwise fall back to Puppeteer
58+
console.debug(`Fetch failed (${errorMessage}), falling back to Puppeteer for ${channel}`);
59+
return await getChannelDataWithPuppeteer(channel, puppeteerOptions);
60+
}
61+
};
62+
63+
/**
64+
* Fetch channel data using Puppeteer (slower, but bypasses Cloudflare)
65+
*/
66+
async function getChannelDataWithPuppeteer(
67+
channel: string,
68+
puppeteerOptions?: LaunchOptions,
69+
): Promise<KickChannelInfo | null> {
2070
let browser: Browser | undefined;
2171
try {
2272
const setup = await setupPuppeteer(puppeteerOptions);
@@ -80,4 +130,4 @@ export const getChannelData = async (
80130
await browser.close();
81131
}
82132
}
83-
};
133+
}

0 commit comments

Comments
 (0)