npm.io
0.0.2-beta.8 • Published 6d ago

@eternalsocial/sdk

Licence
MIT
Version
0.0.2-beta.8
Deps
0
Size
804 kB
Vulns
0
Weekly
104

@eternalsocial/sdk

TypeScript SDK for the Eternal Social Scraper API.

Installation

npm install @eternalsocial/sdk
# or
pnpm add @eternalsocial/sdk

Usage

import { createClient } from "@eternalsocial/sdk";

const client = createClient({
  apiKey: "your-api-key",
});

// Synchronous request - returns data directly
const profile = await client.instagram.getProfile({ username: "instagram" });

// Async request with webhook - returns immediately
const asyncResult = await client.instagram.getProfile({
  username: "instagram",
  webhookUrl: "https://your-webhook.com/callback",
});

The SDK automatically narrows return types based on whether you provide a webhookUrl:

  • Without webhook: Returns response with full data
  • With webhook: Returns immediately with processing status

Client API

const client = createClient({ apiKey: "your-api-key" });

// Platform methods
client.instagram.*   // getProfile, getFeed, getReels, getPost, getStories, etc.
client.tiktok.*      // getProfile, getPosts, getVideo, getVideoComments
client.youtube.*     // getChannel, getVideo, getShorts, getVideos
client.facebook.*    // getProfile, getReels, getReel
client.kwai.*        // getProfile, getPosts, getPost

// Webhook utilities
client.consumeWebhook(data)   // Parse and validate incoming webhook payload
client.VALID_WEBHOOK_TYPES    // Array of valid webhook type strings

Error Handling

The SDK provides typed errors for different failure scenarios:

import {
  createClient,
  EternalSocialError,
  EternalSocialNetworkError,
  EternalSocialAPIError,
  EternalSocialValidationError,
} from "@eternalsocial/sdk";

const client = createClient({ apiKey: "your-api-key" });

try {
  const reels = await client.instagram.getReels({ 
    username: "instagram", 
    maxReels: 50 
  });
} catch (error) {
  if (error instanceof EternalSocialValidationError) {
    // Invalid request parameters (e.g., maxReels > 100)
    console.error(error.message);
    // "Validation error on 'maxReels': Too big: expected number to be <=100"
    console.error(error.validationErrors);
    // [{ path: ['maxReels'], message: '...', code: 'too_big' }]
    
  } else if (error instanceof EternalSocialAPIError) {
    // API returned an error
    console.error(error.code);      // "RATE_LIMITED", "UNAUTHORIZED", etc.
    console.error(error.statusCode); // 429, 401, etc.
    console.error(error.requestId);  // For support tickets
    
    if (error.isRateLimited) {
      // Wait and retry
    }
    if (error.isAuthError) {
      // Check API key
    }
    if (error.isRetryable) {
      // Server error (5xx) - safe to retry
    }
    
  } else if (error instanceof EternalSocialNetworkError) {
    // Network failure (connection, DNS, timeout)
    // Always retryable
    console.error(error.message);
  }
}
Error Types
Error Class When Retryable
EternalSocialValidationError Invalid request parameters No - fix input
EternalSocialAPIError API returned an error Depends on status
EternalSocialNetworkError Network failure Yes
API Error Helpers

EternalSocialAPIError provides helper getters:

Getter Status Code Description
isAuthError 401 Invalid API key
isForbidden 403 Insufficient permissions
isNotFound 404 Resource not found
isRateLimited 429 Rate limit exceeded
isRetryable 5xx Server error, safe to retry

Examples

// Instagram
const feed = await client.instagram.getFeed({ username: "instagram", maxPosts: 10 });
const reels = await client.instagram.getReels({ username: "instagram", maxReels: 10 });
const post = await client.instagram.getPost({ postUrl: "https://instagram.com/p/ABC123" });
const comments = await client.instagram.getPostComments({ postUrl: "https://instagram.com/p/ABC123" });
const followers = await client.instagram.getFollowers({ username: "instagram", maxFollowers: 100 });
const stories = await client.instagram.getStories({ username: "instagram" });

// TikTok
const tiktokProfile = await client.tiktok.getProfile({ username: "tiktok" });
const posts = await client.tiktok.getPosts({ username: "tiktok", maxPosts: 10 });
const video = await client.tiktok.getVideo({ videoUrl: "https://tiktok.com/@user/video/123" });

