Skip to content

Instantly share code, notes, and snippets.

@camwhite
Created November 6, 2015 23:58
Show Gist options
  • Save camwhite/16820dbe29d40737cb2c to your computer and use it in GitHub Desktop.
Save camwhite/16820dbe29d40737cb2c to your computer and use it in GitHub Desktop.
import {ComponentMetadata as Component, ViewMetadata as View, CORE_DIRECTIVES} from 'angular2/angular2';
import {RouteParams, onDeactivate} from 'angular2/router';
import {Socket} from 'services/socket';
import {WebRTC} from 'services/webrtc';
import {Upload} from 'components/upload';
@Component({
selector: 'hideoout',
prodivers: [RouteParams, Socket, WebRTC],
lifecycle: [onDeactivate]
})
@View({
templateUrl: '/components/hideout.html',
directives: [CORE_DIRECTIVES, Upload]
})
export class Hideout {
constructor(routeParams: RouteParams, socket: Socket, webRTC: WebRTC) {
this.routeParams = routeParams;
this.socket = socket;
this.webRTC = webRTC;
this.users = ['foo', 'bar'];
this.room = this.routeParams.params.id;
this.socket.unSync();
this.socket.init(this.room);
this.socket.emit('user:status', this.room);
this.socket.on('status:changed', (users) => {
let currentUsers = [];
for(var key in users) {
currentUsers.push(key);
}
this.users = currentUsers;
});
this.socket.on('msg', (call) => {
this.webRTC.handleCall(call);
});
this.socket.on('data', (file) => {
this.file = {
name: file.name,
size: file.size,
formattedSize: file.formattedSize
};
this.webRTC.file = this.file;
this.webRTC.receiveProgress.max = file.size;
});
}
onDeactivate(next: ComponentInstruction, prev: ComponentInstruction) {
this.socket.leave(this.room);
this.webRTC.sendChannel.close();
}
}
import {ComponentMetadata as Component, ViewMetadata as View, bind, bootstrap} from 'angular2/angular2';
import {ROUTER_BINDINGS, RouteConfig, RouterOutlet, LocationStrategy, PathLocationStrategy} from 'angular2/router';
import {Location} from 'services/location';
import {WebRTC} from 'services/webrtc';
import {Socket} from 'services/socket';
import {Piratexchange} from 'components/piratexchange';
import {Hideout} from 'components/hideout';
@Component({
selector: 'main',
viewBindings: [Location, WebRTC, Socket]
})
@View({
directives: [RouterOutlet],
template: `
<router-outlet></router-outlet>
`
})
@RouteConfig([
{ path: '/', as: 'piratexchange', component: Piratexchange },
{ path: '/hideout/:id', as: 'hideout', component: Hideout }
])
class Main {
constructor() {
}
}
bootstrap(Main, [ROUTER_BINDINGS, bind(LocationStrategy).toClass(PathLocationStrategy)]);
import {Socket} from 'services/socket'
export class Location {
constructor(socket: Socket) {
this.socket = socket;
}
getLocation() {
var promise = new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition((pos) => {
this.socket.emit('user:located', {
lat: pos.coords.latitude,
lng: pos.coords.longitude
});
resolve(pos.coords);
}, (err) => {
reject(err);
});
});
return promise;
}
}
import {ComponentMetadata as Component, ViewMetadata as View, CORE_DIRECTIVES} from 'angular2/angular2';
import {Router} from 'angular2/router';
import {Location} from 'services/location';
import {Socket} from 'services/socket';
import {SvgIcon} from 'components/svg-icon';
@Component({
selector: 'piratexchange',
prodivers: [Router, Location, Socket]
})
@View({
templateUrl: '/components/piratexchange.html',
directives: [CORE_DIRECTIVES, SvgIcon]
})
export class Piratexchange {
constructor(router: Router, locator: Location, socket: Socket) {
this.router = router;
this.locator = locator;
this.socket = socket;
this.me = {};
this.users = [];
this.distances = [];
this.isChrome = /chrome/i.test(navigator.userAgent);
this.locator.getLocation()
.then((coords) => {
this.me.location = {
lat: coords.latitude,
lng: coords.longitude
}
})
.catch((err) => console.log(err));
this.socket.on('push:location', (user) => {
this.users.push(user);
});
this.socket.on('users:matched', (match) => {
this.router.navigate(`/hideout/${match.to}${match.from}`);
});
this.socket.on('user:left', (user) => {
let index = this.users.indexOf(user);
this.users.splice(index, 1);
});
}
matchmaking() {
let geo = google.maps.geometry.spherical;
let p1 = new google.maps.LatLng(this.me.location.lat, this.me.location.lng);
this.users.map((user) => {
let p2 = new google.maps.LatLng(user.pos.lat, user.pos.lng);
let distance = geo.computeDistanceBetween(p1, p2);
this.distances.push({id: user.id, distance: distance});
});
this.match = this.distances.length == 0 ? undefined : this.distances.reduce((prev, curr) => {
return (Math.abs(curr.distance) < Math.abs(prev.distance) ? curr : prev);
});
if(this.match != undefined) {
this.me.id = this.socket.socket.id;
this.socket.emit('match:made', {from: this.me.id, to: this.match.id});;
this.router.navigate(`/hideout/${this.match.id}${this.me.id}`);
}
else {
this.noMatches = true;
this.time = 5;
let countdown = setInterval(() => {
this.time--;
if(this.time == 0) {
clearInterval(countdown);
this.matchmaking();
}
}, 1000);
}
}
}
import Io from 'lib/socket.io';
export class Socket {
socket = Io.connect();
emit(evt, payload) {
this.socket.emit(evt, payload);
}
on(evt, cb) {
this.socket.on(evt, (payload) => {
cb(payload);
});
}
init(room) {
this.socket.emit('join', room);
}
leave(room) {
this.socket.emit('leave', room);
}
unSync() {
this.socket.removeAllListeners();
}
}
import {ComponentMetadata as Component, ViewMetadata as View, ElementRef, Attribute} from 'angular2/angular2';
@Component({
selector: 'svg-icon'
})
@View({
template: `
<object type="image/svg+xml" class="icon"></object>
`
})
export class SvgIcon {
constructor(elemRef: ElementRef) {
this.el = elemRef.nativeElement.children[0];
this.source = elemRef.nativeElement.getAttribute('src')
this.el.setAttribute('data', this.source);
}
}
import {ComponentMetadata as Component, ViewMetadata as View, ElementRef} from 'angular2/angular2';
import {RouteParams} from 'angular2/router';
import {WebRTC} from 'services/webrtc';
@Component({
selector: 'upload',
providers: [RouteParams, WebRTC]
})
@View({
template: `<input type="file"></input>
<progress max="0" value="0"></progress>
<progress max="0" value="0"></progress>
<div class="download"></div>`
})
export class Upload {
constructor(elemRef: ElementRef, routeParams: RouteParams, webRTC: WebRTC) {
this.routeParams = routeParams;
this.el = elemRef.nativeElement;
this.webRTC = webRTC;
this.webRTC.init(this.routeParams.params, this.el);
}
}
import {Socket} from 'services/socket';
export class WebRTC {
constructor(socket: Socket) {
this.socket = socket;
this.currentUserId = this.socket.socket.id;
this.servers = {'iceServers': [
{'url': 'stun:stun.ekiga.net'}
]};
this.peerConnections = {};
this.file = {};
this.receiveBuffer = [];
this.receivedSize = 0;
this.percentLoaded = 0;
}
init(params, elem) {
this.params = params.id;
this.room = this.params;
let firstId = this.params.split('').splice(0, 20).join('');
let secondId = this.params.split('').splice(20, 20).join('');
if(this.currentUserId != firstId) {
this.matchId = firstId;
}
else {
this.matchId = secondId;
}
this.input = elem.children[0];
this.sendProgress = elem.children[1];
this.receiveProgress = elem.children[2];
this.download = elem.children[3];
this.linkEl = document.createElement('a');
this.input.addEventListener('change', () => {
if(this.caller != undefined) {
this.sendData();
}
else {
this.makeOffer();
}
}, false);
}
getPeerConnection(id) {
if(this.peerConnections[id]) {
return this.peerConnections[id];
}
let pc = new RTCPeerConnection(this.servers);
this.peerConnections[id] = pc;
pc.onicecandidate = (evt) => {
if(evt.candidate != null) {
this.socket.emit('msg', {room: this.room, by: this.currentUserId, to: id, ice: evt.candidate, type: 'ice'});
}
}
pc.ondatachannel = (evt) => {
this.receiveChannel = evt.channel;
this.receiveChannel.binaryType = 'arraybuffer';
this.receiveChannel.onmessage = (evt) => {
this.receiveBuffer.push(evt.data);
this.receivedSize += evt.data.byteLength;
this.receiveProgress.value = this.receivedSize;
if (this.receivedSize == this.file.size) {
pc.getStats(null, (stats) => console.log(stats));
let received = new Blob(this.receiveBuffer);
this.receivedSize = 0;
this.receiveBuffer = [];
var link = this.linkEl.cloneNode();
link.href = URL.createObjectURL(received);
link.download = this.file.name;
let text =`<p>Save yer treasure ${this.file.name} - ${this.file.formattedSize}</p>`;
link.innerHTML = text;
this.download.appendChild(link);
}
};
}
this.sendChannel = pc.createDataChannel('Send Channel', {ordered: false, reliable: false});
this.sendChannel.binaryType = 'arraybuffer';
this.sendChannel.onopen = (evt) => {
console.log('send', evt);
if (this.sendChannel.readyState === 'open') {
this.sendData();
}
};
this.sendChannel.onclose = () => {
console.log('channel closed');
this.receiveChannel.close();
this.peerConnections = {};
pc.close();
pc = null;
};
return pc;
}
makeOffer() {
let id = this.matchId;
let pc = this.getPeerConnection(id);
pc.createOffer(sdp => {
pc.setLocalDescription(sdp, () => {
this.socket.emit('msg', {room: this.room, by: this.currentUserId, to: id, type: 'sdp-offer', sdp: sdp})
});
}, (err) => console.log(err));
}
handleCall(call) {
var pc = this.getPeerConnection(call.by);
switch(call.type) {
case 'sdp-offer':
pc.setRemoteDescription(new RTCSessionDescription(call.sdp), () => {
console.log('Setting remote description by offer');
this.caller = true;
pc.createAnswer(sdp => {
pc.setLocalDescription(sdp);
this.socket.emit('msg', {room: this.room, by: call.to, to: call.by, sdp: sdp, type: 'sdp-answer'});
}, (err) => console.log(err));
}, (err) => console.log(err));
break;
case 'sdp-answer':
pc.setRemoteDescription(new RTCSessionDescription(call.sdp), () => {
console.log('Setting remote description by answer');
this.caller = false;
}, (err) => console.error(err));
break;
case 'ice':
if (call.ice) {
console.log('Adding ice candidates');
pc.addIceCandidate(new RTCIceCandidate(call.ice));
}
break;
}
}
sendData() {
let file = this.input.files[0];
if(file == undefined) {
return;
}
this.sendProgress.max = file.size;
let formatBytes = (bytes, decimals) => {
var k = 1000;
var dm = decimals + 1 || 3;
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
var i = Math.floor(Math.log(bytes) / Math.log(k));
return (bytes / Math.pow(k, i)).toPrecision(dm) + ' ' + sizes[i];
}
let formattedBytes = formatBytes(file.size, 3);
this.socket.emit('sending:data', {name: file.name, size: file.size, formattedSize: formattedBytes, room: this.room});
const chunkSize = 16384;
const bufferMax = 15396992;
let count = 0;
let sliceFile = (offset) => {
let reader = new FileReader();
reader.onload = (e) => {
this.sendChannel.send(e.target.result);
let bufferCheck = this.sendChannel.bufferedAmount > bufferMax;
if (file.size > offset + e.target.result.byteLength && bufferCheck) {
setTimeout(sliceFile, count+=10, offset + chunkSize);
}
else if (file.size > offset + e.target.result.byteLength) {
setTimeout(sliceFile, 0, offset + chunkSize);
}
this.sendProgress.value = offset + e.target.result.byteLength;
};
let slice = file.slice(offset, offset + chunkSize);
reader.readAsArrayBuffer(slice);
};
sliceFile(0);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment