feat(ui): add Button, Modal, Spinner, Toast, and Tooltip components with styles
All checks were successful
Build and Release / build-and-release (push) Successful in 13m12s
All checks were successful
Build and Release / build-and-release (push) Successful in 13m12s
- Implemented Button component with various props for customization. - Created Modal component with header, content, and footer subcomponents. - Added Spinner component for loading indicators. - Developed Toast component for displaying notifications. - Introduced Tooltip component for contextual hints with keyboard shortcuts. - Added corresponding CSS modules for styling each component. - Updated index file to export new components. - Configured TypeScript settings for the UI package.
This commit is contained in:
@@ -2,9 +2,24 @@
|
||||
|
||||
import { action } from "./_generated/server";
|
||||
import { v } from "convex/values";
|
||||
import { AccessToken } from "livekit-server-sdk";
|
||||
import { AccessToken, RoomServiceClient } from "livekit-server-sdk";
|
||||
|
||||
// Generate LiveKit token for voice channel
|
||||
/**
|
||||
* Generate a LiveKit join token for a voice channel.
|
||||
*
|
||||
* LiveKit servers run with `room.auto_create: false` reject joins for
|
||||
* rooms that don't already exist — the client gets a 404 "requested
|
||||
* room does not exist" back from the /rtc/v1/validate endpoint. To
|
||||
* make this deployment-agnostic, we pre-create the room via the
|
||||
* LiveKit Server SDK before minting the token. When auto-create is
|
||||
* enabled the `createRoom` call is idempotent (409 Conflict is
|
||||
* swallowed silently), so the same code path works on both
|
||||
* configurations.
|
||||
*
|
||||
* Requires `LIVEKIT_URL` (or the frontend's `VITE_LIVEKIT_URL` as a
|
||||
* fallback) in the Convex environment so the RoomServiceClient
|
||||
* can talk to the LiveKit API.
|
||||
*/
|
||||
export const getToken = action({
|
||||
args: {
|
||||
channelId: v.string(),
|
||||
@@ -15,6 +30,46 @@ export const getToken = action({
|
||||
handler: async (_ctx, args) => {
|
||||
const apiKey = process.env.LIVEKIT_API_KEY || "devkey";
|
||||
const apiSecret = process.env.LIVEKIT_API_SECRET || "secret";
|
||||
const livekitUrl =
|
||||
process.env.LIVEKIT_URL || process.env.VITE_LIVEKIT_URL || "";
|
||||
|
||||
// Ensure the room exists. The LiveKit API accepts `http(s)` URLs
|
||||
// for the management endpoint, but the frontend connect URL is a
|
||||
// `wss://` — swap the scheme when needed.
|
||||
if (livekitUrl) {
|
||||
const httpUrl = livekitUrl
|
||||
.replace(/^wss:\/\//i, "https://")
|
||||
.replace(/^ws:\/\//i, "http://");
|
||||
try {
|
||||
const roomService = new RoomServiceClient(httpUrl, apiKey, apiSecret);
|
||||
await roomService.createRoom({
|
||||
name: args.channelId,
|
||||
// Empty rooms auto-destroy after 5 minutes with no participants,
|
||||
// matching LiveKit's own default so stale rooms from a crashed
|
||||
// client don't pile up forever.
|
||||
emptyTimeout: 5 * 60,
|
||||
// 50 participants is plenty for a voice channel in this
|
||||
// single-server deployment and keeps any runaway join loop
|
||||
// from hitting the global limit.
|
||||
maxParticipants: 50,
|
||||
});
|
||||
} catch (err: any) {
|
||||
// 409 / "already exists" is expected when a room has already
|
||||
// been created by an earlier join — swallow it and continue.
|
||||
const message = String(err?.message ?? err ?? "");
|
||||
const status = err?.status ?? err?.statusCode;
|
||||
const alreadyExists =
|
||||
status === 409 ||
|
||||
/already exists/i.test(message) ||
|
||||
/AlreadyExists/i.test(message);
|
||||
if (!alreadyExists) {
|
||||
// Non-fatal: log and fall through to token generation. If the
|
||||
// real issue was misconfiguration the client will surface the
|
||||
// 404 it already does.
|
||||
console.warn("LiveKit createRoom failed:", message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const at = new AccessToken(apiKey, apiSecret, {
|
||||
identity: args.userId,
|
||||
|
||||
Reference in New Issue
Block a user