@fanno/webrtc-client
Version:
A TypeScript-based WebRTC client SDK built on top of LiveKit for real-time video and audio communication
358 lines (351 loc) • 11.8 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
Config: () => Config,
Logger: () => Logger,
RoomClient: () => RoomClient,
createRoomClient: () => createRoomClient,
createRoomClientWithConfig: () => createRoomClientWithConfig,
setLogLevel: () => setLogLevel
});
module.exports = __toCommonJS(index_exports);
// src/client/RoomClient.ts
var import_livekit_client = require("livekit-client");
// src/utils/logger.ts
var Logger = class {
/**
* Set the logging level
*/
static setLogLevel(level) {
this.logLevel = level;
}
/**
* Log debug messages
*/
static debug(message, ...args) {
if (this.shouldLog("debug")) {
console.debug(`[WebRTC-SDK] ${message}`, ...args);
}
}
/**
* Log info messages
*/
static info(message, ...args) {
if (this.shouldLog("info")) {
console.info(`[WebRTC-SDK] ${message}`, ...args);
}
}
/**
* Log warning messages
*/
static warn(message, ...args) {
if (this.shouldLog("warn")) {
console.warn(`[WebRTC-SDK] ${message}`, ...args);
}
}
/**
* Log error messages
*/
static error(message, error, ...args) {
if (this.shouldLog("error")) {
console.error(`[WebRTC-SDK] ${message}`, error, ...args);
}
}
/**
* Check if we should log at the given level
*/
static shouldLog(level) {
const levels = ["debug", "info", "warn", "error"];
return levels.indexOf(level) >= levels.indexOf(this.logLevel);
}
};
Logger.logLevel = "info";
// src/utils/config.ts
var Config = class {
/**
* Get LiveKit API key from environment variables (optional for browser environments)
*/
static getLiveKitApiKey() {
if (typeof process === "undefined" || !process.env) {
return void 0;
}
return process.env.LIVEKIT_API_KEY;
}
/**
* Get LiveKit API secret from environment variables (optional for browser environments)
*/
static getLiveKitApiSecret() {
if (typeof process === "undefined" || !process.env) {
return void 0;
}
return process.env.LIVEKIT_API_SECRET;
}
/**
* Get LiveKit server URL from environment variables with fallback
*/
static getLiveKitUrl() {
if (typeof process === "undefined" || !process.env) {
return "ws://localhost:7881";
}
return process.env.LIVEKIT_URL || "ws://localhost:7881";
}
/**
* Get all LiveKit configuration from environment (browser-safe)
*/
static getLiveKitConfig() {
return {
url: this.getLiveKitUrl(),
apiKey: this.getLiveKitApiKey(),
apiSecret: this.getLiveKitApiSecret()
};
}
/**
* Validate that all required environment variables are present for server-side usage
* Note: This is optional in browser environments where a tokenProvider should be used instead
*/
static validateEnvironment() {
const apiKey = this.getLiveKitApiKey();
const apiSecret = this.getLiveKitApiSecret();
if (!apiKey || !apiSecret) {
throw new Error("LIVEKIT_API_KEY and LIVEKIT_API_SECRET environment variables are required for server-side token generation. In browser environments, provide a custom tokenProvider instead.");
}
}
};
// src/client/RoomClient.ts
var RoomClient = class extends import_livekit_client.Room {
constructor(config) {
const roomOptions = {
adaptiveStream: true,
dynacast: true,
...config.roomOptions
};
super(roomOptions);
this.connectionStatus = "disconnected";
this.config = config;
this.setupEventListeners();
Logger.info("RoomClient initialized with config", { url: config.url });
}
/**
* Setup event listeners for room state management
*/
setupEventListeners() {
this.on(import_livekit_client.RoomEvent.Connected, () => {
this.connectionStatus = "connected";
Logger.info("Successfully connected to room");
});
this.on(import_livekit_client.RoomEvent.Disconnected, (reason) => {
this.connectionStatus = "disconnected";
Logger.info("Disconnected from room", { reason });
});
this.on(import_livekit_client.RoomEvent.Reconnecting, () => {
this.connectionStatus = "connecting";
Logger.info("Reconnecting to room...");
});
this.on(import_livekit_client.RoomEvent.ParticipantConnected, (participant) => {
Logger.info("Participant joined", { identity: participant.identity });
});
this.on(import_livekit_client.RoomEvent.ParticipantDisconnected, (participant) => {
Logger.info("Participant left", { identity: participant.identity });
});
this.on(import_livekit_client.RoomEvent.ConnectionQualityChanged, (quality, participant) => {
Logger.debug("Connection quality changed", {
quality,
participant: participant?.identity || "local"
});
});
this.on(import_livekit_client.RoomEvent.TrackSubscribed, (track, publication, participant) => {
Logger.debug("Track subscribed", {
trackKind: track.kind,
participant: participant.identity
});
});
this.on(import_livekit_client.RoomEvent.TrackUnsubscribed, (track, publication, participant) => {
Logger.debug("Track unsubscribed", {
trackKind: track.kind,
participant: participant.identity
});
});
}
/**
* Connect to a LiveKit room with the given parameters
*/
async connectToRoom(params) {
try {
Logger.info("Attempting to connect to room", {
roomName: params.roomName,
participantIdentity: params.participantIdentity
});
const token = await this.generateAccessToken(params);
const connectOptions = {
autoSubscribe: true,
...this.config.connectOptions
};
await this.connect(this.config.url, token, connectOptions);
Logger.info("Successfully connected to room", {
roomName: params.roomName,
participantCount: this.numParticipants
});
} catch (error) {
this.connectionStatus = "disconnected";
Logger.error("Failed to connect to room", error);
throw error;
}
}
/**
* Disconnect from the current room
*/
async disconnectFromRoom() {
try {
Logger.info("Disconnecting from room...");
await this.disconnect();
this.connectionStatus = "disconnected";
Logger.info("Successfully disconnected from room");
} catch (error) {
Logger.error("Error during disconnection", error);
throw error;
}
}
/**
* Get current connection status
*/
getConnectionStatus() {
return this.connectionStatus;
}
/**
* Generate access token for room connection
* Note: In a production environment, this should be done on the server side
*/
async generateAccessToken(params) {
if (this.config.tokenProvider) {
Logger.info("Using custom token provider");
return await this.config.tokenProvider(params);
}
if (!this.config.apiKey || !this.config.apiSecret) {
throw new Error("API key and secret are required for server-side token generation, or provide a custom tokenProvider");
}
try {
if (typeof window !== "undefined") {
throw new Error("Server-side token generation is not available in browser environments. Please provide a tokenProvider function in your config that calls your server endpoint.");
}
const { AccessToken } = await import("livekit-server-sdk");
const token = new AccessToken(this.config.apiKey, this.config.apiSecret, {
identity: params.participantIdentity,
metadata: params.participantMetadata
});
token.addGrant({
room: params.roomName,
roomJoin: true,
canPublish: true,
canSubscribe: true,
canPublishData: true
});
return token.toJwt();
} catch (error) {
Logger.warn("Using fallback token generation - implement server-side token generation for production");
const payload = {
iss: this.config.apiKey,
sub: params.participantIdentity,
exp: Math.floor(Date.now() / 1e3) + 3600,
// 1 hour
room: params.roomName,
metadata: params.participantMetadata || ""
};
return btoa(JSON.stringify(payload));
}
}
/**
* Get room information
*/
getRoomInfo() {
return {
name: this.name,
participantCount: this.numParticipants,
connectionState: this.state,
connectionStatus: this.connectionStatus,
metadata: this.metadata
};
}
/**
* Get local participant information
*/
getLocalParticipant() {
if (!this.localParticipant) {
return null;
}
return {
identity: this.localParticipant.identity,
name: this.localParticipant.name,
metadata: this.localParticipant.metadata,
isSpeaking: this.localParticipant.isSpeaking,
audioTracks: Array.from(this.localParticipant.audioTrackPublications.values()),
videoTracks: Array.from(this.localParticipant.videoTrackPublications.values())
};
}
/**
* Get all remote participants information
*/
getRemoteParticipants() {
return Array.from(this.remoteParticipants.values()).map((participant) => ({
identity: participant.identity,
name: participant.name,
metadata: participant.metadata,
isSpeaking: participant.isSpeaking,
audioTracks: Array.from(participant.audioTrackPublications.values()),
videoTracks: Array.from(participant.videoTrackPublications.values())
}));
}
};
function createRoomClient() {
try {
Config.validateEnvironment();
const config = Config.getLiveKitConfig();
const roomClient = new RoomClient(config);
Logger.info("WebRTC client SDK initialized successfully");
return roomClient;
} catch (error) {
Logger.error("Failed to create room client", error);
throw new Error(`Failed to initialize WebRTC client: ${error instanceof Error ? error.message : "Unknown error"}`);
}
}
function createRoomClientWithConfig(config) {
try {
const roomClient = new RoomClient(config);
Logger.info("WebRTC client SDK initialized with custom config");
return roomClient;
} catch (error) {
Logger.error("Failed to create room client with custom config", error);
throw new Error(`Failed to initialize WebRTC client: ${error instanceof Error ? error.message : "Unknown error"}`);
}
}
// src/index.ts
function setLogLevel(level) {
Logger.setLogLevel(level);
}