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`
* from your terminal, run `npm start`
* to run hot, run `nodemon server.js`
* start at least one worker by running `node worker.js`
* visit [localhost:3000](http://localhost:3000) and enjoy!
* start a worker by running `node worker.js` in a separate session in your terminal
* visit [localhost:3000](http://localhost:3000)
## site navigation

View file

@ -37,7 +37,35 @@ function orderTopClaims(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 = {
publishClaim: function(publishObject){
axios.post('http://localhost:5279/lbryapi', publishObject)
.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}));
})
},
serveClaimBasedOnNameOnly: function(claimName, res){
// make a call to the daemon to get the claims list
axios.post('http://localhost:5279/lbryapi', {
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
axios.post('http://localhost:5279/lbryapi', { // receives a promise
method: "claim_list",
params: {
name: claimName
}
}
).then(function (response) {
console.log(">> Claim_list success");
console.log(">> Number of claims:", response.data.result.claims.length)
// return early if no claims were found
if (response.data.result.claims.length === 0){
res.status(200).sendFile(path.join(__dirname, '../public', 'noClaims.html'));
return;
}
// filter the claims to return free, public claims
var freePublicClaims = filterForFreePublicClaims(response.data.result.claims);
// return early if no free, public claims were found
if (!freePublicClaims || (freePublicClaims.length === 0)){
res.status(200).sendFile(path.join(__dirname, '../public', 'noClaims.html'));
return;
}
// order the claims
var orderedPublcClaims = orderTopClaims(freePublicClaims);
// create the uri for the first (selected) claim
console.log(">> ordered free public claims", orderedPublcClaims);
var freePublicClaimUri = "lbry://" + orderedPublcClaims[0].name + "#" + orderedPublcClaims[0].claim_id;
console.log(">> your free public claim uri:", freePublicClaimUri);
// fetch the image to display
axios.post('http://localhost:5279/lbryapi', {
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}));
params: { name: claimName }
})
}).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}));
})
.then(function (response) {
console.log(">> Claim_list success");
var claimsList = response.data.result.claims;
console.log(">> Number of claims:", claimsList.length)
// return early if no claims were found
if (claimsList.length === 0){
reject("no claims were found");
console.log("exiting due to lack of claims");
return;
}
// filter the claims to return only free, public claims
var freePublicClaims = filterForFreePublicClaims(claimsList);
// return early if no free, public claims were found
if (!freePublicClaims || (freePublicClaims.length === 0)){
reject("no free, public claims were found");
console.log("exiting due to lack of free or public claims");
return;
}
// order the claims
var orderedPublcClaims = orderTopClaims(freePublicClaims);
// create the uri for the first (selected) claim
console.log(">> ordered free public claims", orderedPublcClaims);
var freePublicClaimUri = "lbry://" + orderedPublcClaims[0].name + "#" + orderedPublcClaims[0].claim_id;
console.log(">> your free public claim uri:", freePublicClaimUri);
// fetch the image to display
getClaimWithUri(freePublicClaimUri, resolve, reject);
})
.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.
*/
console.log(">> your uri:", uri);
// fetch the image to display
axios.post('http://localhost:5279/lbryapi', { // to do: abstract this code to a function that can be shared
method: "get",
params: {
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);
var deferred = new Promise(function (resolve, reject){
console.log(">> your uri:", uri);
// fetch the image to display
getClaimWithUri(uri, resolve, reject);
});
return deferred;
/* 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
axios.post('http://localhost:5279/lbryapi', {
method: "claim_list",
params: {
name: claimName
}
params: { name: claimName }
}
).then(function (response) {
console.log(">> Claim_list success");
@ -163,7 +179,10 @@ module.exports = {
// order the claims
var orderedPublicClaims = orderTopClaims(freePublicClaims);
// 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){
console.log(">> /c/ error:", error.response.data);
// serve the response

View file

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

View file

@ -6,6 +6,27 @@ var multipartMiddleware = multipart();
var lbryApi = require('../helpers/lbryApi.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
module.exports = function(app){
// route to fetch one free public claim
@ -15,30 +36,10 @@ module.exports = function(app){
});
// route to publish a new claim
app.post("/publish", multipartMiddleware, function(req, res){
// receive the request
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
var publishObject = {
"method":"publish",
"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)
var publishObject = createPublishObject(req);
console.log("publish", publishObject);
// post the task to the que
queueApi.addNewTaskToQueue(JSON.stringify({
type: 'publish',
@ -46,7 +47,6 @@ module.exports = function(app){
}));
// respond to the client that the task has been queued
res.status(200).sendFile(path.join(__dirname, '../public', 'publishingClaim.html'));
});
// route to fetch one free public claim
app.get("/:name/all", function(req, res){
@ -56,23 +56,21 @@ module.exports = function(app){
});
// route to fetch one free public claim
app.get("/:name/:claim_id", function(req, res){
var uri = "lbry://" + req.params.name + "#" + req.params.claim_id;
console.log(">> GET request on /" + uri);
lbryApi.serveClaimBasedOnUri(uri, res);
console.log(">> GET request on /" + req.params.name + "#" + req.params.claim_id);
res.status(200).sendFile(path.join(__dirname, '../public', 'claim.html'));
});
// route to fetch one free public claim
app.get("/:name", function(req, res){
var name = req.params.name;
console.log(">> GET request on /" + name)
// publish a message to the cue
// queueApi.addNewTaskToQueue("return claim for " + req.params.name + " ...")
// retrieve the claim
lbryApi.serveClaimBasedOnNameOnly(name, res);
// send page (includes a socket to get the file)
res.status(200).sendFile(path.join(__dirname, '../public', 'claim.html'));
});
// route for the home page
app.get("/", function(req, res){
res.sendFile(path.join(__dirname, '../public', 'index.html'));
});
// a catch-all route if someone visits a page that does not exist
app.use("*", function(req, res){
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 bodyParser = require('body-parser');
var path = require('path');
// set port
var PORT = 80;
// initialize express
var PORT = 3000;
// initialize express app
var app = express();
// make express look in the public directory for assets (css/js/img)
app.use(express.static(__dirname + '/public'));
// configure epress
// configure express app
app.use(bodyParser.json()); // for parsing application/json
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/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
app.listen(PORT, function() {
http.listen(PORT, function() {
console.log("Listening on PORT " + PORT);
});