-const body = document.createElement('body')
-const root = document.createElement('div')
-document.title = "Strapp.io Client"
-const conf = {"iceServers": [{ "urls": "stun:stun.1.google.com:19302" }] }
-let dataChannel
-
-/* TODO: duplicate in both client.js and host.js */
-function getPublicKey() {
- return new Promise( (resolve, reject) => {
- /* Check local storage for public key */
- if (!window.localStorage.getItem('public-key')) {
- /* If doesn't exist, generate public and private key pair, store in
- local storage */
- crypto.subtle.generateKey(
- { name:'RSA-OAEP',
- modulusLength: 2048,
- publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
- hash: {name: "SHA-256"}
- },
- true,
- ['encrypt', 'decrypt']
- ).then((keyPair) => {
- /* TODO: Do we need to store the private key as well? */
- crypto.subtle.exportKey('jwk', keyPair.publicKey)
- .then((exportedKey) => {
- window.localStorage.setItem('publicKey', exportedKey)
- console.log('public key is' + window.localStorage.getItem('publicKey'))
- resolve(exportedKey)
- })
-
- })
- }
- else {
- resolve(window.localStorage.getItem('publicKey'))
- }
-})
-
-}
-
-function postServer(url, data) {
- const request = new XMLHttpRequest()
- request.open('POST', url, true)
- request.setRequestHeader('Content-Type', 'application/json' )
- request.setRequestHeader('X-Strapp-Type', 'ice-candidate-submission')
- request.send(data)
-}
-
-/* TODO: All this does is wrap a function in a promise. Allows pollServerForAnswer
-to call itself recursively with the same promise */
-function pollServer(url, clientPubKey, func) {
- return new Promise((resolve, reject) => {
- func(url, clientPubKey, resolve, reject )
- })
-}
-
-/* Poll the server. Send get request, wait for timeout, send another request.
-Do this until...? Can be used for either reconnecting or waiting for answer*/
-function pollServerForAnswer(url, data, resolve, reject) {
- const request = new XMLHttpRequest()
- request.open('GET', url, true)
- /* But there is no JSON? */
- request.setRequestHeader('Content-Type', 'application/json' )
- request.setRequestHeader('X-Strapp-Type', 'client-sdp-offer')
- request.setRequestHeader('X-Client-Offer', JSON.stringify(data))
- request.onreadystatechange = () => {
- if (request.status === 200) {
- if(request.readyState === 4) {
- console.log('Client: Recieved Answer from Host')
- console.log(request)
- resolve(request.response)
- }
- }
- else if (request.status === 504) {
- console.log('timed out, resending')
- pollServerForAnswer(url, data, resolve, reject)
- }
- else {
- reject('server unhandled response of status ' + request.status)
- }
- }
- request.send()
-}
-
-/* Poll server for ice candidates until ice is complete */
-function pollServerForICECandidate(cpc, url, pubKey) {
- let intervalID = window.setInterval(() => {
- if (cpc.iceConnectionState.localeCompare('connected') !== 0
- && cpc.iceConnectionState.localeCompare('completed') !== 0) {
- console.log('Client: Polling server begin for intervalID = ' + intervalID)
- console.log('Client: Requesting ICE Candidates from server')
- const request = new XMLHttpRequest()
- request.open('GET', url, true)
- request.setRequestHeader('Content-Type', 'application/json' )
- request.setRequestHeader('X-Strapp-Type', 'ice-candidate-request')
- request.setRequestHeader('X-client-pubkey', pubKey)
- request.onreadystatechange = () => {
- if (request.status === 200) {
- if(request.readyState === 4) {
- console.log('Client: Recieved ICE response from Host')
- let response = JSON.parse(request.response)
- switch(response['iceState']) {
- case "a":
- cpc.addIceCandidate(new RTCIceCandidate(response.ice))
- break
- case "g": /* Gathering so let interval keep polling */
- break
- case "c": /* host iceState == Complete, stop bugging it */
- clearInterval(intervalID)
- clearTimeout()
- break
- default:
- console.log('Unhandled iceState in pollServerForICECandidate()' + response['iceState'])
- break
- }
- }
- }
- else {
- console.log('server unhandled response of status ' + request.status)
- clearInterval(intervalID)
- }
- }
- request.send()
- }
- else {
- clearTimeout()
- clearInterval(intervalID)
- }
- }, 5000)
-}
-
-/* Create and send offer -> Send ICE Candidates -> Poll for ICE Candidates */
-getPublicKey().then((cpk) => {
- console.log('Client: Create and send offer')
- const cpc = new RTCPeerConnection(conf)
-
- cpc.oniceconnectionstatechange = () => {
- console.log('iceConnectionState = ' + cpc.iceConnectionState)
- }
-
- cpc.onnegotiationneeded = () => {
- console.log('negotiation needed!')
- cpc.createOffer().then((offer) => {
- return cpc.setLocalDescription(offer)
- })
- .then(() => {
- console.log('Client: Sending offer to host')
- let offer = {
- cmd: '> sdp pubKey',
- sdp: cpc.localDescription,
- pubKey: cpk.n
- }
- return pollServer(window.location, offer, pollServerForAnswer)
- }).then((serverResponse) => {
- const answer = JSON.parse(serverResponse)
- console.log('Client: Polling for ICE candidates')
- pollServerForICECandidate(cpc, window.location, cpk.n)
- cpc.setRemoteDescription(answer.sdp)
- cpc.onicecandidate = (event) => {
- if (event.candidate) {
- console.log('Client: Sending ice candidate to host')
- postServer(window.location, JSON.stringify({
- cmd: '> ice pubkey',
- ice: event.candidate,
- pubKey: cpk.n
- }))
- }
- else {
- console.log('Client: No more Ice Candidates to send')
- }
- }
-
-
- }).catch( (err) => {
- console.log('error in sdp handshake: ' + err)
- })
- }
- /* Start data channel */
- dataChannel = cpc.createDataChannel("sendChannel");
- dataChannel.onmessage = (msg) => {
- /* Get mediaStream from host and add it to the video */
- let video = document.querySelector('')
- }
- dataChannel.onopen = () => {
- dataChannel.send(`Hi from the Client`)
- document.write('<button> Connection with host established! </button> <video autoplay id="screenOutput"></video>')
- }
-
-})
-document.addEventListener('DOMContentLoaded', () => {
-
- document.body.innerHTML = `<button> Setting up connection with host </button>`
-
-});
+const body = document.createElement('body')\r
+const root = document.createElement('div')\r
+document.title = "Strapp.io Client"\r
+const conf = {"iceServers": [{ "urls": "stun:stun.1.google.com:19302" }] }\r
+let dataChannel\r
+let hostScreen\r
+\r
+/* TODO: duplicate in both client.js and host.js */\r
+function getPublicKey() {\r
+ return new Promise( (resolve, reject) => {\r
+ /* Check local storage for public key */\r
+ if (!window.localStorage.getItem('public-key')) {\r
+ /* If doesn't exist, generate public and private key pair, store in\r
+ local storage */\r
+ crypto.subtle.generateKey(\r
+ { name:'RSA-OAEP',\r
+ modulusLength: 2048,\r
+ publicExponent: new Uint8Array([0x01, 0x00, 0x01]),\r
+ hash: {name: "SHA-256"}\r
+ },\r
+ true,\r
+ ['encrypt', 'decrypt']\r
+ ).then((keyPair) => {\r
+ /* TODO: Do we need to store the private key as well? */\r
+ crypto.subtle.exportKey('jwk', keyPair.publicKey)\r
+ .then((exportedKey) => {\r
+ window.localStorage.setItem('publicKey', exportedKey)\r
+ console.log('public key is' + window.localStorage.getItem('publicKey'))\r
+ resolve(exportedKey)\r
+ })\r
+\r
+ })\r
+ }\r
+ else {\r
+ resolve(window.localStorage.getItem('publicKey'))\r
+ }\r
+})\r
+\r
+}\r
+\r
+function postServer(url, data) {\r
+ const request = new XMLHttpRequest()\r
+ request.open('POST', url, true)\r
+ request.setRequestHeader('Content-Type', 'application/json' )\r
+ request.setRequestHeader('X-Strapp-Type', 'ice-candidate-submission')\r
+ request.send(data)\r
+}\r
+\r
+/* TODO: All this does is wrap a function in a promise. Allows pollServerForAnswer\r
+to call itself recursively with the same promise */\r
+function pollServer(url, clientPubKey, func) {\r
+ return new Promise((resolve, reject) => {\r
+ func(url, clientPubKey, resolve, reject )\r
+ })\r
+}\r
+\r
+/* Poll the server. Send get request, wait for timeout, send another request.\r
+Do this until...? Can be used for either reconnecting or waiting for answer*/\r
+function pollServerForAnswer(url, data, resolve, reject) {\r
+ const request = new XMLHttpRequest()\r
+ request.open('GET', url, true)\r
+ /* But there is no JSON? */\r
+ request.setRequestHeader('Content-Type', 'application/json' )\r
+ request.setRequestHeader('X-Strapp-Type', 'client-sdp-offer')\r
+ request.setRequestHeader('X-Client-Offer', JSON.stringify(data))\r
+ request.onreadystatechange = () => {\r
+ if (request.status === 200) {\r
+ if(request.readyState === 4) {\r
+ console.log('Client: Recieved Answer from Host')\r
+ console.log(request)\r
+ resolve(request.response)\r
+ }\r
+ }\r
+ else if (request.status === 504) {\r
+ console.log('timed out, resending')\r
+ pollServerForAnswer(url, data, resolve, reject)\r
+ }\r
+ else {\r
+ reject('server unhandled response of status ' + request.status)\r
+ }\r
+ }\r
+ request.send()\r
+}\r
+\r
+/* Poll server for ice candidates until ice is complete */\r
+function pollServerForICECandidate(cpc, url, pubKey) {\r
+ let intervalID = window.setInterval(() => {\r
+ if (cpc.iceConnectionState.localeCompare('connected') !== 0\r
+ && cpc.iceConnectionState.localeCompare('completed') !== 0) {\r
+ console.log('Client: Polling server begin for intervalID = ' + intervalID)\r
+ console.log('Client: Requesting ICE Candidates from server')\r
+ const request = new XMLHttpRequest()\r
+ request.open('GET', url, true)\r
+ request.setRequestHeader('Content-Type', 'application/json' )\r
+ request.setRequestHeader('X-Strapp-Type', 'ice-candidate-request')\r
+ request.setRequestHeader('X-client-pubkey', pubKey)\r
+ request.onreadystatechange = () => {\r
+ if (request.status === 200) {\r
+ if(request.readyState === 4) {\r
+ console.log('Client: Recieved ICE response from Host')\r
+ let response = JSON.parse(request.response)\r
+ switch(response['iceState']) {\r
+ case "a":\r
+ cpc.addIceCandidate(new RTCIceCandidate(response.ice))\r
+ break\r
+ case "g": /* Gathering so let interval keep polling */\r
+ break\r
+ case "c": /* host iceState == Complete, stop bugging it */\r
+ clearInterval(intervalID)\r
+ clearTimeout()\r
+ break\r
+ default:\r
+ console.log('Unhandled iceState in pollServerForICECandidate()' + response['iceState'])\r
+ break\r
+ }\r
+ }\r
+ }\r
+ else {\r
+ console.log('server unhandled response of status ' + request.status)\r
+ clearInterval(intervalID)\r
+ }\r
+ }\r
+ request.send()\r
+ }\r
+ else {\r
+ clearTimeout()\r
+ clearInterval(intervalID)\r
+ }\r
+ }, 5000)\r
+}\r
+\r
+/* Create and send offer -> Send ICE Candidates -> Poll for ICE Candidates */\r
+getPublicKey().then((cpk) => {\r
+ console.log('Client: Create and send offer')\r
+ const cpc = new RTCPeerConnection(conf)\r
+\r
+ cpc.oniceconnectionstatechange = () => {\r
+ console.log('iceConnectionState = ' + cpc.iceConnectionState)\r
+ }\r
+\r
+ cpc.onnegotiationneeded = () => {\r
+ console.log('negotiation needed!')\r
+ cpc.createOffer().then((offer) => {\r
+ return cpc.setLocalDescription(offer)\r
+ })\r
+ .then(() => {\r
+ console.log('Client: Sending offer to host')\r
+ let offer = {\r
+ cmd: '> sdp pubKey',\r
+ sdp: cpc.localDescription,\r
+ pubKey: cpk.n\r
+ }\r
+ return pollServer(window.location, offer, pollServerForAnswer)\r
+ }).then((serverResponse) => {\r
+ const answer = JSON.parse(serverResponse)\r
+ console.log('Client: Polling for ICE candidates')\r
+ pollServerForICECandidate(cpc, window.location, cpk.n)\r
+ cpc.setRemoteDescription(answer.sdp)\r
+ cpc.onicecandidate = (event) => {\r
+ if (event.candidate) {\r
+ console.log('Client: Sending ice candidate to host')\r
+ postServer(window.location, JSON.stringify({\r
+ cmd: '> ice pubkey',\r
+ ice: event.candidate,\r
+ pubKey: cpk.n\r
+ }))\r
+ }\r
+ else {\r
+ console.log('Client: No more Ice Candidates to send')\r
+ }\r
+ }\r
+\r
+\r
+ }).catch( (err) => {\r
+ console.log('error in sdp handshake: ' + err)\r
+ })\r
+ }\r
+ /* Start data channel */\r
+ dataChannel = cpc.createDataChannel("sendChannel");\r
+ cpc.ontrack = (event) => {\r
+ console.log(`track event is ${event}`)\r
+ let remoteRTPSenders = cpc.getSenders()\r
+ let remoteRTPReceivers = cpc.getReceivers()\r
+ console.log(remoteRTPReceivers)\r
+ /* Add each remoteRTPSenders.track to own stream */\r
+ let video = document.querySelector('video')\r
+ video.autoplay = true\r
+ console.log(video)\r
+ hostScreen = new MediaStream([remoteRTPReceivers[0].track])\r
+ if(!video.srcObject) {\r
+ video.srcObject = hostScreen\r
+ }\r
+ console.log(hostScreen.getVideoTracks())\r
+ console.log(video.srcObject)\r
+ video.play()\r
+ video.onloadedmetadata = () => {\r
+ video.play()\r
+ }\r
+ }\r
+ dataChannel.onmessage = (msg) => {\r
+ /* Get mediaStream from host and add it to the video */\r
+ let hostMessage = JSON.parse(msg.data)\r
+ cpc.setRemoteDescription(hostMessage.sdp).then(() => {\r
+ cpc.createAnswer().then((answer) => {\r
+ return cpc.setLocalDescription(answer)\r
+ }).then(() => {\r
+ dataChannel.send(JSON.stringify({\r
+ "cmd": "> screen dataChannel",\r
+ "sdp": cpc.localDescription\r
+ }))\r
+ })\r
+ })\r
+\r
+\r
+ }\r
+ dataChannel.onopen = () => {\r
+ document.body.innerHTML = (`<div><button> Connection with host established! </button></div> <video controls></video>`)\r
+ }\r
+\r
+})\r
+document.addEventListener('DOMContentLoaded', () => {\r
+\r
+ document.body.innerHTML = `<button> Setting up connection with host </button>`\r
+\r
+});\r
-document.title = "Strapp.io Host"
-
-
-const conf = {"iceServers": [{ "urls": "stun:stun.1.google.com:19302" }] }
-const clients = new Map([])
-const iceCandidates = []
-let dataChannel
-
-
-/* TODO: duplicate in both client.js and host.jhs */
-function getPublicKey() {
- return new Promise( (resolve, reject) => {
- /* Check local storage for public key */
- if (!window.localStorage.getItem('public-key')) {
- console.log('public key is undefined')
- /* If doesn't exist, generate public and private key pair, store in
- local storage */
- crypto.subtle.generateKey(
- { name:'RSA-OAEP',
- modulusLength: 2048,
- publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
- hash: {name: "SHA-256"}
- },
- true,
- ['encrypt', 'decrypt']
- ).then((keyPair) => {
- /* TODO: Do we need to store the private key as well? */
- crypto.subtle.exportKey('jwk', keyPair.publicKey)
- .then((exportedKey) => {
- window.localStorage.setItem('publicKey', exportedKey)
- console.log('public key is' + window.localStorage.getItem('publicKey'))
- resolve(exportedKey)
- })
-
- })
- }
- else {
- resolve(window.localStorage.getItem('publicKey'))
- }
- })
-}
-
-function handleNewClientConnection(offer) {
- /* New Client Connection*/
- hpc = new RTCPeerConnection(conf)
- //console.log(offer)
- clients.set(offer.pubKey, hpc)
- hpc.setRemoteDescription(offer.sdp)
- .then(() => {
- hpc.createAnswer().then((answer) => {
- return hpc.setLocalDescription(answer)
- })
- .then(() => {
- getPublicKey().then((hpk) => {
- hpc.onicecandidate = (event) => {
- if (event.candidate) {
- console.log('Host: Allocating ice candidate for client')
- iceCandidates.push(JSON.stringify({
- cmd: "< ice pubKey",
- ice: event.candidate,
- hostPubKey: hpk.n, /* TODO: do we need to send this? */
- clientPubKey: offer.pubKey,
- iceState: "a"
- }))
- }
- else {
- console.log('Host: Finished sending ICE candidates')
- console.log(hpc)
- }
- }
- console.log('Host: Sending answer to Client')
- wsock.send(JSON.stringify({
- cmd: '< sdp pubKey',
- sdp: hpc.localDescription,
- hostPubKey: hpk.n,
- clientPubKey: offer.pubKey
- }))
- hpc.ondatachannel = (evt) => {
- dataChannel = evt.channel
- console.log(evt)
- dataChannel.onmessage = (msg) => {
- console.log(msg.data)
- }
- dataChannel.onopen = () => {
- dataChannel.send(`Hi ${offer.pubKey} -host`)
- }
- }
- hpc.oniceconnectionstatechange = () => {
- console.log('iceConnectionState = ' + hpc.iceConnectionState)
- }
- })
- }).catch((err) => {
- console.log(`error in host answer ${err}`)
- })
- })
-
-}
-
-function handleNewIceSubmission(msg) {
- console.log('Host: Adding new ice candidate')
- const hpc = clients.get(msg.pubKey)
- let candidate = new RTCIceCandidate(msg.ice)
- hpc.addIceCandidate(candidate)
-}
-
-function handleIceRequest(msg) {
- console.log('Host: Handling ice candidate request')
- console.log(iceCandidates)
- const hpc = clients.get(msg.pubKey)
- const iceCandidate = iceCandidates.pop()
- if (iceCandidate !== undefined) {
- wsock.send(iceCandidate)
- } else {
- if (hpc.iceGatheringState.localeCompare('gathering') === 0) {
- wsock.send(`{"cmd" : "< ice pubKey", "clientPubKey":"${msg.pubKey}", "iceState": "g"}`)
- }
- else if (hpc.iceGatheringState.localeCompare('complete') === 0) {
- wsock.send(`{"cmd" : "< ice pubKey", "clientPubKey":"${msg.pubKey}", "iceState": "c"}`)
- }
-
- }
-
-}
-if ("WebSocket" in window) {
- document.addEventListener('DOMContentLoaded', (event) => {
- wsock = new WebSocket(`${_strapp_protocol}://${window.location.hostname}:${_strapp_port}`)
- wsock.onopen = () => {
- console.log(`Strapped to ${_strapp_protocol}://${window.location.hostname}:${_strapp_port}`)
- }
-
- wsock.onmessage = (serverMsg) => {
- /* msg is either offer or ice candidate or ice candidate request*/
-
- /* What if data null? */
- let msg = JSON.parse(serverMsg.data)
-
- const clientID = msg.pubKey
-
- /* TODO: redo this trash */
- if (clients.has(clientID)) {
- if (msg.ice) {
- handleNewIceSubmission(msg)
- } else if (msg.sdp) {
- //handleRepeatedOffer
- } else {
- handleIceRequest(msg)
- }
- }
- else {
- if (msg.ice) {
- console.log('Host: Client that doesnt exist is sending ice submissions')
- } else if (msg.sdp) {
- handleNewClientConnection(msg)
- } else {
- console.log('Host: Client that doesnt exist is sending ice requests')
- }
- }
- }
- document.body.innerHTML = '<div>Choose options for client</div> <video autoplay></video>'
-
- navigator.mediaDevices.getUserMedia({ video : { mediaSource: "screen", // whole screen sharing
- // mediaSource: "window", // choose a window to share
- // mediaSource: "application", // choose a window to share
- width: {max: '1920'},
- height: {max: '1080'},
- frameRate: {max: '10'}} })
- .then(function(mediaStream) {
- let video = document.querySelector('video')
- video.srcObject = mediaStream
- video.onloadedmetadata = function(e) {
- video.play()
- }
- })
- .catch(function(err) { console.log(err); }); // always check for errors at the end.
- })
-}
-else {
- document.addEventListener('DOMContentLoaded', () => {
- document.body.innerHTML = 'Websockets not supported in your browser'
- })
-}
+document.title = "Strapp.io Host"\r
+\r
+\r
+const conf = {"iceServers": [{ "urls": "stun:stun.1.google.com:19302" }] }\r
+const clients = new Map([])\r
+const iceCandidates = []\r
+let dataChannel\r
+let screenStream /* TODO: Remove if can access localStreams */\r
+let tracks = []\r
+\r
+\r
+/* TODO: duplicate in both client.js and host.jhs */\r
+function getPublicKey() {\r
+ return new Promise( (resolve, reject) => {\r
+ /* Check local storage for public key */\r
+ if (!window.localStorage.getItem('public-key')) {\r
+ console.log('public key is undefined')\r
+ /* If doesn't exist, generate public and private key pair, store in\r
+ local storage */\r
+ crypto.subtle.generateKey(\r
+ { name:'RSA-OAEP',\r
+ modulusLength: 2048,\r
+ publicExponent: new Uint8Array([0x01, 0x00, 0x01]),\r
+ hash: {name: "SHA-256"}\r
+ },\r
+ true,\r
+ ['encrypt', 'decrypt']\r
+ ).then((keyPair) => {\r
+ /* TODO: Do we need to store the private key as well? */\r
+ crypto.subtle.exportKey('jwk', keyPair.publicKey)\r
+ .then((exportedKey) => {\r
+ window.localStorage.setItem('publicKey', exportedKey)\r
+ console.log('public key is' + window.localStorage.getItem('publicKey'))\r
+ resolve(exportedKey)\r
+ })\r
+\r
+ })\r
+ }\r
+ else {\r
+ resolve(window.localStorage.getItem('publicKey'))\r
+ }\r
+ })\r
+}\r
+\r
+function handleNewClientConnection(offer) {\r
+ /* New Client Connection*/\r
+ hpc = new RTCPeerConnection(conf)\r
+ //console.log(offer)\r
+ clients.set(offer.pubKey, hpc)\r
+ hpc.setRemoteDescription(offer.sdp)\r
+ .then(() => {\r
+ hpc.createAnswer().then((answer) => {\r
+ return hpc.setLocalDescription(answer)\r
+ })\r
+ .then(() => {\r
+ getPublicKey().then((hpk) => {\r
+ hpc['hpk'] = hpk\r
+ hpc.onicecandidate = (event) => {\r
+ if (event.candidate) {\r
+ console.log('Host: Allocating ice candidate for client')\r
+ iceCandidates.push(JSON.stringify({\r
+ cmd: "< ice pubKey",\r
+ ice: event.candidate,\r
+ hostPubKey: hpk.n, /* TODO: do we need to send this? */\r
+ clientPubKey: offer.pubKey,\r
+ iceState: "a"\r
+ }))\r
+ }\r
+ else {\r
+ console.log('Host: Finished sending ICE candidates')\r
+ }\r
+ }\r
+ console.log('Host: Sending answer to Client')\r
+ wsock.send(JSON.stringify({\r
+ cmd: '< sdp pubKey',\r
+ sdp: hpc.localDescription,\r
+ hostPubKey: hpk.n,\r
+ clientPubKey: offer.pubKey\r
+ }))\r
+\r
+\r
+ })\r
+ }).catch((err) => {\r
+ console.log(`error in host answer ${err}`)\r
+ })\r
+ })\r
+ hpc.oniceconnectionstatechange = () => {\r
+ console.log('iceConnectionState = ' + hpc.iceConnectionState)\r
+ }\r
+ hpc.ondatachannel = (evt) => {\r
+ dataChannel = evt.channel\r
+ dataChannel.onmessage = (msg) => {\r
+ let clientMessage = JSON.parse(msg.data)\r
+ console.log(clientMessage)\r
+ hpc.setRemoteDescription(clientMessage.sdp).then(() => {\r
+ console.log('should be streaming now')\r
+ })\r
+ }\r
+ dataChannel.onopen = () => {\r
+ screenStream.getTracks().forEach( (track) => {\r
+ hpc.addTrack(track, screenStream)\r
+ })\r
+ console.log(hpc.getSenders())\r
+ /* Create offer */\r
+ hpc.createOffer().then((offer) => {\r
+ return hpc.setLocalDescription(offer)\r
+ }).then( () => {\r
+ dataChannel.send(JSON.stringify({\r
+ "cmd": "< screen dataChannel",\r
+ "sdp": hpc.localDescription,\r
+ "pubKey": hpc['hpk'].n\r
+ }))\r
+ })\r
+ }\r
+ }\r
+ hpc.onnegotiationneeded = () => {\r
+ console.log('negotiation needed')\r
+ }\r
+\r
+}\r
+\r
+function handleNewIceSubmission(msg) {\r
+ console.log('Host: Adding new ice candidate')\r
+ const hpc = clients.get(msg.pubKey)\r
+ let candidate = new RTCIceCandidate(msg.ice)\r
+ hpc.addIceCandidate(candidate)\r
+}\r
+\r
+function handleIceRequest(msg) {\r
+ console.log('Host: Handling ice candidate request')\r
+ console.log(iceCandidates)\r
+ const hpc = clients.get(msg.pubKey)\r
+ const iceCandidate = iceCandidates.pop()\r
+ if (iceCandidate !== undefined) {\r
+ wsock.send(iceCandidate)\r
+ } else {\r
+ if (hpc.iceGatheringState.localeCompare('gathering') === 0) {\r
+ wsock.send(`{"cmd" : "< ice pubKey", "clientPubKey":"${msg.pubKey}", "iceState": "g"}`)\r
+ }\r
+ else if (hpc.iceGatheringState.localeCompare('complete') === 0) {\r
+ wsock.send(`{"cmd" : "< ice pubKey", "clientPubKey":"${msg.pubKey}", "iceState": "c"}`)\r
+ }\r
+\r
+ }\r
+\r
+}\r
+if ("WebSocket" in window) {\r
+ document.addEventListener('DOMContentLoaded', (event) => {\r
+ document.body.innerHTML = '<div>Choose options for client</div> <video autoplay></video>'\r
+ navigator.mediaDevices.getUserMedia({\r
+ video : { mediaSource: "screen",\r
+ width: {max: '1920'},\r
+ height: {max: '1080'},\r
+ frameRate: {max: '10'}} })\r
+ .then(function(mediaStream) {\r
+ let video = document.querySelector('video')\r
+ screenStream = mediaStream\r
+ console.log(mediaStream)\r
+ video.srcObject = mediaStream\r
+ console.log('Grabbed media')\r
+ video.onloadedmetadata = function(e) {\r
+ console.log(e)\r
+ video.play()\r
+ }\r
+ })\r
+ .catch(function(err) {\r
+ document.body.innerHTML = 'Help me help you. Reload the page and allow screen sharing!'\r
+ console.log(err);\r
+ }); // always check for errors at the end.\r
+\r
+ wsock = new WebSocket(`${_strapp_protocol}://${window.location.hostname}:${_strapp_port}`)\r
+ wsock.onopen = () => {\r
+ console.log(`Strapped to ${_strapp_protocol}://${window.location.hostname}:${_strapp_port}`)\r
+ }\r
+\r
+ wsock.onmessage = (serverMsg) => {\r
+ /* msg is either offer or ice candidate or ice candidate request*/\r
+\r
+ /* What if data null? */\r
+ let msg = JSON.parse(serverMsg.data)\r
+\r
+ const clientID = msg.pubKey\r
+\r
+ /* TODO: redo this trash */\r
+ if (clients.has(clientID)) {\r
+ if (msg.ice) {\r
+ handleNewIceSubmission(msg)\r
+ } else if (msg.sdp) {\r
+ //handleRepeatedOffer\r
+ } else {\r
+ handleIceRequest(msg)\r
+ }\r
+ }\r
+ else {\r
+ if (msg.ice) {\r
+ console.log('Host: Client that doesnt exist is sending ice submissions')\r
+ } else if (msg.sdp) {\r
+ handleNewClientConnection(msg)\r
+ } else {\r
+ console.log('Host: Client that doesnt exist is sending ice requests')\r
+ }\r
+ }\r
+ }\r
+\r
+\r
+\r
+ })\r
+}\r
+else {\r
+ document.addEventListener('DOMContentLoaded', () => {\r
+ document.body.innerHTML = 'Websockets not supported in your browser'\r
+ })\r
+}\r
-/**
-* @file Node entry and main driver
-* @author Jordan Lavatai, Ken Grimes
-* @version 0.0.1
-* @license AGPL-3.0
-* @copyright loljk 2017
-* @summary HTTP(S) Router that uses the first directory in the requested URL
-* as the route name
-*/
-const fs = require('fs')
-const ws = require('ws')
-const path = require('path')
-const http = require('http')
-const https = require('https')
-const getport = require('get-port')
-const mime = require('mime')
-const opts = require('./opts.js')
-
-const router = {
- skelPage: fs.readFileSync('./skel.html', { encoding: 'utf8' }).split('<!--STRAPP_SRC-->'),
- clientJS: fs.readFileSync(opts['client-js']),
- hostJS: fs.readFileSync(opts['host-js']),
- routes: {},
- httpsOpt: undefined,
- httpd: undefined,
- wsProtocol: opts['no-tls'] ? 'ws' : 'wss',
- respond: (request,response) => {
- console.log('server handling request')
- const serveFile = (fPath) => {
- fs.readFile(fPath, { encoding: 'utf8' }, (err, data) => {
- if (err || data == undefined) {
- response.writeHead(404)
- response.end()
- }
- else {
- response.writeHead(200, { 'Content-Type': mime.lookup(fPath) })
- response.write(data)
- response.end()
- }
- })
- }
- const htArgv = request.url.slice(1).split("?")
- let routePath = htArgv[0].split('/')
- let routeName = routePath[0]
-
-
- if (routeName === '' || routeName === 'index.html')
- serveFile(opts['index'])
- else if (routeName in opts['bindings']) {
- let localPath = path.normalize(opts['bindings'][routeName].concat(path.sep + routePath.slice(1).join(path.sep)))
- if (localPath.includes(opts['bindings'][routeName])) {
- fs.readdir(localPath, (err, files) => {
- if (err)
- serveFile(localPath)
- else
- serveFile(`${localPath}/index.html`)
- })
- }
- else {
- console.log(`SEC: ${localPath} references files not in route`)
- }
- }
- /* TODO: Handle reconnecting host */
- else if (routeName in router.routes) {
- const route = router.routes[routeName]
- const clients = route['clients']
- const headerData = request.headers['x-strapp-type']
-
-
-
-
- /* Client is INIT GET */
- if (headerData === undefined) {
- console.log('client init GET')
- response.writeHead(200, { 'Content-Type': 'text/html' })
- response.write(`${router.skelPage[0]}${router.clientJS}${router.skelPage[1]}`)
- response.end()
- //TODO: if route.socket == undefined: have server delay this send until host connects
- // (this happens when a client connects to an active route with no currently-online host)
- }
- else if (headerData.localeCompare('ice-candidate-request') === 0) {
- console.log('Server: received ice-candidate-request from Client')
- let pubKey = request.headers['x-client-pubkey']
- clients.set(pubKey, response)
- pubKey = '{ "pubKey": "' + pubKey + '" }'
- route.socket.send(pubKey)
- }
- else if (headerData.localeCompare('ice-candidate-submission') === 0) {
- console.log('Server: recieved ice-candidate-submission from Client')
- let data = []
- request.on('data', (chunk) => {
- data.push(chunk)
- }).on('end', () => {
- console.log('Sending ice-candidate-submission to Host')
- data = Buffer.concat(data).toString();
- clients.set(JSON.parse(data)['pubKey'], response)
- route.socket.send(data)
- })
- }
- else if (headerData.localeCompare('client-sdp-offer') === 0){ /* Client sent offer, waiting for answer */
- console.log('Server: Sending client offer to host')
- clients.set(JSON.parse(request.headers['x-client-offer'])['pubKey'], response)
- route.socket.send(request.headers['x-client-offer'])
- } else {
- console.log('Unhandled stuff')
- console.log(request.headers)
- }
-
- }
- else {
- router.routes[routeName] = true
- const newRoute = {}
- newRoute.clients = new Map([])
- newRoute.host = request.headers['x-forwarded-for'] || request.connection.remoteAddress
- getport().then( (port) => {
- newRoute.port = port
- if (opts['no-tls'])
- newRoute.httpd = http.createServer()
- else
- newRoute.httpd = https.createServer(router.httpsOpts)
- newRoute.httpd.listen(newRoute.port)
- newRoute.wsd = new ws.Server( { server: newRoute.httpd } )
- newRoute.wsd.on('connection', (sock) => {
- console.log(`${routeName} server has been established`)
- newRoute.socket = sock
-
- /* Handle all messages from host */
- sock.on('message', (hostMessage) => {
- hostMessage = JSON.parse(hostMessage)
- response = newRoute.clients.get(hostMessage['clientPubKey'])
-
- /* If the host response is a answer */
- if (hostMessage['cmd'].localeCompare('< sdp pubKey') === 0) {
- console.log('Server: Sending host answer to client')
- response.writeHead(200, { 'Content-Type': 'application/json' })
- response.write(JSON.stringify(hostMessage))
- response.end()
- }
- else if (hostMessage['cmd'].localeCompare('< ice pubKey') === 0){
- /* if the host response is an ice candidate */
- console.log('Server: Handling host ICE message')
- let iceState = hostMessage['iceState']
- /* If there are any ice candidates, send them back */
- switch(iceState) {
- case "a":
- response.writeHead('200', {'x-strapp-type': 'ice-candidate-available'})
- response.write(JSON.stringify(hostMessage))
- response.end()
- break
- case "g":
- console.log('Server: Host is still gathering candidates, keep trying')
- response.writeHead('200', {'x-strapp-type': 'ice-state-gathering'})
- response.write(JSON.stringify(hostMessage))
- response.end()
- break
- case "c":
- console.log('Server: Host has completed gathering candidates')
- response.writeHead('200', {'x-strapp-type': 'ice-state-complete'})
- response.write(JSON.stringify(hostMessage))
- response.end()
- break
- default:
- console.log('unhandled iceState from host')
- break
- }
- }
-
- })
- })
-
- console.log(`Listening for websocket ${newRoute.host} on port ${newRoute.port}`)
- router.routes[routeName] = newRoute
- }).then(() => {
- response.writeHead(200, { 'Content-Type': 'text/html' })
- response.write(`${router.skelPage[0]}` +
- `\tconst _strapp_port = ${newRoute.port}\n` +
- `\tconst _strapp_protocol = '${router.wsProtocol}'\n` +
- `${router.hostJS}\n${router.skelPage[1]}`)
- response.end()
- })
- }
-
-
- }
- }
-
- /**
- * @summary Boot up the router. With TLS, we must wait for file reads to sync.
- */
- if (!opts['no-tls']) {
- console.log('tls')
- let filesRead = 0
- let key = undefined
- let cert = undefined
- const syncRead = () => {
- if (++filesRead == 2) {
- if (key == undefined)
- console.log(`ERR: Key ${opts['ca-key']} inaccessible, tls will fail`)
- if(cert == undefined)
- console.log(`ERR: Cert ${opts['ca-cert']} inaccessible, tls will fail`)
- else if (key != undefined) {
- router.httpsOpts = { cert: cert, key: key}
- router.httpd = https.createServer(router.httpsOpts, router.respond)
- .listen(opts['port'])
- }
- }
- }
- fs.readFile(opts['ca-key'], { encoding: 'utf8' }, (err, data) => {
- if (!err) key = data
- syncRead()
- })
- fs.readFile(opts['ca-cert'], { encoding: 'utf8' }, (err, data) => {
- if (!err) cert = data
- syncRead()
- })
- }
- else
- router.httpd = http.createServer(router.respond).listen(opts['port'])
-
- //TODO: if ("electron" in process.versions) open a local renderwindow, and route to it
+/**\r
+* @file Node entry and main driver\r
+* @author Jordan Lavatai, Ken Grimes\r
+* @version 0.0.1\r
+* @license AGPL-3.0\r
+* @copyright loljk 2017\r
+* @summ ary HTTP(S) Router that uses the first directory in the requested URL\r
+* as the route name\r
+*/\r
+const fs = require('fs')\r
+const ws = require('ws')\r
+const path = require('path')\r
+const http = require('http')\r
+const https = require('https')\r
+const getport = require('get-port')\r
+const mime = require('mime')\r
+const opts = require('./opts.js')\r
+\r
+const router = {\r
+ skelPage: fs.readFileSync('./skel.html', { encoding: 'utf8' }).split('<!--STRAPP_SRC-->'),\r
+ clientJS: fs.readFileSync(opts['client-js']),\r
+ hostJS: fs.readFileSync(opts['host-js']),\r
+ routes: {},\r
+ httpsOpt: undefined,\r
+ httpd: undefined,\r
+ wsProtocol: opts['no-tls'] ? 'ws' : 'wss',\r
+ respond: (request,response) => {\r
+ console.log('server handling request')\r
+ const serveFile = (fPath) => {\r
+ fs.readFile(fPath, { encoding: 'utf8' }, (err, data) => {\r
+ if (err || data == undefined) {\r
+ response.writeHead(404)\r
+ response.end()\r
+ }\r
+ else {\r
+ response.writeHead(200, { 'Content-Type': mime.lookup(fPath) })\r
+ response.write(data)\r
+ response.end()\r
+ }\r
+ })\r
+ }\r
+ const htArgv = request.url.slice(1).split("?")\r
+ let routePath = htArgv[0].split('/')\r
+ let routeName = routePath[0]\r
+\r
+\r
+ if (routeName === '' || routeName === 'index.html')\r
+ serveFile(opts['index'])\r
+ else if (routeName in opts['bindings']) {\r
+ let localPath = path.normalize(opts['bindings'][routeName].concat(path.sep + routePath.slice(1).join(path.sep)))\r
+ if (localPath.includes(opts['bindings'][routeName])) {\r
+ fs.readdir(localPath, (err, files) => {\r
+ if (err)\r
+ serveFile(localPath)\r
+ else\r
+ serveFile(`${localPath}/index.html`)\r
+ })\r
+ }\r
+ else {\r
+ console.log(`SEC: ${localPath} references files not in route`)\r
+ }\r
+ }\r
+ /* TODO: Handle reconnecting host */\r
+ else if (routeName in router.routes) {\r
+ const route = router.routes[routeName]\r
+ const clients = route['clients']\r
+ const headerData = request.headers['x-strapp-type']\r
+\r
+\r
+\r
+\r
+ /* Client is INIT GET */\r
+ if (headerData === undefined) {\r
+ console.log('client init GET')\r
+ response.writeHead(200, { 'Content-Type': 'text/html' })\r
+ response.write(`${router.skelPage[0]}${router.clientJS}${router.skelPage[1]}`)\r
+ response.end()\r
+ //TODO: if route.socket == undefined: have server delay this send until host connects\r
+ // (this happens when a client connects to an active route with no currently-online host)\r
+ }\r
+ else if (headerData.localeCompare('ice-candidate-request') === 0) {\r
+ console.log('Server: received ice-candidate-request from Client')\r
+ let pubKey = request.headers['x-client-pubkey']\r
+ clients.set(pubKey, response)\r
+ pubKey = '{ "pubKey": "' + pubKey + '" }'\r
+ route.socket.send(pubKey)\r
+ }\r
+ else if (headerData.localeCompare('ice-candidate-submission') === 0) {\r
+ console.log('Server: recieved ice-candidate-submission from Client')\r
+ let data = []\r
+ request.on('data', (chunk) => {\r
+ data.push(chunk)\r
+ }).on('end', () => {\r
+ console.log('Sending ice-candidate-submission to Host')\r
+ data = Buffer.concat(data).toString();\r
+ clients.set(JSON.parse(data)['pubKey'], response)\r
+ route.socket.send(data)\r
+ })\r
+ }\r
+ else if (headerData.localeCompare('client-sdp-offer') === 0){ /* Client sent offer, waiting for answer */\r
+ console.log('Server: Sending client offer to host')\r
+ clients.set(JSON.parse(request.headers['x-client-offer'])['pubKey'], response)\r
+ route.socket.send(request.headers['x-client-offer'])\r
+ } else {\r
+ console.log('Unhandled stuff')\r
+ console.log(request.headers)\r
+ }\r
+\r
+ }\r
+ else {\r
+ router.routes[routeName] = true\r
+ const newRoute = {}\r
+ newRoute.clients = new Map([])\r
+ newRoute.host = request.headers['x-forwarded-for'] || request.connection.remoteAddress\r
+ getport().then( (port) => {\r
+ newRoute.port = port\r
+ if (opts['no-tls'])\r
+ newRoute.httpd = http.createServer()\r
+ else\r
+ newRoute.httpd = https.createServer(router.httpsOpts)\r
+ newRoute.httpd.listen(newRoute.port)\r
+ newRoute.wsd = new ws.Server( { server: newRoute.httpd } )\r
+ newRoute.wsd.on('connection', (sock) => {\r
+ console.log(`${routeName} server has been established`)\r
+ newRoute.socket = sock\r
+\r
+ /* Handle all messages from host */\r
+ sock.on('message', (hostMessage) => {\r
+ hostMessage = JSON.parse(hostMessage)\r
+ response = newRoute.clients.get(hostMessage['clientPubKey'])\r
+\r
+ /* If the host response is a answer */\r
+ if (hostMessage['cmd'].localeCompare('< sdp pubKey') === 0) {\r
+ console.log('Server: Sending host answer to client')\r
+ response.writeHead(200, { 'Content-Type': 'application/json' })\r
+ response.write(JSON.stringify(hostMessage))\r
+ response.end()\r
+ }\r
+ else if (hostMessage['cmd'].localeCompare('< ice pubKey') === 0){\r
+ /* if the host response is an ice candidate */\r
+ console.log('Server: Handling host ICE message')\r
+ let iceState = hostMessage['iceState']\r
+ /* If there are any ice candidates, send them back */\r
+ switch(iceState) {\r
+ case "a":\r
+ response.writeHead('200', {'x-strapp-type': 'ice-candidate-available'})\r
+ response.write(JSON.stringify(hostMessage))\r
+ response.end()\r
+ break\r
+ case "g":\r
+ console.log('Server: Host is still gathering candidates, keep trying')\r
+ response.writeHead('200', {'x-strapp-type': 'ice-state-gathering'})\r
+ response.write(JSON.stringify(hostMessage))\r
+ response.end()\r
+ break\r
+ case "c":\r
+ console.log('Server: Host has completed gathering candidates')\r
+ response.writeHead('200', {'x-strapp-type': 'ice-state-complete'})\r
+ response.write(JSON.stringify(hostMessage))\r
+ response.end()\r
+ break\r
+ default:\r
+ console.log('unhandled iceState from host')\r
+ break\r
+ }\r
+ }\r
+\r
+ })\r
+ })\r
+\r
+ console.log(`Listening for websocket ${newRoute.host} on port ${newRoute.port}`)\r
+ router.routes[routeName] = newRoute\r
+ }).then(() => {\r
+ response.writeHead(200, { 'Content-Type': 'text/html' })\r
+ response.write(`${router.skelPage[0]}` +\r
+ `\tconst _strapp_port = ${newRoute.port}\n` +\r
+ `\tconst _strapp_protocol = '${router.wsProtocol}'\n` +\r
+ `${router.hostJS}\n${router.skelPage[1]}`)\r
+ response.end()\r
+ })\r
+ }\r
+\r
+\r
+ }\r
+ }\r
+\r
+ /**\r
+ * @summary Boot up the router. With TLS, we must wait for file reads to sync.\r
+ */\r
+ if (!opts['no-tls']) {\r
+ console.log('tls')\r
+ let filesRead = 0\r
+ let key = undefined\r
+ let cert = undefined\r
+ const syncRead = () => {\r
+ if (++filesRead == 2) {\r
+ if (key == undefined)\r
+ console.log(`ERR: Key ${opts['ca-key']} inaccessible, tls will fail`)\r
+ if(cert == undefined)\r
+ console.log(`ERR: Cert ${opts['ca-cert']} inaccessible, tls will fail`)\r
+ else if (key != undefined) {\r
+ router.httpsOpts = { cert: cert, key: key}\r
+ router.httpd = https.createServer(router.httpsOpts, router.respond)\r
+ .listen(opts['port'])\r
+ }\r
+ }\r
+ }\r
+ fs.readFile(opts['ca-key'], { encoding: 'utf8' }, (err, data) => {\r
+ if (!err) key = data\r
+ syncRead()\r
+ })\r
+ fs.readFile(opts['ca-cert'], { encoding: 'utf8' }, (err, data) => {\r
+ if (!err) cert = data\r
+ syncRead()\r
+ })\r
+ }\r
+ else\r
+ router.httpd = http.createServer(router.respond).listen(opts['port'])\r
+\r
+ //TODO: if ("electron" in process.versions) open a local renderwindow, and route to it\r
--- /dev/null
+{
+ "name": "strapp",
+ "version": "0.0.1",
+ "lockfileVersion": 1,
+ "dependencies": {
+ "@types/node": {
+ "version": "https://registry.npmjs.org/@types/node/-/node-7.0.32.tgz",
+ "integrity": "sha1-av5sZlIKTDFmI6FK7xI5CNAbS7o="
+ },
+ "ajv": {
+ "version": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz",
+ "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY="
+ },
+ "ansi-regex": {
+ "version": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
+ },
+ "array-find-index": {
+ "version": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
+ "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E="
+ },
+ "asn1": {
+ "version": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
+ "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y="
+ },
+ "assert-plus": {
+ "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz",
+ "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ="
+ },
+ "asynckit": {
+ "version": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
+ },
+ "aws-sign2": {
+ "version": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz",
+ "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8="
+ },
+ "aws4": {
+ "version": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz",
+ "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4="
+ },
+ "balanced-match": {
+ "version": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
+ },
+ "bcrypt-pbkdf": {
+ "version": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz",
+ "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=",
+ "optional": true
+ },
+ "boom": {
+ "version": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
+ "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8="
+ },
+ "brace-expansion": {
+ "version": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
+ "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI="
+ },
+ "builtin-modules": {
+ "version": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
+ "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8="
+ },
+ "camelcase": {
+ "version": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
+ "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8="
+ },
+ "camelcase-keys": {
+ "version": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
+ "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc="
+ },
+ "caseless": {
+ "version": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
+ },
+ "co": {
+ "version": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+ "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ="
+ },
+ "code-point-at": {
+ "version": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
+ },
+ "combined-stream": {
+ "version": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
+ "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk="
+ },
+ "concat-map": {
+ "version": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+ },
+ "concat-stream": {
+ "version": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz",
+ "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=",
+ "dependencies": {
+ "isarray": {
+ "version": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+ },
+ "readable-stream": {
+ "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.2.tgz",
+ "integrity": "sha1-WgTfBeT1f+Pw3Gj90R3FyXx+b00="
+ },
+ "string_decoder": {
+ "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
+ "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs="
+ }
+ }
+ },
+ "core-util-is": {
+ "version": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+ },
+ "cryptiles": {
+ "version": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
+ "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g="
+ },
+ "currently-unhandled": {
+ "version": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
+ "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o="
+ },
+ "dashdash": {
+ "version": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+ "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+ "dependencies": {
+ "assert-plus": {
+ "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+ }
+ }
+ },
+ "debug": {
+ "version": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz",
+ "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw="
+ },
+ "decamelize": {
+ "version": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
+ },
+ "deep-extend": {
+ "version": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz",
+ "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8="
+ },
+ "delayed-stream": {
+ "version": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
+ },
+ "ecc-jsbn": {
+ "version": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
+ "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=",
+ "optional": true
+ },
+ "electron": {
+ "version": "https://registry.npmjs.org/electron/-/electron-1.6.11.tgz",
+ "integrity": "sha1-vnnA69zv7bW/KBF0CYAPpTus7/o="
+ },
+ "electron-download": {
+ "version": "https://registry.npmjs.org/electron-download/-/electron-download-3.3.0.tgz",
+ "integrity": "sha1-LP1U1pZsAZxNSa1l++Zcyc3vaMg="
+ },
+ "error-ex": {
+ "version": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz",
+ "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw="
+ },
+ "es6-promise": {
+ "version": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.1.0.tgz",
+ "integrity": "sha1-3aA8qPn4m8WX5omEKSnee6jOvfA="
+ },
+ "extend": {
+ "version": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
+ "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ="
+ },
+ "extract-zip": {
+ "version": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.5.tgz",
+ "integrity": "sha1-maBnNbbqIOqbcF13ms/8yHz/BEA=",
+ "dependencies": {
+ "debug": {
+ "version": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
+ "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo="
+ },
+ "ms": {
+ "version": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
+ "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg="
+ }
+ }
+ },
+ "extsprintf": {
+ "version": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz",
+ "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA="
+ },
+ "fd-slicer": {
+ "version": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz",
+ "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU="
+ },
+ "find-up": {
+ "version": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
+ "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8="
+ },
+ "forever-agent": {
+ "version": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
+ },
+ "form-data": {
+ "version": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz",
+ "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE="
+ },
+ "fs-extra": {
+ "version": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz",
+ "integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A="
+ },
+ "fs.realpath": {
+ "version": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
+ },
+ "get-port": {
+ "version": "https://registry.npmjs.org/get-port/-/get-port-3.1.0.tgz",
+ "integrity": "sha1-7wGxioTKZIaXD/meVERhQac//T4="
+ },
+ "get-stdin": {
+ "version": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
+ "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4="
+ },
+ "getpass": {
+ "version": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+ "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+ "dependencies": {
+ "assert-plus": {
+ "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+ }
+ }
+ },
+ "glob": {
+ "version": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
+ "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU="
+ },
+ "graceful-fs": {
+ "version": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
+ "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
+ },
+ "har-schema": {
+ "version": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz",
+ "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4="
+ },
+ "har-validator": {
+ "version": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz",
+ "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio="
+ },
+ "hawk": {
+ "version": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
+ "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ="
+ },
+ "hoek": {
+ "version": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
+ "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0="
+ },
+ "home-path": {
+ "version": "https://registry.npmjs.org/home-path/-/home-path-1.0.5.tgz",
+ "integrity": "sha1-eIspgVsS1Tus9XVkhHbm+QQdEz8="
+ },
+ "hosted-git-info": {
+ "version": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.4.2.tgz",
+ "integrity": "sha1-AHa59GonBQbduq6lZJaJdGBhKmc="
+ },
+ "http-signature": {
+ "version": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
+ "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8="
+ },
+ "indent-string": {
+ "version": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
+ "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA="
+ },
+ "inflight": {
+ "version": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk="
+ },
+ "inherits": {
+ "version": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+ },
+ "ini": {
+ "version": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz",
+ "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4="
+ },
+ "ip": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
+ "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo="
+ },
+ "is-arrayish": {
+ "version": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="
+ },
+ "is-builtin-module": {
+ "version": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
+ "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74="
+ },
+ "is-finite": {
+ "version": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz",
+ "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko="
+ },
+ "is-fullwidth-code-point": {
+ "version": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs="
+ },
+ "is-typedarray": {
+ "version": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
+ },
+ "is-utf8": {
+ "version": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
+ "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI="
+ },
+ "isarray": {
+ "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
+ },
+ "isstream": {
+ "version": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
+ },
+ "jsbn": {
+ "version": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
+ "optional": true
+ },
+ "json-schema": {
+ "version": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+ "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
+ },
+ "json-stable-stringify": {
+ "version": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz",
+ "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8="
+ },
+ "json-stringify-safe": {
+ "version": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
+ },
+ "jsonfile": {
+ "version": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
+ "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug="
+ },
+ "jsonify": {
+ "version": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
+ "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM="
+ },
+ "jsprim": {
+ "version": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz",
+ "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=",
+ "dependencies": {
+ "assert-plus": {
+ "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+ }
+ }
+ },
+ "klaw": {
+ "version": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz",
+ "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk="
+ },
+ "load-json-file": {
+ "version": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+ "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA="
+ },
+ "loud-rejection": {
+ "version": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
+ "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8="
+ },
+ "map-obj": {
+ "version": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
+ "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0="
+ },
+ "meow": {
+ "version": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
+ "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs="
+ },
+ "mime": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.6.tgz",
+ "integrity": "sha1-WR2E02U6awtKO5343lqoEI5y5eA="
+ },
+ "mime-db": {
+ "version": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz",
+ "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE="
+ },
+ "mime-types": {
+ "version": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz",
+ "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0="
+ },
+ "minimatch": {
+ "version": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM="
+ },
+ "minimist": {
+ "version": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
+ },
+ "mkdirp": {
+ "version": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz",
+ "integrity": "sha1-HXMHam35hs2TROFecfzAWkyavxI=",
+ "dependencies": {
+ "minimist": {
+ "version": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
+ }
+ }
+ },
+ "ms": {
+ "version": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ },
+ "normalize-package-data": {
+ "version": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.8.tgz",
+ "integrity": "sha1-2Bntoqne29H/pWPqQHHZNngilbs="
+ },
+ "nugget": {
+ "version": "https://registry.npmjs.org/nugget/-/nugget-2.0.1.tgz",
+ "integrity": "sha1-IBCVpIfhrTYIGzQy+jytpPjQcbA="
+ },
+ "number-is-nan": {
+ "version": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
+ },
+ "oauth-sign": {
+ "version": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
+ "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM="
+ },
+ "object-assign": {
+ "version": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
+ },
+ "object-keys": {
+ "version": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz",
+ "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY="
+ },
+ "once": {
+ "version": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E="
+ },
+ "parse-json": {
+ "version": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
+ "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck="
+ },
+ "path-exists": {
+ "version": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
+ "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s="
+ },
+ "path-is-absolute": {
+ "version": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
+ },
+ "path-type": {
+ "version": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
+ "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE="
+ },
+ "pend": {
+ "version": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
+ "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA="
+ },
+ "performance-now": {
+ "version": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz",
+ "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU="
+ },
+ "pify": {
+ "version": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw="
+ },
+ "pinkie": {
+ "version": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+ "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA="
+ },
+ "pinkie-promise": {
+ "version": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+ "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o="
+ },
+ "pretty-bytes": {
+ "version": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-1.0.4.tgz",
+ "integrity": "sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ="
+ },
+ "process-nextick-args": {
+ "version": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
+ "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
+ },
+ "progress-stream": {
+ "version": "https://registry.npmjs.org/progress-stream/-/progress-stream-1.2.0.tgz",
+ "integrity": "sha1-LNPP6jO6OonJwSHsM0er6asSX3c="
+ },
+ "punycode": {
+ "version": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
+ },
+ "qs": {
+ "version": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz",
+ "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM="
+ },
+ "rc": {
+ "version": "https://registry.npmjs.org/rc/-/rc-1.2.1.tgz",
+ "integrity": "sha1-LgPo5C7kULjLPc5lvhv4l04d/ZU="
+ },
+ "read-pkg": {
+ "version": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
+ "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg="
+ },
+ "read-pkg-up": {
+ "version": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
+ "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI="
+ },
+ "readable-stream": {
+ "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+ "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk="
+ },
+ "redent": {
+ "version": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
+ "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94="
+ },
+ "repeating": {
+ "version": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
+ "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo="
+ },
+ "request": {
+ "version": "https://registry.npmjs.org/request/-/request-2.81.0.tgz",
+ "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA="
+ },
+ "rimraf": {
+ "version": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz",
+ "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0="
+ },
+ "safe-buffer": {
+ "version": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
+ "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM="
+ },
+ "semver": {
+ "version": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
+ "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8="
+ },
+ "signal-exit": {
+ "version": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
+ },
+ "single-line-log": {
+ "version": "https://registry.npmjs.org/single-line-log/-/single-line-log-1.1.2.tgz",
+ "integrity": "sha1-wvg/Jzo+GhbtsJlWYdoO1e8DM2Q="
+ },
+ "sntp": {
+ "version": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
+ "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg="
+ },
+ "spdx-correct": {
+ "version": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz",
+ "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A="
+ },
+ "spdx-expression-parse": {
+ "version": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz",
+ "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw="
+ },
+ "spdx-license-ids": {
+ "version": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz",
+ "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc="
+ },
+ "speedometer": {
+ "version": "https://registry.npmjs.org/speedometer/-/speedometer-0.1.4.tgz",
+ "integrity": "sha1-mHbb0qFp0xFUAtSObqYynIgWpQ0="
+ },
+ "sshpk": {
+ "version": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz",
+ "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=",
+ "dependencies": {
+ "assert-plus": {
+ "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+ }
+ }
+ },
+ "string_decoder": {
+ "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+ "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
+ },
+ "string-width": {
+ "version": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M="
+ },
+ "stringstream": {
+ "version": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
+ "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg="
+ },
+ "strip-ansi": {
+ "version": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8="
+ },
+ "strip-bom": {
+ "version": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
+ "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4="
+ },
+ "strip-indent": {
+ "version": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
+ "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI="
+ },
+ "strip-json-comments": {
+ "version": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+ "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
+ },
+ "sumchecker": {
+ "version": "https://registry.npmjs.org/sumchecker/-/sumchecker-1.3.1.tgz",
+ "integrity": "sha1-ebs7RFbdBPGOvbwNcDodHa7FEF0="
+ },
+ "throttleit": {
+ "version": "https://registry.npmjs.org/throttleit/-/throttleit-0.0.2.tgz",
+ "integrity": "sha1-z+34jmDADdlpe2H90qg0OptoDq8="
+ },
+ "through2": {
+ "version": "https://registry.npmjs.org/through2/-/through2-0.2.3.tgz",
+ "integrity": "sha1-6zKE2k6jEbbMis42U3SKUqvyWj8="
+ },
+ "tough-cookie": {
+ "version": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz",
+ "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo="
+ },
+ "trim-newlines": {
+ "version": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
+ "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM="
+ },
+ "tunnel-agent": {
+ "version": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+ "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0="
+ },
+ "tweetnacl": {
+ "version": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
+ "optional": true
+ },
+ "typedarray": {
+ "version": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
+ },
+ "ultron": {
+ "version": "https://registry.npmjs.org/ultron/-/ultron-1.1.0.tgz",
+ "integrity": "sha1-sHoualQagV/Go0zNRTO67DB8qGQ="
+ },
+ "util-deprecate": {
+ "version": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+ },
+ "uuid": {
+ "version": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
+ "integrity": "sha1-PdPT55Crwk17DToDT/q6vijrvAQ="
+ },
+ "validate-npm-package-license": {
+ "version": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz",
+ "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w="
+ },
+ "verror": {
+ "version": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz",
+ "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw="
+ },
+ "wrappy": {
+ "version": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+ },
+ "ws": {
+ "version": "https://registry.npmjs.org/ws/-/ws-3.0.0.tgz",
+ "integrity": "sha1-mN2wAFbIOQy3Ued4h4hJf5kQO2w=",
+ "dependencies": {
+ "safe-buffer": {
+ "version": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz",
+ "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c="
+ }
+ }
+ },
+ "xtend": {
+ "version": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz",
+ "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os="
+ },
+ "yauzl": {
+ "version": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz",
+ "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU="
+ }
+ }
+}
--- /dev/null
+/**
+ * @file HTTP(S) Router that treats the first directory in a URL's path as
+ * a route to a host.
+ * @author Ken Grimes
+ * @version 0.0.1
+ * @license AGPL-3.0
+ * @copyright jk software 2017
+ */
+const fs = require('fs')
+
+module.exports = (opts) => {
+ const startHttpd = (listener) => {
+ if (opts['no-tls'])
+ return require('http').createServer(listener)
+ if (fs.existsSync(opts['ca-cert']))
+
+ }
+ return {
+ httpd: startHttpd(),
+ wsd: startWsd()
+ }
+}
can override any of the previous file's settings.
(/etc/strapp.conf:~/.strapp/strapp.conf:./strapp.conf)
- config settings are overridden by command line opts
- except where noted
-j, --client-js=path Path to the client Strapp code (./client.js)
-J, --host-js=path Path to the host Strapp code (./host.js)
- -T, --no-tls=bool Don't use HTTPS and WSS protocols (false)
- - makes 'ca-cert' and 'ca-key' unnecessary
-
-C, --ca-cert=path Accessible location of the CA Cert (../certs/cert.pem)
-K, --ca-key=path Accessible location of the CA Key (../certs/key.pem)
- -p, --port=number The local port to bind HTTPS listener to (2443)
- -i, --index=path File serviced at the root domain (./index.html)
+ -p, --port=number The local port to bind HTTP/S listener to (2443)
+ -d, --file-dir=path Path to the directory that will serve files over HTTP
+ if a client's requested route doesn't exist and can't
+ be created
ROUTING
-b, --bind=[string:path[,string:path]]...
be established, so no remote hosts may exist
COMPATIBILITY
+ --no-tls=bool Don't use HTTPS and WSS protocols (false)
+ - makes 'ca-cert' and 'ca-key' unnecessary
+
--legacy-socket=bool Use Socket.io compatibility layer to enable
long-polling and AJAX fallbacks (false)
- enables optional socket.io dependency