vact_sdk 2.2.2
vact_sdk: ^2.2.2 copied to clipboard
Dependency-free one-to-one voice and video calling for VACT. Plain HTTPS plus WebRTC.
vact_sdk #
Secure one-to-one voice & video calling for Flutter — pure HTTPS + WebRTC, no Firebase.
New to VACT? Create a free account at vact.online — you get ₹100 of calling credit to build with, no card required. Full guides and API reference: vact.online/docs.html.
Install #
dependencies:
vact_sdk: ^2.2.0
flutter_webrtc: ^1.5.2 # RTCVideoRenderer / RTCVideoView
permission_handler: ^12.0.3 # camera & microphone prompts
flutter pub get
Platform setup #
Calls need the camera and microphone. Do this before writing call code, and ask for permission on a screen before the first call (requesting mid-call often makes the first connection fail).
Android #
android/app/build.gradle — minSdkVersion 23 (WebRTC requires 23+). Then in
android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
iOS #
ios/Runner/Info.plist — minimum deployment target 13.0:
<key>NSCameraUsageDescription</key>
<string>Used for video calls</string>
<key>NSMicrophoneUsageDescription</key>
<string>Used for calls</string>
Request at runtime #
import 'package:permission_handler/permission_handler.dart';
await [Permission.microphone, Permission.camera].request();
Security boundary #
- Only the public App ID (
vact_app_…) goes in the app. - The App Secret (
vact_live_…) stays in your backend secret manager. - Your backend authenticates its own user, chooses the
userId, and mints a five-minute one-time access token. Hand that token to the app andconnect. - The package ships no App Secret, TURN token, or customer data, and never logs tokens, SDP or candidates.
Connect and call #
import 'package:vact_sdk/vact_sdk.dart';
final vact = Vact(appId: 'vact_app_your_public_app_id');
// Fetch this one-time token from YOUR authenticated backend.
await vact.connect(accessToken: accessTokenFromYourBackend);
final call = await vact.call('user_42', video: true);
call.states.listen((state) => print(state)); // never print credentials
Show the video #
Bind the renderers, and re-attach the remote renderer when the remote track arrives — it appears after the call starts, so a renderer bound to an empty stream stays black without this:
final remote = RTCVideoRenderer();
await remote.initialize();
remote.srcObject = call.remoteStream;
call.onRemoteTrack = () { // fires when the remote media attaches
setState(() => remote.srcObject = call.remoteStream);
};
// RTCVideoView(remote) … and RTCVideoView(localRenderer, mirror: true)
Receive calls #
incomingCalls() emits the current set of ringing calls each time it changes.
Handle one at a time and de-dupe by id, so a reconnect can't stack prompts:
String? prompting;
vact.incomingCalls().listen((calls) async {
if (prompting != null || calls.isEmpty) return;
final incoming = calls.first;
prompting = incoming.id;
final answer = await showAnswerDialog(incoming); // your UI
prompting = null;
final call = answer ? await vact.accept(incoming) : null;
if (answer == false) await vact.decline(incoming);
});
The host app owns FCM/APNs push for calls that arrive while it is closed, Android full-screen call presentation, and iOS PushKit/CallKit. Configure a signed webhook (see the server SDK) and push from your own project.
Backend token endpoint (runs on YOUR server) #
app.post('/api/vact-token', requireYourLogin, async (req, res) => {
const r = await fetch(
`https://vact.online/v1/apps/${process.env.VACT_APP_ID}/tokens`,
{ method: 'POST',
headers: { Authorization: `Bearer ${process.env.VACT_APP_SECRET}`,
'Content-Type': 'application/json' },
body: JSON.stringify({ userId: req.user.id }) }); // never trust a client userId
const body = await r.json();
res.status(r.status).json(r.ok ? { accessToken: body.accessToken } : { error: 'unavailable' });
});
Never ship the App Secret in the app or in client-side config — it is recoverable from a built binary.
Runnable examples (Node & Firebase backends): https://github.com/danishktabbas/vact-example · Docs: https://vact.online/docs.html · Sign up: https://vact.online