move livekit client to herolib

This commit is contained in:
timurgordon
2025-01-22 23:55:18 +00:00
parent 03e5a56d62
commit b3509611a2
9 changed files with 265 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
!!hero_code.generate_client
name:'livekit'
classname:'LivekitClient'
singleton:0
default:1
reset:0

View File

@@ -0,0 +1,9 @@
module livekit
// App struct with `livekit.Client`, API keys, and other shared data
pub struct Client {
pub:
url string @[required]
api_key string @[required]
api_secret string @[required]
}

View File

@@ -0,0 +1,6 @@
module livekit
pub fn new(client Client) Client {
return Client{...client}
}

View File

@@ -0,0 +1,25 @@
# livekit
To get started
```vlang
import freeflowuniverse.herolib.clients.livekit
mut client:= livekit.get()!
client...
```
## example heroscript
```hero
!!livekit.configure
livekit_url:''
livekit_api_key:''
livekit_api_secret:''
```

View File

@@ -0,0 +1,51 @@
module livekit
import net.http
import json
@[params]
pub struct ListRoomsParams {
names []string
}
pub struct ListRoomsResponse {
pub:
rooms []Room
}
pub fn (c Client) list_rooms(params ListRoomsParams) !ListRoomsResponse {
// Prepare request body
request := params
request_json := json.encode(request)
// create token and give grant to list rooms
mut token := c.new_access_token()!
token.grants.video.room_list = true
// make POST request
url := '${c.url}/twirp/livekit.RoomService/ListRooms'
// Configure HTTP request
mut headers := http.new_header_from_map({
http.CommonHeader.authorization: 'Bearer ${token.to_jwt()!}',
http.CommonHeader.content_type: 'application/json'
})
response := http.fetch(http.FetchConfig{
url: url
method: .post
header: headers
data: request_json
})!
if response.status_code != 200 {
return error('Failed to list rooms: $response.status_code')
}
// Parse response
rooms_response := json.decode(ListRoomsResponse, response.body) or {
return error('Failed to parse response: $err')
}
return rooms_response
}

View File

@@ -0,0 +1,33 @@
module livekit
import net.http
import json
pub struct Codec {
pub:
fmtp_line string
mime string
}
pub struct Version {
pub:
ticks u64
unix_micro string
}
pub struct Room {
pub:
active_recording bool
creation_time string
departure_timeout int
empty_timeout int
enabled_codecs []Codec
max_participants int
metadata string
name string
num_participants int
num_publishers int
sid string
turn_password string
version Version
}

View File

@@ -0,0 +1,22 @@
module livekit
import os
import freeflowuniverse.herolib.osal
fn testsuite_begin() ! {
osal.load_env_file('${os.dir(@FILE)}/.env')!
}
fn new_test_client() Client {
return new(
url: os.getenv('LIVEKIT_URL')
api_key: os.getenv('LIVEKIT_API_KEY')
api_secret: os.getenv('LIVEKIT_API_SECRET')
)
}
fn test_client_list_rooms() ! {
client := new_test_client()
rooms := client.list_rooms()!
panic(rooms)
}

View File

@@ -0,0 +1,36 @@
module livekit
import time
import rand
import crypto.hmac
import crypto.sha256
import encoding.base64
import json
// Define AccessTokenOptions struct
@[params]
pub struct AccessTokenOptions {
pub mut:
ttl int | string // TTL in seconds or a time span (e.g., '2d', '5h')
name string // Display name for the participant
identity string // Identity of the user
metadata string // Custom metadata to be passed to participants
}
// Constructor for AccessToken
pub fn (client Client) new_access_token(options AccessTokenOptions) !AccessToken {
ttl := if options.ttl is int { options.ttl } else { 21600 } // Default TTL of 6 hours (21600 seconds)
return AccessToken{
api_key: client.api_key
api_secret: client.api_secret
identity: options.identity
ttl: ttl
grants: ClaimGrants{
exp: time.now().unix()+ttl
iss: client.api_key
sub: options.name
name: options.name
}
}
}

View File

@@ -0,0 +1,76 @@
module livekit
import time
import rand
import crypto.hmac
import crypto.sha256
import encoding.base64
import json
// Struct representing grants
pub struct ClaimGrants {
pub mut:
video VideoGrant
iss string
exp i64
nbf int
sub string
name string
}
// VideoGrant struct placeholder
pub struct VideoGrant {
pub mut:
room string
room_join bool @[json: 'roomJoin']
room_list bool @[json: 'roomList']
can_publish bool @[json: 'canPublish']
can_publish_data bool @[json: 'canPublishData']
can_subscribe bool @[json: 'canSubscribe']
}
// SIPGrant struct placeholder
struct SIPGrant {}
// AccessToken class
pub struct AccessToken {
mut:
api_key string
api_secret string
grants ClaimGrants
identity string
ttl int | string
}
// Method to add a video grant to the token
pub fn (mut token AccessToken) add_video_grant(grant VideoGrant) {
token.grants.video = grant
}
// Method to generate a JWT token
pub fn (token AccessToken) to_jwt() !string {
// Create JWT payload
payload := json.encode(token.grants)
println('payload: ${payload}')
// Create JWT header
header := '{"alg":"HS256","typ":"JWT"}'
// Encode header and payload in base64
header_encoded := base64.url_encode_str(header)
payload_encoded := base64.url_encode_str(payload)
// Create the unsigned token
unsigned_token := '${header_encoded}.${payload_encoded}'
// Create the HMAC-SHA256 signature
signature := hmac.new(token.api_secret.bytes(), unsigned_token.bytes(), sha256.sum, sha256.block_size)
// Encode the signature in base64
signature_encoded := base64.url_encode(signature)
// Create the final JWT
jwt := '${unsigned_token}.${signature_encoded}'
return jwt
}