Bläddra i källkod

On the way to WebRTC

master
Roderic Day 5 år sedan
förälder
incheckning
30e31ea478
3 ändrade filer med 154 tillägg och 117 borttagningar
  1. +46
    -1
      pico.js
  2. +43
    -39
      pico.py
  3. +65
    -77
      test.py

+ 46
- 1
pico.js Visa fil

@@ -4,6 +4,51 @@ const State = {
users: [],
}

/*
*
* WEBRTC
*
*/
const rpcCall = async (username) => {
const constraints = {audio: true, video: true}
const media = await navigator.mediaDevices.getUserMedia(constraints)
const rpc = new RTCPeerConnection({iceServers: []})
// rpc.oniceconnectionstatechange = (e) => {
// console.log(e)
// }
// rpc.onicecandidate = (e) => {
// e.candidate
// const candidate = new RTCIceCandidate();
// rpc.addIceCandidate(candidate);
// }
// rpc.ontrack = (e) => {
// console.log(e)
// // appendVideo(media)
// }

const makeOffer = async () => {
rpc.addStream(media)
const offer = await rpc.createOffer()
await rpc.setLocalDescription(offer)
return {msgType: 'offer', rsd: rpc.localDescription}
}

const makeAnswer = async (msg) => {
const offer = new RTCSessionDescription(msg.rsd)
await rpc.setRemoteDescription(offer)
const answer = await rpc.createAnswer()
await rpc.setLocalDescription(answer)
return {msgType: 'answer', rsd: rpc.localDescription}
}

const finishShake = async (msg) => {
const answer = new RTCSessionDescription(msg.rsd)
await rpc.setRemoteDescription(answer)

}
finishShake(await makeAnswer(await makeOffer()))
}

