Bild Carousel V2
This commit is contained in:
@@ -3,6 +3,17 @@ import { db } from '@/lib/db';
|
||||
export const TIKTOK_OAUTH_STATE_COOKIE_NAME = 'tiktok_oauth_state';
|
||||
export const TIKTOK_ACCOUNT_KEY = 'hermes-agent';
|
||||
|
||||
export class TiktokApiError extends Error {
|
||||
status: number;
|
||||
body?: unknown;
|
||||
|
||||
constructor(message: string, status = 502, body?: unknown) {
|
||||
super(message);
|
||||
this.status = status;
|
||||
this.body = body;
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh when the access token expires within this window, so Hermes never
|
||||
// receives a token that dies mid-upload.
|
||||
const REFRESH_BUFFER_MS = 5 * 60 * 1000;
|
||||
@@ -58,16 +69,12 @@ export async function getValidTiktokTokens() {
|
||||
});
|
||||
}
|
||||
|
||||
const REFRESH_BUFFER_MS = 5 * 60 * 1000;
|
||||
|
||||
export async function getLiveTiktokAccessToken() {
|
||||
const integration = await db.tiktokIntegration.findUnique({
|
||||
where: { accountKey: TIKTOK_ACCOUNT_KEY },
|
||||
});
|
||||
if (!integration) {
|
||||
const error = new Error('No TikTok account connected.');
|
||||
error.status = 404;
|
||||
throw error;
|
||||
throw new TiktokApiError('No TikTok account connected.', 404);
|
||||
}
|
||||
|
||||
if (integration.accessTokenExpiresAt.getTime() - Date.now() > REFRESH_BUFFER_MS) {
|
||||
@@ -77,12 +84,14 @@ export async function getLiveTiktokAccessToken() {
|
||||
const clientKey = process.env.TIKTOK_CLIENT_KEY;
|
||||
const clientSecret = process.env.TIKTOK_CLIENT_SECRET;
|
||||
if (!clientKey || !clientSecret) {
|
||||
const error = new Error('TikTok client credentials are not configured.');
|
||||
error.status = 500;
|
||||
throw error;
|
||||
throw new TiktokApiError('TikTok client credentials are not configured.', 500);
|
||||
}
|
||||
|
||||
return getValidTiktokTokens();
|
||||
const refreshed = await getValidTiktokTokens();
|
||||
if (!refreshed) {
|
||||
throw new TiktokApiError('No TikTok account connected.', 404);
|
||||
}
|
||||
return refreshed;
|
||||
}
|
||||
|
||||
export async function tiktokApi(url: string, options: RequestInit = {}) {
|
||||
@@ -109,10 +118,7 @@ export async function tiktokApi(url: string, options: RequestInit = {}) {
|
||||
|
||||
if (!res.ok || (data?.error as Record<string, string> | undefined)?.code !== 'ok') {
|
||||
const message = (data?.error as Record<string, string> | undefined)?.message || (typeof data?.raw === 'string' ? data.raw : '') || `TikTok API error: ${res.status}`;
|
||||
const error = new Error(message);
|
||||
error.status = res.status;
|
||||
error.body = data;
|
||||
throw error;
|
||||
throw new TiktokApiError(message, res.status, data);
|
||||
}
|
||||
|
||||
return data;
|
||||
@@ -125,14 +131,12 @@ export async function uploadBinaryToTiktok(uploadUrl: string, buffer: Buffer, mi
|
||||
'Content-Type': mimeType,
|
||||
'Content-Length': String(buffer.length),
|
||||
},
|
||||
body: buffer,
|
||||
body: new Uint8Array(buffer),
|
||||
});
|
||||
|
||||
const text = await res.text();
|
||||
if (!res.ok && res.status !== 201) {
|
||||
const error = new Error(`TikTok binary upload failed: ${res.status} ${text}`);
|
||||
error.status = res.status;
|
||||
throw error;
|
||||
throw new TiktokApiError(`TikTok binary upload failed: ${res.status} ${text}`, res.status);
|
||||
}
|
||||
|
||||
return { status: res.status, body: text };
|
||||
|
||||
Reference in New Issue
Block a user