restream-client.ts•5.58 kB
/**
* Restream API Client
* Handles authentication and API requests to Restream
*/
import axios, { AxiosInstance, AxiosError } from 'axios';
import type {
RestreamConfig,
AuthToken,
Channel,
UserProfile,
Stream,
StreamSettings,
ApiError,
} from './types.js';
export class RestreamClient {
private config: RestreamConfig;
private axiosInstance: AxiosInstance;
private accessToken: string | null = null;
private tokenExpiry: number = 0;
constructor(config: RestreamConfig) {
this.config = config;
this.axiosInstance = axios.create({
baseURL: config.baseUrl,
headers: {
'Content-Type': 'application/json',
},
});
// Add request interceptor to include auth token
this.axiosInstance.interceptors.request.use(async (config) => {
await this.ensureAuthenticated();
if (this.accessToken) {
config.headers.Authorization = `Bearer ${this.accessToken}`;
}
return config;
});
}
/**
* Ensure we have a valid access token
*/
private async ensureAuthenticated(): Promise<void> {
const now = Date.now();
if (this.accessToken && this.tokenExpiry > now) {
return; // Token is still valid
}
await this.authenticate();
}
/**
* Authenticate with Restream API using client credentials
*/
private async authenticate(): Promise<void> {
try {
const response = await axios.post<AuthToken>(
`${this.config.baseUrl}/oauth/token`,
{
grant_type: 'client_credentials',
client_id: this.config.clientId,
client_secret: this.config.clientSecret,
}
);
this.accessToken = response.data.access_token;
this.tokenExpiry = Date.now() + response.data.expires_in * 1000;
} catch (error) {
throw this.handleError(error, 'Authentication failed');
}
}
/**
* Get user profile information
*/
async getUserProfile(): Promise<UserProfile> {
try {
const response = await this.axiosInstance.get<UserProfile>('/user/profile');
return response.data;
} catch (error) {
throw this.handleError(error, 'Failed to fetch user profile');
}
}
/**
* Get all connected channels
*/
async getChannels(): Promise<Channel[]> {
try {
const response = await this.axiosInstance.get<{ channels: Channel[] }>('/user/channels');
return response.data.channels || [];
} catch (error) {
throw this.handleError(error, 'Failed to fetch channels');
}
}
/**
* Get a specific channel by ID
*/
async getChannel(channelId: string): Promise<Channel> {
try {
const response = await this.axiosInstance.get<Channel>(`/user/channels/${channelId}`);
return response.data;
} catch (error) {
throw this.handleError(error, `Failed to fetch channel ${channelId}`);
}
}
/**
* Enable or disable a channel
*/
async updateChannelStatus(channelId: string, enabled: boolean): Promise<Channel> {
try {
const response = await this.axiosInstance.patch<Channel>(
`/user/channels/${channelId}`,
{ enabled }
);
return response.data;
} catch (error) {
throw this.handleError(error, `Failed to update channel ${channelId}`);
}
}
/**
* Get current stream information
*/
async getCurrentStream(): Promise<Stream | null> {
try {
const response = await this.axiosInstance.get<Stream>('/user/stream');
return response.data;
} catch (error) {
if (axios.isAxiosError(error) && error.response?.status === 404) {
return null; // No active stream
}
throw this.handleError(error, 'Failed to fetch current stream');
}
}
/**
* Update stream settings
*/
async updateStreamSettings(settings: StreamSettings): Promise<Stream> {
try {
const response = await this.axiosInstance.patch<Stream>('/user/stream', settings);
return response.data;
} catch (error) {
throw this.handleError(error, 'Failed to update stream settings');
}
}
/**
* Get stream analytics/statistics
*/
async getStreamAnalytics(streamId?: string): Promise<any> {
try {
const endpoint = streamId ? `/streams/${streamId}/analytics` : '/user/analytics';
const response = await this.axiosInstance.get(endpoint);
return response.data;
} catch (error) {
throw this.handleError(error, 'Failed to fetch stream analytics');
}
}
/**
* Start a new stream
*/
async startStream(settings?: StreamSettings): Promise<Stream> {
try {
const response = await this.axiosInstance.post<Stream>('/user/stream/start', settings || {});
return response.data;
} catch (error) {
throw this.handleError(error, 'Failed to start stream');
}
}
/**
* Stop the current stream
*/
async stopStream(): Promise<void> {
try {
await this.axiosInstance.post('/user/stream/stop');
} catch (error) {
throw this.handleError(error, 'Failed to stop stream');
}
}
/**
* Handle API errors and convert to ApiError
*/
private handleError(error: unknown, defaultMessage: string): ApiError {
if (axios.isAxiosError(error)) {
const axiosError = error as AxiosError<any>;
return {
message: axiosError.response?.data?.message || axiosError.message || defaultMessage,
code: axiosError.response?.data?.code,
status: axiosError.response?.status,
};
}
return {
message: error instanceof Error ? error.message : defaultMessage,
};
}
}