/*
*
* GUI
@@ -61,7 +106,7 @@ const Chat = {
),
m('.users',
m('button', {onclick: Login.sendLogout}, 'Logout'),
m('ul.user-list', State.users.map(username => m('li', username)))
m('ul.user-list', State.users.map(username => m('li', username))),
),
)
},

+ 43
- 39
pico.py Visa fil

@@ -3,13 +3,13 @@ import collections
import datetime
import http
import json
import functools
from functools import partial
from pathlib import Path

import websockets


rooms = collections.defaultdict(set)
rooms = collections.defaultdict(dict)


class PicoProtocol(websockets.WebSocketServerProtocol):
@@ -37,10 +37,6 @@ class PicoProtocol(websockets.WebSocketServerProtocol):
return await super().process_request(path, request_headers)


def get_usernames(sockets):
return sorted(ws.username for ws in sockets)


async def send_json_many(targets, **data):
for websocket in list(targets):
await send_json(websocket, **data)
@@ -57,56 +53,64 @@ async def recv_json(websocket):
try:
return json.loads(await websocket.recv())
except websockets.exceptions.ConnectionClosed:
return {'action': 'logout'}
return {'kind': 'logout'}
except json.decoder.JSONDecodeError:
return {}


async def core(ws, path, server_name):
sockets = rooms[path]
room = rooms[path]
usernames = room.keys()
sockets = room.values()
username = None

while True:
data = await recv_json(ws)
ts = datetime.datetime.now().isoformat()
reply = functools.partial(send_json, websocket=ws, ts=ts)
error = functools.partial(reply, kind='error')
broadcast = functools.partial(send_json_many, targets=set(sockets), ts=ts)

if 'action' not in data:
await error(info='Message without action is invalid')

elif data['action'] == 'login':
ws.username = data['username']
if not ws.username:
await error(info='Username not allowed')
emit = partial(send_json_many, kind=data['kind'], value=data.get('value'), ts=ts)
broadcast = partial(emit, targets=sockets)
reply = partial(emit, targets=[ws])
error = partial(reply, kind='error')

if 'kind' not in data:
await error(value='Message without kind is invalid')

elif data['kind'] == 'login':
username = data['value']
if not username:
await error(value='Username not allowed')
break
if ws.username in get_usernames(sockets):
await error(info='Username taken')
if username in usernames:
await error(value='Username taken')
break

sockets.add(ws)
online = get_usernames(sockets)
await reply(kind='update', users=online, info=f'Welcome to {path}', username=ws.username)
await broadcast(kind='update', users=online, info=f'{ws.username} joined')

elif data['action'] == 'post':
text = data['text']
if not text:
continue
elif 'target' in data:
targets = {ss for ss in sockets if ss.username in {ws.username, data['target']}}
await send_json_many(ts=ts, targets=targets, kind='post', source=ws.username, text=text)
else:
await broadcast(kind='post', source=ws.username, text=text)
others = list(sockets)
room[username] = ws
online = list(usernames)
await reply(online=online)
await broadcast(kind='post', value=f'{username} joined', online=online, targets=others)
await reply(kind='post', value=f'Welcome to {path}')

elif data['action'] == 'logout':
sockets.discard(ws)
await broadcast(kind='update', users=get_usernames(sockets), info=f'{ws.username} left')
elif username not in room:
await error(value='Login required')
break

elif data['kind'] == 'logout':
del room[username]
online = list(usernames)
await broadcast(kind='post', value=f'{username} left', online=online)
break

else:
if 'target' in data:
targets = {v for k, v in room.items() if k in {username, data['target']}}
await broadcast(source=username, targets=targets)
else:
await broadcast(source=username)


async def start_server(host, port, server_name):
bound_core = functools.partial(core, server_name=server_name)
bound_core = partial(core, server_name=server_name)
return await websockets.serve(bound_core, host, port, create_protocol=PicoProtocol)



+ 65
- 77
test.py Visa fil

@@ -29,6 +29,8 @@ async def _make_client(path, timeout, script):
if kind == 'recv':
A, B = message, await recv_json(websocket)
B.pop('ts')
if B['kind'] == 'login':
username = B['value']
state.update(message)
if A != B:
error = True
@@ -38,8 +40,8 @@ async def _make_client(path, timeout, script):
elif kind == 'send':
await send_json(websocket, **message)

while 'users' in state:
if min(state['users']) == state['username']:
while 'online' in state:
if state['online'][0] == username:
break
message = await recv_json(websocket)
message.pop('ts')
@@ -69,116 +71,102 @@ async def _test(*clients):

def test_happy_path():
client = _make_client('ws://localhost:8642/A', 0.1, Script()
+ {'action': 'login', 'username': 'TestUser'}
- {'kind': 'update', 'users': ['TestUser'], 'info': 'Welcome to /A', 'username': 'TestUser'}
+ {'action': 'post', 'text': 'Hello World!'}
- {'kind': 'post', 'source': 'TestUser', 'text': 'Hello World!'}
+ {'kind': 'login', 'value': 'TestUser'}
- {'kind': 'login', 'value': 'TestUser', 'online': ['TestUser']}
- {'kind': 'post', 'value': 'Welcome to /A'}
+ {'kind': 'post', 'value': 'Hello World!'}
- {'kind': 'post', 'value': 'Hello World!', 'source': 'TestUser'}
)
return _test(client)


def test_name_taken():
client1 = _make_client('ws://localhost:8642/', 0.10, Script()
+ {'action': 'login', 'username': 'A'}
- {'kind': 'update', 'users': ['A'], 'info': 'Welcome to /', 'username': 'A'}
- {'kind': 'update', 'users': ['A', 'B'], 'info': 'B joined'}
+ {'kind': 'login', 'value': 'A'}
- {'kind': 'login', 'value': 'A', 'online': ['A']}
- {'kind': 'post', 'value': 'Welcome to /'}
- {'kind': 'post', 'value': 'B joined', 'online': ['A', 'B']}
)
client2 = _make_client('ws://localhost:8642/', 0.11, Script()
+ {'action': 'login', 'username': 'A'}
- {'kind': 'error', 'info': 'Username taken'}
+ {'kind': 'login', 'value': 'A'}
- {'kind': 'error', 'value': 'Username taken'}
)
client3 = _make_client('ws://localhost:8642/', 0.12, Script()
+ {'action': 'login', 'username': 'B'}
- {'kind': 'update', 'users': ['A', 'B'], 'info': 'Welcome to /', 'username': 'B'}
- {'kind': 'update', 'users': ['B'], 'info': 'A left'}
+ {'kind': 'login', 'value': 'B'}
- {'kind': 'login', 'value': 'B', 'online': ['A', 'B']}
- {'kind': 'post', 'value': 'Welcome to /'}
- {'kind': 'post', 'value': 'A left', 'online': ['B']}
)
return _test(client1, client2, client3)


def test_interact():
def test_interaction():
client1 = _make_client('ws://localhost:8642/', 0.10, Script()
+ {'action': 'login', 'username': 'Alice'}
- {'kind': 'update', 'users': ['Alice'], 'info': 'Welcome to /', 'username': 'Alice'}
- {'kind': 'update', 'users': ['Alice', 'Bob'], 'info': 'Bob joined'}
+ {'action': 'post', 'text': 'Hey Bob!'}
- {'kind': 'post', 'source': 'Alice', 'text': 'Hey Bob!'}
- {'kind': 'post', 'source': 'Bob', 'text': 'Howdy!'}
+ {'kind': 'login', 'value': 'Alice'}
- {'kind': 'login', 'value': 'Alice', 'online': ['Alice']}
- {'kind': 'post', 'value': 'Welcome to /'}
- {'kind': 'post', 'value': 'Bob joined', 'online': ['Alice', 'Bob']}
+ {'kind': 'post', 'value': 'Hey Bob!'}
- {'kind': 'post', 'value': 'Hey Bob!', 'source': 'Alice'}
- {'kind': 'post', 'value': 'Howdy!', 'source': 'Bob'}
)
client2 = _make_client('ws://localhost:8642/', 0.11, Script()
+ {'action': 'login', 'username': 'Bob'}
- {'kind': 'update', 'users': ['Alice', 'Bob'], 'info': 'Welcome to /', 'username': 'Bob'}
- {'kind': 'post', 'source': 'Alice', 'text': 'Hey Bob!'}
+ {'action': 'post', 'text': 'Howdy!'}
- {'kind': 'post', 'source': 'Bob', 'text': 'Howdy!'}
+ {'action': 'post', 'text': ''}
- {'kind': 'update', 'users': ['Bob'], 'info': 'Alice left'}
+ {'kind': 'login', 'value': 'Bob'}
- {'kind': 'login', 'value': 'Bob', 'online': ['Alice', 'Bob']}
- {'kind': 'post', 'value': 'Welcome to /'}
- {'kind': 'post', 'value': 'Hey Bob!', 'source': 'Alice'}
+ {'kind': 'post', 'value': 'Howdy!'}
- {'kind': 'post', 'value': 'Howdy!', 'source': 'Bob'}
- {'kind': 'post', 'value': 'Alice left', 'online': ['Bob']}
)
return _test(client1, client2)


def test_party():
client1 = _make_client('ws://localhost:8642/', 0.10, Script()
+ {'action': 'login', 'username': 'Norman'}
- {'kind': 'update', 'users': ['Norman'], 'info': 'Welcome to /', 'username': 'Norman'}
- {'kind': 'update', 'users': ['Norman', 'Ray'], 'info': 'Ray joined'}
- {'kind': 'update', 'users': ['Emma', 'Norman', 'Ray'], 'info': 'Emma joined'}
+ {'action': 'post', 'text': 'なに?'}
- {'kind': 'post', 'source': 'Norman', 'text': 'なに?'}
- {'kind': 'update', 'users': ['Norman', 'Ray'], 'info': 'Emma left'}
)
client2 = _make_client('ws://localhost:8642/', 0.11, Script()
+ {'action': 'login', 'username': 'Ray'}
- {'kind': 'update', 'users': ['Norman', 'Ray'], 'info': 'Welcome to /', 'username': 'Ray'}
- {'kind': 'update', 'users': ['Emma', 'Norman', 'Ray'], 'info': 'Emma joined'}
- {'kind': 'post', 'source': 'Norman', 'text': 'なに?'}
- {'kind': 'update', 'users': ['Norman', 'Ray'], 'info': 'Emma left'}
- {'kind': 'update', 'users': ['Ray'], 'info': 'Norman left'}
)
client3 = _make_client('ws://localhost:8642/', 0.12, Script()
+ {'action': 'login', 'username': 'Emma'}
- {'kind': 'update', 'users': ['Emma', 'Norman', 'Ray'], 'info': 'Welcome to /', 'username': 'Emma'}
- {'kind': 'post', 'source': 'Norman', 'text': 'なに?'}
)
return _test(client1, client2, client3)


def test_rooms():
client1 = _make_client('ws://localhost:8642/A', 0.10, Script()
+ {'action': 'login', 'username': 'Dandy'}
- {'kind': 'update', 'users': ['Dandy'], 'info': 'Welcome to /A', 'username': 'Dandy'}
+ {'action': 'post', 'text': 'Hi'}
- {'kind': 'post', 'source': 'Dandy', 'text': 'Hi'}
+ {'kind': 'login', 'value': 'Dandy'}
- {'kind': 'login', 'value': 'Dandy', 'online': ['Dandy']}
- {'kind': 'post', 'value': 'Welcome to /A'}
+ {'kind': 'post', 'value': 'Hi', 'source': 'Dandy'}
- {'kind': 'post', 'value': 'Hi', 'source': 'Dandy'}
)
client2 = _make_client('ws://localhost:8642/B', 0.10, Script()
+ {'action': 'login', 'username': 'Dandy'}
- {'kind': 'update', 'users': ['Dandy'], 'info': 'Welcome to /B', 'username': 'Dandy'}
+ {'action': 'post', 'text': 'Howdy'}
- {'kind': 'post', 'source': 'Dandy', 'text': 'Howdy'}
+ {'kind': 'login', 'value': 'Dandy'}
- {'kind': 'login', 'value': 'Dandy', 'online': ['Dandy']}
- {'kind': 'post', 'value': 'Welcome to /B'}
+ {'kind': 'post', 'value': 'Hi', 'source': 'Dandy'}
- {'kind': 'post', 'value': 'Hi', 'source': 'Dandy'}
)
return _test(client1, client2)


def test_private_message():
client1 = _make_client('ws://localhost:8642/', 0.10, Script()
+ {'action': 'login', 'username': 'Norman'}
- {'kind': 'update', 'users': ['Norman'], 'info': 'Welcome to /', 'username': 'Norman'}
- {'kind': 'update', 'users': ['Norman', 'Ray'], 'info': 'Ray joined'}
- {'kind': 'update', 'users': ['Emma', 'Norman', 'Ray'], 'info': 'Emma joined'}
+ {'action': 'post', 'target': 'Emma', 'text': 'なに?'}
- {'kind': 'post', 'source': 'Norman', 'text': 'なに?'}
- {'kind': 'update', 'users': ['Norman', 'Ray'], 'info': 'Emma left'}
+ {'kind': 'login', 'value': 'Norman'}
- {'kind': 'login', 'value': 'Norman', 'online': ['Norman']}
- {'kind': 'post', 'value': 'Welcome to /'}
- {'kind': 'post', 'value': 'Ray joined', 'online': ['Norman', 'Ray']}
- {'kind': 'post', 'value': 'Emma joined', 'online': ['Norman', 'Ray', 'Emma']}
+ {'kind': 'post', 'value': 'なに?', 'target': 'Emma'}
- {'kind': 'post', 'value': 'なに?', 'source': 'Norman'}
)
client2 = _make_client('ws://localhost:8642/', 0.11, Script()
+ {'action': 'login', 'username': 'Ray'}
- {'kind': 'update', 'users': ['Norman', 'Ray'], 'info': 'Welcome to /', 'username': 'Ray'}
- {'kind': 'update', 'users': ['Emma', 'Norman', 'Ray'], 'info': 'Emma joined'}
- {'kind': 'update', 'users': ['Norman', 'Ray'], 'info': 'Emma left'}
- {'kind': 'update', 'users': ['Ray'], 'info': 'Norman left'}
+ {'kind': 'login', 'value': 'Ray'}
- {'kind': 'login', 'value': 'Ray', 'online': ['Norman', 'Ray']}
- {'kind': 'post', 'value': 'Welcome to /'}
- {'kind': 'post', 'value': 'Emma joined', 'online': ['Norman', 'Ray', 'Emma']}
- {'kind': 'post', 'value': '秘密!', 'source': 'Emma'}
- {'kind': 'post', 'value': 'Norman left', 'online': ['Ray', 'Emma']}
)
client3 = _make_client('ws://localhost:8642/', 0.12, Script()
+ {'action': 'login', 'username': 'Emma'}
- {'kind': 'update', 'users': ['Emma', 'Norman', 'Ray'], 'info': 'Welcome to /', 'username': 'Emma'}
- {'kind': 'post', 'source': 'Norman', 'text': 'なに?'}
+ {'kind': 'login', 'value': 'Emma'}
- {'kind': 'login', 'value': 'Emma', 'online': ['Norman', 'Ray', 'Emma']}
- {'kind': 'post', 'value': 'Welcome to /'}
- {'kind': 'post', 'value': 'なに?', 'source': 'Norman'}
+ {'kind': 'post', 'value': '秘密!', 'target': 'Ray'}
- {'kind': 'post', 'value': '秘密!', 'source': 'Emma'}
- {'kind': 'post', 'value': 'Norman left', 'online': ['Ray', 'Emma']}
- {'kind': 'post', 'value': 'Ray left', 'online': ['Emma']}
)
return _test(client1, client2, client3)


Laddar…
Avbryt
Spara