adding web sockets (part 1) #15

Merged
bones7242 merged 6 commits from master into master 2017-05-26 10:37:42 +02:00
9 changed files with 246 additions and 120 deletions
Showing only changes of commit bfcfcf88c0 - Show all commits

View file

@ -11,8 +11,8 @@ spee.ch is a single-serving site that reads and publishes images to and from the
* run `npm install` * run `npm install`
* from your terminal, run `npm start` * from your terminal, run `npm start`
* to run hot, run `nodemon server.js` * to run hot, run `nodemon server.js`
* start at least one worker by running `node worker.js` * start a worker by running `node worker.js` in a separate session in your terminal
* visit [localhost:3000](http://localhost:3000) and enjoy! * visit [localhost:3000](http://localhost:3000)
## site navigation ## site navigation

View file

@ -37,7 +37,35 @@ function orderTopClaims(claimsListArray){
return claimsListArray; return claimsListArray;
} }
function getClaimWithUri(uri, resolve, reject){
console.log(">> making get request to lbry daemon")
axios.post('http://localhost:5279/lbryapi', {
method: "get",
params: { uri: uri }
}
).then(function (getUriResponse) {
console.log(">> 'get claim' success...");
//check to make sure the daemon didn't just time out
if (getUriResponse.data.result.error === "Timeout"){
kauffj commented 2017-05-31 20:38:11 +02:00 (Migrated from github.com)
Review

It's a good idea to define strings that have special values as constants.

It's a good idea to define strings that have special values as constants.
reject("get request to lbry daemon timed out");
}
console.log(">> response data:", getUriResponse.data);
console.log(">> dl path =", getUriResponse.data.result.download_path)
// resolve the promise with the download path for the claim we got
/*
note: put in a check to make sure we do not resolve until the download is actually complete
*/
resolve(getUriResponse.data.result.download_path);
kauffj commented 2017-05-31 20:39:59 +02:00 (Migrated from github.com)
Review

If you find yourself accessing the same field repeatedly, like getUriResponse.data, it can be good to just dereference it.

Additionally, assuming you're writing ES6 (which is probably a good idea), you can just write:

.then(({data}) => { if (data.result.error === "Timeout") //etc. })

If you find yourself accessing the same field repeatedly, like getUriResponse.data, it can be good to just dereference it. Additionally, assuming you're writing ES6 (which is probably a good idea), you can just write: `.then(({data}) => { if (data.result.error === "Timeout") //etc. })`
}).catch(function(getUriError){
console.log(">> 'get' error:", getUriError.response.data);
// reject the promise with an error message
reject(getUriError.response.data.error.message);
return;
});
}
module.exports = { module.exports = {
publishClaim: function(publishObject){ publishClaim: function(publishObject){
axios.post('http://localhost:5279/lbryapi', publishObject) axios.post('http://localhost:5279/lbryapi', publishObject)
.then(function (response) { .then(function (response) {
@ -56,93 +84,81 @@ module.exports = {
//res.status(500).send(JSON.stringify({msg: "your file was not published", err: error.response.data.error.message})); //res.status(500).send(JSON.stringify({msg: "your file was not published", err: error.response.data.error.message}));
}) })
}, },
serveClaimBasedOnNameOnly: function(claimName, res){
getClaimBasedOnNameOnly: function(claimName){
// 1. create a promise
var deferred = new Promise(function (resolve, reject){
// 2. code to resolve or reject the promise
// make a call to the daemon to get the claims list // make a call to the daemon to get the claims list
axios.post('http://localhost:5279/lbryapi', { axios.post('http://localhost:5279/lbryapi', { // receives a promise
method: "claim_list", method: "claim_list",
params: { params: { name: claimName }
name: claimName })
} .then(function (response) {
}
).then(function (response) {
console.log(">> Claim_list success"); console.log(">> Claim_list success");
console.log(">> Number of claims:", response.data.result.claims.length)
var claimsList = response.data.result.claims;
console.log(">> Number of claims:", claimsList.length)
// return early if no claims were found // return early if no claims were found
if (response.data.result.claims.length === 0){ if (claimsList.length === 0){
res.status(200).sendFile(path.join(__dirname, '../public', 'noClaims.html')); reject("no claims were found");
console.log("exiting due to lack of claims");
return; return;
} }
// filter the claims to return free, public claims
var freePublicClaims = filterForFreePublicClaims(response.data.result.claims); // filter the claims to return only free, public claims
var freePublicClaims = filterForFreePublicClaims(claimsList);
// return early if no free, public claims were found // return early if no free, public claims were found
if (!freePublicClaims || (freePublicClaims.length === 0)){ if (!freePublicClaims || (freePublicClaims.length === 0)){
res.status(200).sendFile(path.join(__dirname, '../public', 'noClaims.html')); reject("no free, public claims were found");
console.log("exiting due to lack of free or public claims");
return; return;
} }
// order the claims // order the claims
var orderedPublcClaims = orderTopClaims(freePublicClaims); var orderedPublcClaims = orderTopClaims(freePublicClaims);
// create the uri for the first (selected) claim // create the uri for the first (selected) claim
console.log(">> ordered free public claims", orderedPublcClaims); console.log(">> ordered free public claims", orderedPublcClaims);
var freePublicClaimUri = "lbry://" + orderedPublcClaims[0].name + "#" + orderedPublcClaims[0].claim_id; var freePublicClaimUri = "lbry://" + orderedPublcClaims[0].name + "#" + orderedPublcClaims[0].claim_id;
console.log(">> your free public claim uri:", freePublicClaimUri); console.log(">> your free public claim uri:", freePublicClaimUri);
// fetch the image to display // fetch the image to display
axios.post('http://localhost:5279/lbryapi', { getClaimWithUri(freePublicClaimUri, resolve, reject);
method: "get",
params: {
uri: freePublicClaimUri
}
}
).then(function (getResponse) {
console.log(">> 'get claim' success...");
console.log(">> response data:", getResponse.data);
console.log(">> dl path =", getResponse.data.result.download_path)
// return the claim we got
res.status(200).sendFile(getResponse.data.result.download_path);
}).catch(function(getError){
console.log(">> /c/ 'get' error:", getError.response.data);
res.status(500).send(JSON.stringify({msg: "An error occurred while fetching the free, public claim by URI.", err: getError.response.data.error.message}));
})
}).catch(function(error){
console.log(">> /c/ error:", error.response.data);
res.status(500).send(JSON.stringify({msg: "An error occurred while getting the claim list.", err: error.response.data.error.message}));
}) })
.catch(function(error){
console.log(">> error:", error);
// reject the promise with an approriate message
reject(error.response.data.error);
kauffj commented 2017-05-31 20:40:55 +02:00 (Migrated from github.com)
Review

This should probably be defined as a constant.

This should probably be defined as a constant.
return;
});
});
// 3. return the promise
return deferred;
}, },
serveClaimBasedOnUri: function(uri, res){
getClaimBasedOnUri: function(uri){
/* /*
to do: need to pass the URI through a test (use 'resolve') to see if it is free and public. Right now it is jumping straight to 'get'ing and serving the asset. to do: need to pass the URI through a test (use 'resolve') to see if it is free and public. Right now it is jumping straight to 'get'ing and serving the asset.
*/ */
var deferred = new Promise(function (resolve, reject){
console.log(">> your uri:", uri); console.log(">> your uri:", uri);
// fetch the image to display // fetch the image to display
axios.post('http://localhost:5279/lbryapi', { // to do: abstract this code to a function that can be shared getClaimWithUri(uri, resolve, reject);
method: "get", });
params: { return deferred;
uri: uri
}
}
).then(function (getResponse) {
console.log(">> 'get claim' success...");
console.log(">> response data:", getResponse.data);
console.log(">> dl path =", getResponse.data.result.download_path)
/*
to do: make sure the file has completed downloading before serving back the file
*/
// return the claim we got
res.status(200).sendFile(getResponse.data.result.download_path);
/* delete the file after a certain amount of time? */
}).catch(function(error){
console.log(">> /c/ 'get' error:", error.response.data);
res.status(500).send(JSON.stringify({msg: "an error occurred", err: error.response.data.error.message}));
})
}, },
serveAllClaims: function(claimName, res){
serveAllClaims: function(claimName, res){ // note: work in progress
// make a call to the daemon to get the claims list // make a call to the daemon to get the claims list
axios.post('http://localhost:5279/lbryapi', { axios.post('http://localhost:5279/lbryapi', {
method: "claim_list", method: "claim_list",
params: { params: { name: claimName }
name: claimName
}
} }
).then(function (response) { ).then(function (response) {
console.log(">> Claim_list success"); console.log(">> Claim_list success");
@ -163,7 +179,10 @@ module.exports = {
// order the claims // order the claims
var orderedPublicClaims = orderTopClaims(freePublicClaims); var orderedPublicClaims = orderTopClaims(freePublicClaims);
// serve the response // serve the response
res.status(200).send(orderedPublicClaims); //to do: rather than returning json, serve a page of all these claims /*
to do: rather than returning json, serve a page of all these claims
*/
res.status(200).send(orderedPublicClaims);
}).catch(function(error){ }).catch(function(error){
console.log(">> /c/ error:", error.response.data); console.log(">> /c/ error:", error.response.data);
// serve the response // serve the response

View file

@ -28,6 +28,7 @@
"body-parser": "^1.17.1", "body-parser": "^1.17.1",
"connect-multiparty": "^2.0.0", "connect-multiparty": "^2.0.0",
"express": "^4.15.2", "express": "^4.15.2",
"nodemon": "^1.11.0" "nodemon": "^1.11.0",
"socket.io": "^2.0.1"
} }
} }

36
public/claim.html Normal file
View file

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Spee.ch Claim</title>
</head>
<body>
<div id="image">
<h1>spee.ch</h1>
<p>spee.ch is a single-serving site that reads and publishes images to and from the <a href="https://lbry.io">LBRY</a> blockchain.</p>
<h3>Status:</h3>
<p id="status">Your image is being retrieved</p>
</div>
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io();
var url = document.URL.substring(document.URL.indexOf('3000/') + 5);
// request the claim through the socket
socket.emit("claim-request", url);
// listen for updates
socket.on("claim-update", function(data){
console.log("data:", data);
document.getElementById("status").innerHTML = data;
})
// receive the claim through the socket
socket.on("claim-send", function(data){
if (data.image) {
var base64Image = 'data:image/jpeg;base64,' + data.buffer;
document.getElementById("image").innerHTML = '<img src="' + base64Image + '"/>';
}
})
</script>
</body>
</html>

BIN
public/eagle.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View file

@ -23,9 +23,11 @@
<br/> <br/>
<img src="" height="200" alt="Image preview..."/> <img src="" height="200" alt="Image preview..."/>
<br/> <br/>
Name: <input type="text" name="name" value="name"/>
<br/>
Title: <input type="text" name="title" value="title"/> Title: <input type="text" name="title" value="title"/>
<br/> <br/>
Description: <input type="text" name="description" value="description"/> Description: <input type="text" name="description" value="I love spee.ch!"/>
<br/> <br/>
Author: <input type="text" name="author" value="author"/> Author: <input type="text" name="author" value="author"/>
<br/> <br/>
@ -36,9 +38,9 @@
<option value="Public Domain">Public Domain</option> <option value="Public Domain">Public Domain</option>
</select> </select>
<br/> <br/>
NSFW: <select type="text" name="nsfw" value="nsfw"> NSFW: <select type="text" name="nsfw" value="false">
<option value="true">True</option>
<option value="false">False</option> <option value="false">False</option>
<option value="true">True</option>
</select> </select>
<br/> <br/>
<button type="submit">Submit</button> <button type="submit">Submit</button>
@ -101,6 +103,9 @@
previewFile(); //calls the function named previewFile() previewFile(); //calls the function named previewFile()
</script> </script>
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io();
</script>
</body> </body>
</html> </html>

View file

@ -6,6 +6,27 @@ var multipartMiddleware = multipart();
var lbryApi = require('../helpers/lbryApi.js'); var lbryApi = require('../helpers/lbryApi.js');
var queueApi = require('../helpers/queueApi.js'); var queueApi = require('../helpers/queueApi.js');
// helper functions
function createPublishObject(req){
var publishObject = {
"method":"publish",
"params": {
"name": req.body.name,
"file_path": req.files.file.path,
"bid": 0.1,
"metadata": {
"description": req.body.description,
"title": req.body.title,
"author": req.body.author,
"language": req.body.language,
"license": req.body.license,
"nsfw": req.body.nsfw
}
}
};
return publishObject;
}
// routes to export // routes to export
module.exports = function(app){ module.exports = function(app){
// route to fetch one free public claim // route to fetch one free public claim
@ -15,30 +36,10 @@ module.exports = function(app){
}); });
// route to publish a new claim // route to publish a new claim
app.post("/publish", multipartMiddleware, function(req, res){ app.post("/publish", multipartMiddleware, function(req, res){
// receive the request
console.log(" >> POST request on /publish"); console.log(" >> POST request on /publish");
//console.log(">> req.files:", req.files)
console.log(" >> req.body:", req.body)
// build the data needed to publish the file // build the data needed to publish the file
var publishObject = { var publishObject = createPublishObject(req);
"method":"publish", console.log("publish", publishObject);
"params": {
"name": req.body.title,
"file_path": req.files.file.path,
"bid": 0.1,
"metadata": {
"description": req.body.description,
"title": req.body.title,
"author": req.body.author,
"language": req.body.language,
"license": req.body.license,
"nsfw": req.body.nsfw.value
}
}
};
//console.log(">> publishObject:", publishObject)
// post the task to the que // post the task to the que
queueApi.addNewTaskToQueue(JSON.stringify({ queueApi.addNewTaskToQueue(JSON.stringify({
type: 'publish', type: 'publish',
@ -46,7 +47,6 @@ module.exports = function(app){
})); }));
// respond to the client that the task has been queued // respond to the client that the task has been queued
res.status(200).sendFile(path.join(__dirname, '../public', 'publishingClaim.html')); res.status(200).sendFile(path.join(__dirname, '../public', 'publishingClaim.html'));
}); });
// route to fetch one free public claim // route to fetch one free public claim
app.get("/:name/all", function(req, res){ app.get("/:name/all", function(req, res){
@ -56,23 +56,21 @@ module.exports = function(app){
}); });
// route to fetch one free public claim // route to fetch one free public claim
app.get("/:name/:claim_id", function(req, res){ app.get("/:name/:claim_id", function(req, res){
var uri = "lbry://" + req.params.name + "#" + req.params.claim_id; console.log(">> GET request on /" + req.params.name + "#" + req.params.claim_id);
console.log(">> GET request on /" + uri); res.status(200).sendFile(path.join(__dirname, '../public', 'claim.html'));
lbryApi.serveClaimBasedOnUri(uri, res);
}); });
// route to fetch one free public claim // route to fetch one free public claim
app.get("/:name", function(req, res){ app.get("/:name", function(req, res){
var name = req.params.name; var name = req.params.name;
console.log(">> GET request on /" + name) console.log(">> GET request on /" + name)
// publish a message to the cue // send page (includes a socket to get the file)
// queueApi.addNewTaskToQueue("return claim for " + req.params.name + " ...") res.status(200).sendFile(path.join(__dirname, '../public', 'claim.html'));
// retrieve the claim
lbryApi.serveClaimBasedOnNameOnly(name, res);
}); });
// route for the home page // route for the home page
app.get("/", function(req, res){ app.get("/", function(req, res){
res.sendFile(path.join(__dirname, '../public', 'index.html')); res.sendFile(path.join(__dirname, '../public', 'index.html'));
}); });
// a catch-all route if someone visits a page that does not exist // a catch-all route if someone visits a page that does not exist
app.use("*", function(req, res){ app.use("*", function(req, res){
res.sendFile(path.join(__dirname, '../public', 'fourOhfour.html')); res.sendFile(path.join(__dirname, '../public', 'fourOhfour.html'));

57
routes/sockets-routes.js Normal file
View file

@ -0,0 +1,57 @@
module.exports = function(app) {
var http = require('http').Server(app);
var io = require('socket.io')(http);
var fs = require('fs');
var path = require('path');
var lbryApi = require('../helpers/lbryApi.js');
function sendTheImage(socket, filePath){
fs.readFile(filePath, function(err, buff){
if (err) {
console.log("socket: fs err:", err);
return;
};
//console.log("buff", buff);
socket.emit('claim-send', { image: true, buffer: buff.toString('base64') });
console.log('socket: the image file has been sent via sockets');
});
}
io.on('connection', function(socket){
console.log('a user connected');
// serve an image file from the server
socket.on('claim-request', function(query){
// 1. retrieve the image from lbry via daemon
console.log("socket: received claim request for:", query)
if (query.indexOf("/") === -1){
var promise = lbryApi.getClaimBasedOnNameOnly(query)
} else {
var uri = query.replace("/", "#");
var promise = lbryApi.getClaimBasedOnUri(uri)
}
promise.then(function(data){
console.log("socket: claim-request - success:", data)
// 3. serve the image back once it is retrieved
sendTheImage(socket, data);
return;
})
.catch(function(error){
console.log("socket: claim-request - error:", error)
// handle the error
socket.emit("claim-update", error);
return;
});
// 2. emit updates as the image is being retrieved
socket.emit("claim-update", "We are getting your claim for " + query);
})
// handle disconnect
socket.on('disconnect', function(){
console.log('user disconnected');
});
});
return http;
}

View file

@ -2,19 +2,29 @@
var express = require('express'); var express = require('express');
var bodyParser = require('body-parser'); var bodyParser = require('body-parser');
var path = require('path'); var path = require('path');
// set port // set port
var PORT = 80; var PORT = 3000;
// initialize express
// initialize express app
var app = express(); var app = express();
// make express look in the public directory for assets (css/js/img) // make express look in the public directory for assets (css/js/img)
app.use(express.static(__dirname + '/public')); app.use(express.static(__dirname + '/public'));
// configure epress
// configure express app
app.use(bodyParser.json()); // for parsing application/json app.use(bodyParser.json()); // for parsing application/json
app.use(bodyParser.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded app.use(bodyParser.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded
// require in routes
// require express routes
require("./routes/api-routes.js")(app); require("./routes/api-routes.js")(app);
require("./routes/html-routes.js")(app); require("./routes/html-routes.js")(app);
// include socket.io functionality
// this wraps the server in sockets, to intercept incoming sockets requests
var http = require("./routes/sockets-routes.js")(app);
// start server // start server
app.listen(PORT, function() { http.listen(PORT, function() {
console.log("Listening on PORT " + PORT); console.log("Listening on PORT " + PORT);
}); });