mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-27 12:04:56 +02:00
144 lines
4.6 KiB
TypeScript
144 lines
4.6 KiB
TypeScript
//
|
|
// Copyright © 2025 Hardcore Engineering Inc.
|
|
//
|
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License. You may
|
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
//
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
|
|
import { concatLink, MeasureContext, systemAccountUuid, WorkspaceUuid } from '@hcengineering/core'
|
|
import { generateToken } from '@hcengineering/server-token'
|
|
import { AccessToken, EgressInfo } from 'livekit-server-sdk'
|
|
import config from './config'
|
|
|
|
// Example:
|
|
// {
|
|
// "roomId": "RM_ROOM_ID",
|
|
// "roomName": "w-room-name",
|
|
// "numParticipants": 2,
|
|
// "bandwidth": "1000",
|
|
// "connectionMinutes": "120",
|
|
// "startTime": "2025-01-01T12:00:00Z",
|
|
// "endTime": "2025-01-01T13:00:00Z",
|
|
// "participants": [ ... ]
|
|
// }
|
|
interface LiveKitSession {
|
|
roomId: string
|
|
roomName: string
|
|
startTime: string
|
|
endTime: string
|
|
bandwidth: string
|
|
connectionMinutes: string
|
|
}
|
|
|
|
async function getLiveKitSession (ctx: MeasureContext, sessionId: string): Promise<LiveKitSession> {
|
|
const token = await createAnalyticsToken()
|
|
const endpoint = `https://cloud-api.livekit.io/api/project/${config.LiveKitProject}/sessions/${sessionId}?v=2`
|
|
try {
|
|
const response = await fetch(endpoint, {
|
|
method: 'GET',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
'Content-Type': 'application/json'
|
|
}
|
|
})
|
|
|
|
if (!response.ok) {
|
|
ctx.error('failed to get session analytics', { session: sessionId, status: response.status })
|
|
throw new Error(`HTTP error: ${response.status} ${response.statusText}`)
|
|
}
|
|
|
|
return (await response.json()) as LiveKitSession
|
|
} catch (error) {
|
|
throw new Error('Failed to get session analytics')
|
|
}
|
|
}
|
|
|
|
const createAnalyticsToken = async (): Promise<string> => {
|
|
const at = new AccessToken(config.ApiKey, config.ApiSecret, { ttl: '10m' })
|
|
at.addGrant({ roomList: true })
|
|
|
|
return await at.toJwt()
|
|
}
|
|
|
|
export async function saveLiveKitSessionBilling (ctx: MeasureContext, sessionId: string): Promise<void> {
|
|
if (config.BillingUrl === '' || config.LiveKitProject === '') {
|
|
return
|
|
}
|
|
|
|
const session = await getLiveKitSession(ctx, sessionId)
|
|
const workspace = session.roomName.split('_')[0] as WorkspaceUuid
|
|
const endpoint = concatLink(config.BillingUrl, `/api/v1/billing/${workspace}/livekit/session`)
|
|
|
|
const token = generateToken(systemAccountUuid, workspace)
|
|
|
|
try {
|
|
const res = await fetch(endpoint, {
|
|
method: 'POST',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
sessionId,
|
|
sessionStart: session.startTime,
|
|
sessionEnd: session.endTime,
|
|
bandwidth: Number(session.bandwidth),
|
|
minutes: Number(session.connectionMinutes),
|
|
room: session.roomName
|
|
})
|
|
})
|
|
if (!res.ok) {
|
|
throw new Error(await res.text())
|
|
}
|
|
} catch (err: any) {
|
|
ctx.error('failed to save session billing', { workspace, session, err })
|
|
throw new Error('Failed to save session billing: ' + err)
|
|
}
|
|
}
|
|
|
|
export async function saveLiveKitEgressBilling (ctx: MeasureContext, egress: EgressInfo): Promise<void> {
|
|
if (config.BillingUrl === '') {
|
|
return
|
|
}
|
|
|
|
const egressStart = Number(egress.startedAt) / 1000 / 1000
|
|
const egressEnd = Number(egress.endedAt) / 1000 / 1000
|
|
const duration = (egressEnd - egressStart) / 1000
|
|
|
|
const workspace = egress.roomName.split('_')[0] as WorkspaceUuid
|
|
const endpoint = concatLink(config.BillingUrl, `/api/v1/billing/${workspace}/livekit/egress`)
|
|
|
|
const token = generateToken(systemAccountUuid, workspace)
|
|
|
|
try {
|
|
const res = await fetch(endpoint, {
|
|
method: 'POST',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
room: egress.roomName,
|
|
egressId: egress.egressId,
|
|
egressStart: new Date(egressStart).toISOString(),
|
|
egressEnd: new Date(egressEnd).toISOString(),
|
|
duration
|
|
})
|
|
})
|
|
if (!res.ok) {
|
|
throw new Error(await res.text())
|
|
}
|
|
} catch (err: any) {
|
|
ctx.error('failed to save egress billing', { workspace, egress, err })
|
|
throw new Error('Failed to save egress billing: ' + err)
|
|
}
|
|
}
|