Created
November 6, 2015 23:58
-
-
Save camwhite/16820dbe29d40737cb2c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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