Client SDK Realtime Session
Realtime sessions integrated into your application use one of Gabber's Client SDKs. There are two steps involved:
- Generating connection details
- Connecting to a Realtime Session
Generating Connection Details
Realtime sessions can be started using the startRealtimeSession API.
A successful response contains connection_details
which are used by a Gabber Client SDK to connect to a WebRTC session.
example connection details:
{
"url": "<connection url to initiate the Gabber WebRTC session>",
"token": "<connection token to authenticate the Gabber WebRTC"
}
Here's a minimal example Express server that starts a session and returns connection details:
Connecting to a Realtime Session
A Gabber Client SDK is used to connect to a Realtime Session once connection details have been generated.
Install SDK
- NPM
- HTML + Javascript
- Swift
npm install gabber-client-core
If using react, install the React SDK as well:
npm install gabber-client-react
<html>
<head>
<script src="https://unpkg.com/gabber-client-core/dist/index.iife.js"></script>
</head>
<body>
</body>
</html>
Javascript basic
Add the Gabber Swift Client SDK to your Xcode packages:
https://github.com/gabber-dev/sdk-swift.git
Connect
- React
- HTML + Javascript
- Swift
import React, { useEffect, useState } from 'react';
import { RealtimeSessionEngineProvider } from 'gabber-client-react';
import { SDKConnectOptions } from 'gabber-client-core';
function App() {
const [connectionDetails, setConnectionDetails] = useState<RealtimeSessionConnectionDetails | null>(null);
const connect = async () => {
const response = await fetch('http://localhost:4000/connection_details');
const connectionDetails = await response.json();
setConnectionDetails(connectionDetails);
}
if (!connectionDetails) {
return (
<div>
<button onClick={connect}></button>
</div>
);
return null
}
return (
<RealtimeSessionEngineProvider connectionOpts={{connection_details: connectionDetails}}>
<AppInner />
</RealtimeSessionEngineProvider>
)
}
function AppInner() {
const {
id,
agentState,
connectionState,
lastError,
agentVolumeBands,
agentVolume,
userVolumeBands,
userVolume,
remainingSeconds,
messages,
sendChatMessage,
transcription,
microphoneEnabled,
setMicrophoneEnabled,
canPlayAudio,
startAudio,
} = useRealtimeSessionEngine();
return (
<div>
<button onClick={async () => {
await setMicrophoneEnabled(!microphoneEnabled);
}}>Toggle Mute</button>
</div>
)
}
<html>
<head>
<script src="https://unpkg.com/gabber-client-core/dist/index.iife.js"></script>
<script>
var realtimeSessionEngine = null;
var microphoneEnabled = false;
function getConnectionDetails() {
return fetch('http://localhost:4000/connection_details')
.then(response => response.json());
}
function onAgentError(error) {
console.error(error);
}
function onAgentStateChanged(state) {
console.log(state);
}
function onRemainingSecondsChanged(seconds) {
console.log(seconds);
}
function onUserVolumeChanged(volumeBands, volume) {
console.log(volume);
}
function onAgentVolumeChanged(volumeBands, volume) {
console.log(volume);
}
function onConnectionStateChanged(state) {
console.log(state);
}
function onMessagesChanged(messages) {
console.log(messages);
}
function onMicrophoneChanged(enabled) {
microphoneEnabled = enabled;
}
function onCanPlayAudioChanged(canPlayAudio) {
console.log(canPlayAudio);
}
function connect() {
getConnectionDetails().then(connectionDetails => {
realtimeSessionEngine = new Gabber.RealtimeSessionEngine({
connectionDetails,
onAgentError,
onAgentStateChanged,
onRemainingSecondsChanged,
onUserVolumeChanged,
onAgentVolumeChanged,
onConnectionStateChanged,
onMessagesChanged,
onMicrophoneChanged,
onCanPlayAudioChanged
});
return realtimeSessionEngine.connect({connection_details: connectionDetails});
});
}
function toggleMute() {
if (realtimeSessionEngine) {
realtimeSessionEngine.setMicrophoneEnabled(!microphoneEnabled);
}
}
</script>
</head>
<body>
<button onclick="connect()">Connect</button>
<button onClick="toggleMute()">Toggle Mute</button>
</body>
</html>
import Foundation
import Gabber
import Combine
import AVFoundation
class AppViewModel: ObservableObject, SessionDelegate {
@Published var inputMessage: String = ""
@Published var connectionState: ConnectionState = .notConnected
@Published var agentState: AgentState = .warmup
@Published var microphoneEnabled: Bool = false
@Published var remainingSeconds: Float?
@Published var agentVolume: (bands: [Float], volume: Float) = ([], 0)
@Published var userVolume: (bands: [Float], volume: Float) = ([], 0)
private lazy var session: Session = {
return Session(delegate: self)
}()
private func generateConnectionDetails() async throws -> RealtimeSessionConnectionDetails {
print("Generating token")
// Define the URL for the token endpoint
guard let url = URL(string: "http://localhost:4000/connection_details") else {
throw NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"])
}
// Prepare the URL request
var request = URLRequest(url: url)
request.httpMethod = "GET"
do {
// Perform the network request
let (data, _) = try await URLSession.shared.data(for: request)
// Try to parse the JSON response
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
let token = json["token"] as? String,
let url = json["url"] as? String {
return token
} else {
throw NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Token not found in response"])
}
} catch {
throw NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Network error: \(error.localizedDescription)"])
}
}
func connect() async throws {
guard let connDetails = try await generateConnectionDetails() else {
throw NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid connection details"])
}
checkMicrophonePermission()
try await session.connect(opts: .connectionDetails(connDetails))
}
func toggleMicrophone() async throws {
try await session.setMicrophone(enabled: !microphoneEnabled)
}
// MARK: - GabberDelegate methods
func ConnectionStateChanged(state: ConnectionState) {
print("ConnectionStateChanged: \(state)")
DispatchQueue.main.async {
self.connectionState = state
}
}
func MessagesChanged(messages: [SessionTranscription]) {
print("MessagesChanged: \(messages)")
DispatchQueue.main.async {
self.messages = messages
}
}
func MicrophoneStateChanged(enabled: Bool) {
print("MicrophoneStateChanged: \(enabled)")
DispatchQueue.main.async {
self.microphoneEnabled = enabled
}
}
func AgentStateChanged(_ state: AgentState) {
print("AgentStateChanged: \(state)")
DispatchQueue.main.async {
self.agentState = state
}
}
func AgentVolumeChanged(bands: [Float], volume: Float) {
print("AgentVolumeChanged: Volume: \(volume)")
DispatchQueue.main.async {
self.agentVolume = (bands, volume)
}
}
func UserVolumeChanaged(bands: [Float], volume: Float) {
print("UserVolumeChanaged: Volume: \(volume)")
DispatchQueue.main.async {
self.userVolume = (bands, volume)
}
}
func RemainingSecondsChange(seconds: Float) {
print("RemainingSecondsChange: \(seconds)")
DispatchQueue.main.async {
self.remainingSeconds = seconds
}
}
func AgentError(msg: String) {
print("AgentError: \(msg)")
}
func checkMicrophonePermission() {
do {
try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
switch AVAudioSession.sharedInstance().recordPermission {
case .granted:
print("Microphone permission granted")
case .denied:
print("Microphone permission denied")
case .undetermined:
print("Microphone permission not determined")
AVAudioSession.sharedInstance().requestRecordPermission { granted in
print("Microphone permission \(granted ? "granted" : "denied")")
}
@unknown default:
print("Unknown microphone permission status")
}
} catch {
print("Error setting up audio session: \(error.localizedDescription)")
}
}
}