// YouTube
const channel = await client.youtube.getChannel({ channelUrl: "https://youtube.com/@MrBeast" });
const shorts = await client.youtube.getShorts({ channelUrl: "https://youtube.com/@MrBeast" });
const videos = await client.youtube.getVideos({ channelUrl: "https://youtube.com/@MrBeast" });
const video = await client.youtube.getVideo({ videoUrl: "https://youtube.com/watch?v=abc123" });

// Facebook
const fbProfile = await client.facebook.getProfile({ username: "meta" });
const fbReels = await client.facebook.getReels({ username: "meta" });

// Kwai
const kwaiProfile = await client.kwai.getProfile({ username: "kwai" });
const kwaiPosts = await client.kwai.getPosts({ username: "kwai" });

Webhooks

For long-running requests, use webhooks to receive results asynchronously.

Tracking Requests

When you make an async request, store the requestId to correlate with the webhook response:

const client = createClient({ apiKey: "your-api-key" });

// Option 1: Store requestId from response
const { requestId } = await client.instagram.getProfile({
  username: "instagram",
  webhookUrl: "https://your-app.com/webhook",
});
await db.pendingRequests.create({ requestId, username: "instagram" });

// Option 2: Add custom query params to webhook URL
const userId = "user_123";
const jobId = "job_456";
await client.instagram.getProfile({
  username: "instagram",
  webhookUrl: `https://your-app.com/webhook?userId=${userId}&jobId=${jobId}`,
});
Handling Webhooks
import { createClient, EternalSocialValidationError } from "@eternalsocial/sdk";

const client = createClient({ apiKey: "your-api-key" });

app.post("/webhook", async (req, res) => {
  // Option 2: Extract custom query params
  const { userId, jobId } = req.query;
  
  try {
    const payload = client.consumeWebhook(req.body);
    
    // Option 1: Look up original request context by requestId
    const context = await db.pendingRequests.findByRequestId(payload.requestId);
    
    if (payload.status === "failed") {
      console.error(`Request ${payload.requestId} failed:`, payload.error);
      return res.status(200).send("ok");
    }
    
    // Narrow by type to get typed data
    switch (payload.type) {
      case "INSTAGRAM_PROFILE":
        // payload.data is now InstagramUserData
        console.log(payload.data.username);
        console.log(payload.data.full_name);
        console.log(payload.data.follower_count);
        break;
        
      case "INSTAGRAM_FEED":
      case "INSTAGRAM_REELS":
      case "INSTAGRAM_HASHTAG":
      case "INSTAGRAM_TAGGED":
        // payload.data is InstagramMediaItem[]
        for (const post of payload.data) {
          console.log(post.id, post.like_count, post.caption);
        }
        break;
        
      case "INSTAGRAM_FOLLOWERS":
      case "INSTAGRAM_FOLLOWING":
        // payload.data is InstagramUser[]
        for (const user of payload.data) {
          console.log(user.username, user.full_name);
        }
        break;
        
      case "TIKTOK_PROFILE":
        // payload.data is TikTokUserData
        console.log(payload.data.user.uniqueId);
        console.log(payload.data.stats.followerCount);
        break;
        
      case "TIKTOK_POSTS":
        // payload.data is TikTokItem[]
        for (const video of payload.data) {
          console.log(video.id, video.desc, video.stats.playCount);
        }
        break;
        
      case "YOUTUBE_CHANNEL_INFO":
        // payload.data is YouTubeChannelData
        console.log(payload.data.title);
        console.log(payload.data.subscriberCount);
        break;
        
      // ... handle other types
    }
    
    // Common fields available on all successful payloads
    console.log(payload.requestId);     // Correlate with original request
    console.log(payload.itemCount);     // Number of items returned
    console.log(payload.creditsCharged); // Credits used
    
    res.status(200).send("ok");
  } catch (error) {
    if (error instanceof EternalSocialValidationError) {
      // Invalid webhook payload structure
      console.error("Invalid webhook:", error.message);
    }
    res.status(400).send("invalid payload");
  }
});
Webhook Payload Types

All successful webhook payloads share these fields:

Field Type Description
status "completed" Always "completed" for success
type string Request type (e.g., "INSTAGRAM_PROFILE")
data varies The scraped data (type depends on type)
requestId string Correlate with your original request
itemCount number? Number of items returned
creditsCharged number? Credits charged for this request

Failed webhook payloads have:

Field Type Description
status "failed" Always "failed" for errors
type string Which request type failed
error string Error message
requestId string Correlate with your original request

Development

pnpm install
pnpm generate  # Regenerate client from OpenAPI
pnpm build     # Generate + build (ESM/CJS + types)

License

MIT

Keywords