@happyvertical/smrt-social
Social media account management with OAuth, post scheduling, and analytics tracking across YouTube, Threads, X, and Bluesky.
v0.20.44OAuthPost SchedulingMulti-Platform
Overview
smrt-social manages social media connections and publishing across multiple platforms. It handles OAuth credential storage, post creation and scheduling, and per-post analytics tracking with PKCE support for secure OAuth flows.
Installation
bash
npm install @happyvertical/smrt-socialQuick Start
typescript
import { SocialAccount, SocialPost, OAuthState } from '@happyvertical/smrt-social';
// Connect a social account
const account = new SocialAccount({
name: 'Bentley News YouTube',
platform: 'youtube',
platformUsername: 'Bentley News',
accessToken: 'encrypted-token',
refreshToken: 'encrypted-refresh',
tokenExpiresAt: new Date('2026-06-01'),
defaultHashtags: ['news', 'local'],
linkBehavior: 'description',
});
await account.save();
// Check readiness before publishing
if (account.isReady) {
const post = new SocialPost({
socialAccountId: account.id,
title: 'Breaking News from Bentley',
description: 'Latest updates from the town council meeting.',
hashtags: ['news', 'local', 'bentley'],
linkUrl: 'https://example.com/article',
scheduledAt: new Date('2026-03-05T18:00:00Z'),
status: 'scheduled',
});
await post.save();
}
// OAuth flow: create state, redirect user, verify callback
const state = new OAuthState({
platform: 'youtube',
state: OAuthState.generateState(),
codeVerifier: OAuthState.generateCodeVerifier(),
redirectUri: 'https://app.example.com/oauth/callback',
scopes: ['youtube.upload', 'youtube.readonly'],
});
await state.save();
// On callback: state.verifyState(callbackState)Core Models
SocialAccount (STI)
typescript
class SocialAccount extends SmrtObject {
name: string
platform: 'youtube' | 'threads' | 'x' | 'bluesky'
platformUsername?: string
accessToken?: string
refreshToken?: string
tokenExpiresAt?: Date
status: 'connected' | 'expired' | 'error'
defaultHashtags?: string[]
linkBehavior: 'description' | 'reply' | 'none'
get isReady(): boolean // active + connected + token + not expired
get isTokenExpired(): boolean // 5-minute buffer before expiry
}SocialPost
typescript
class SocialPost extends SmrtObject {
socialAccountId: string
title?: string
description: string
hashtags?: string[]
linkUrl?: string
scheduledAt?: Date
publishedAt?: Date
status: 'draft' | 'scheduled' | 'publishing' | 'published' | 'failed'
analytics: string // JSON: views, likes, comments, shares, clicks
get isEditable(): boolean // true when draft or failed
get fullText(): string // description + formatted hashtags
}OAuthState (STI)
typescript
class OAuthState extends SmrtObject {
platform: string
state: string // CSRF token
codeVerifier?: string // PKCE code verifier
redirectUri: string
scopes?: string[]
// 10-minute TTL
get isValid(): boolean
verifyState(callback: string): boolean
static generateState(): string
static generateCodeVerifier(): string
static generateCodeChallenge(verifier: string): string // S256
}Best Practices
DOs
- Check
account.isReadybefore attempting to publish - Use
OAuthState.generateState()for CSRF protection - Use PKCE (
generateCodeVerifier/generateCodeChallenge) for OAuth flows - Implement a job runner to trigger publishing at
scheduledAttime - Clean up expired OAuthState records (10-minute TTL)
DON'Ts
- Don't assume auto-publishing (scheduledAt is metadata only -- app must trigger)
- Don't expect analytics to auto-populate (must sync from platform APIs)
- Don't store tokens without encryption (currently plaintext -- integrate smrt-secrets)
- Don't skip the 5-minute token expiry buffer when checking readiness
- Don't extend the platform enum without code changes (hardcoded list)