Python TypeScript
python/app.py typescript/index.ts Copy
"""Create a video room and join from the browser.
Server side: create a Conference Room resource over REST (it gets a Fabric
address, /public/<name>), then mint a Guest token per participant that can dial
only that address. Browser side (typescript/): the participant dials the room
with audio and video.
Written against signalwire-sdk 3.0.1 (RestClient.fabric) and Flask.
"""
import os
from dotenv import load_dotenv
from flask import Flask , jsonify , request
from signalwire.rest import RestClient
# the SDK does not read .env for you
load_dotenv ()
app = Flask ( __name__ )
client = RestClient ()
ROOM = os . getenv ( "ROOM_NAME" , "team-standup" )
TOKEN_TTL_MINUTES = int ( os . getenv ( "TOKEN_TTL_MINUTES" , "60" ))
def create_room ( name = ROOM ):
"""A Conference Room is a Fabric resource; its address is /public/<name>."""
return client . fabric . conference_rooms . create (
name = name ,
display_name = name . replace ( "-" , " " ) . title (),
max_members = 25 ,
layout = "grid-responsive" ,
quality = "720p" ,
record_on_start = False ,
enable_room_previews = True , # documented as required by the create endpoint
)
def guest_token_for ( room = ROOM , expire_at = None ):
"""Outbound-only token pinned to the room's address (max 10 addresses)."""
body = { "allowed_addresses" : [ f "/public/ { room } " ]}
if expire_at :
body [ "expire_at" ] = expire_at
return client . fabric . tokens . create_guest_token ( ** body )
@app . post ( "/rooms" )
def rooms ():
return jsonify ( create_room (( request . json or {}) . get ( "name" , ROOM )))
@app . post ( "/token" )
def token ():
"""The browser asks for a token; it never sees the project API token."""
room = ( request . json or {}) . get ( "room" , ROOM )
t = guest_token_for ( room )
return jsonify ( token = t [ "token" ], destination = f "/public/ { room } " )
if __name__ == "__main__" :
app . run ( port = int ( os . getenv ( "PORT" , "8080" ))) // Join the room from the browser with the SignalWire Browser SDK v4.
// The page asks our server for a Guest token (python/app.py: POST /token), then
// dials the room's Fabric address with audio and video. The token can reach
// only that address, and the project API token never leaves the server.
import { SignalWire , StaticCredentialProvider } from "@signalwire/js" ;
const ROOM = new URLSearchParams ( location . search ). get ( "room" ) ?? "team-standup" ;
async function joinRoom ( rootElement : HTMLElement ) : Promise < void > {
const res = await fetch ( "/token" , {
method : "POST" ,
headers : { "content-type" : "application/json" },
body : JSON.stringify ({ room : ROOM }),
});
const { token , destination } = ( await res . json ()) as {
token : string ;
destination : string ;
};
const client = new SignalWire ( new StaticCredentialProvider ({ token }));
await client . connect ();
// destination is "/public/<room>"; audio + video makes this a video join.
const call = await client . dial ( destination , { audio : true , video : true });
// Render the mixed room stream (an MCU: one stream per participant).
const video = document . createElement ( "video" );
video . autoplay = true ;
video . playsInline = true ;
rootElement . appendChild ( video );
call . remoteStream$ . subscribe (( stream : MediaStream | undefined ) => {
if ( stream ) video . srcObject = stream ;
});
// Layouts and screen share are call-level controls.
document . getElementById ( "layout" ) ? . addEventListener ( "change" , ( e ) => {
// setLayout(layout, positions): e.g. "grid-responsive", "highlight-1-responsive"
void call . setLayout (( e . target as HTMLSelectElement ). value , {});
});
document . getElementById ( "share" ) ? . addEventListener ( "click" , () => {
void call . self ? . startScreenShare (); // self is null until the call is connected
});
document . getElementById ( "leave" ) ? . addEventListener ( "click" , () => {
void call . hangup ();
});
}
joinRoom ( document . getElementById ( "room" ) ! ). catch (( err ) =>
console . error ( "join failed" , err ),
); Run it cd python && pip install -r requirements.txt
export SIGNALWIRE_SPACE=... SIGNALWIRE_PROJECT_ID=... SIGNALWIRE_API_TOKEN=... ROOM_NAME=team-standup
python app.py
curl -X POST localhost:8080/rooms -H 'content-type: application/json' -d '{"name": "team-standup"}'
cd ../typescript && npm ci && npm start # serve index.ts with a page that has #room, #layout, #share, #leave Proxy /token from the page to the Flask app (or serve both from one origin).
Verify it python verify.py # from the recipe folder, not python/ With the HTTP layer recorded, the two routes must make the documented conference_rooms and guests/tokens requests, checked against tools/openapi/rest.json. The verifier also asserts:
the required enable_room_previews is present the token’s allowed_addresses is exactly the room’s address the project token never appears in the response the TypeScript client connects and dials with the documented calls When typescript/node_modules is present the client is also type-checked with tsc.