From 4cfea0ac3df479573d2d36eee8e2a80a8ee169bd Mon Sep 17 00:00:00 2001 From: Hippo Date: Thu, 14 May 2020 22:05:29 +0530 Subject: [PATCH] Allow Seance upload/download through websockets It's all working! Yay!! --- package.json | 1 + server.js | 91 +++++++++++++++++++++++++++ static/app.js | 138 +++++++++++++++++++++++++++++++++++++++++ views/fetch-medium.vue | 9 ++- yarn.lock | 19 ++++++ 5 files changed, 253 insertions(+), 5 deletions(-) create mode 100644 static/app.js diff --git a/package.json b/package.json index aed6fa6..2eb9e77 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "css-loader": "^3.5.3", "dotenv": "^8.2.0", "express": "^4.17.1", + "express-ws": "^4.0.0", "js-yaml": "^3.13.1", "markdown": "^0.5.0", "mediumexporter": "github:badrihippo/mediumexporter#seance-latest", diff --git a/server.js b/server.js index 61409cb..23c5f1b 100644 --- a/server.js +++ b/server.js @@ -4,7 +4,10 @@ const { vueRenderer } = require('@doweb/vuexpress') const slugify = require('underscore.string/slugify') const fs = require('fs') +const { Seance } = require ('./seance') + const app = express() +var expressWs = require('express-ws')(app) // ALlow URLencoded API app.use(bodyParser.urlencoded({ @@ -25,6 +28,9 @@ let options = { watch: true, metaInfo: { title: 'Seance', + script: [ + { type: 'text/javascript', src: '/app.js' }, + ], }, extractCSS: true, cssOutputPath: '/css/styles.css', @@ -110,6 +116,7 @@ app.post('/fetch', (req, res) => { author: post.author, featuredImage: post.featuredImage, mediumUrl: post.mediumUrl, + slug: post.slug, } }) }) @@ -125,6 +132,90 @@ app.get('/api', (req, res) => { }) }) +app.ws('/ws/fetch-medium', (ws, req) => { + ws.on('message', async(msg) => { + console.log(`We got: ${msg}`) + + command = msg.split(' ') + if (command.length == 3 && command[0] == 'fetch') { + const postSlug = command[1] + const mediumUrl = command[2] + + const postMetaFile = `${postSlug}.json` + + if (!fs.existsSync(postMetaFile)) { + ws.send(`error: post ${postSlug} not yet loaded`) + return + } + + // Start the Seance session + seance = new Seance() + + // set up handlers + seance.on('update', (e) => { + ws.send(`update: ${e.message}`) + }) + + seance.on('notification', (e) => { + ws.send(`notification: ${e.message}`) + }) + + seance.on('error', (e) => { + ws.send(`error: ${e.message}`) + }) + + seance.fetchFromMedium(postMetaFile) + .then((post) => { + console.info(`"${post.title}" fetched successfully.`) + ws.send(`done: ${post.slug}`) + }) + } + + }) + + ws.on('close', () => { + console.log('socket closed') + }) +}) + +app.ws('/ws/push-ghost', (ws, req) => { + ws.on('message', async(msg) => { + console.log(`We got: ${msg}`) + + command = msg.split(' ') + if (command.length == 2 && command[0] == 'push') { + const postSlug = command[1] + + // Start the Seance session + seance = new Seance() + + // set up handlers + seance.on('update', (e) => { + ws.send(`update: ${e.message}`) + }) + + seance.on('notification', (e) => { + ws.send(`notification: ${e.message}`) + }) + + seance.on('error', (e) => { + ws.send(`error: ${e.message}`) + }) + + seance.pushToGhost(postSlug) + .then(() => { + console.info(`"${postSlug}" pushed successfully.`) + ws.send(`done: ${postSlug}`) + }) + } + + }) + + ws.on('close', () => { + console.log('socket closed') + }) +}) + const port = process.env.PORT || 4000 app.listen(port, () => { console.log(`Listening on ${port}`) diff --git a/static/app.js b/static/app.js new file mode 100644 index 0000000..04cc108 --- /dev/null +++ b/static/app.js @@ -0,0 +1,138 @@ +function addNotification(msg, className='is-warning') { + var n = document.createElement('div') + n.classList.add('notification') + n.classList.add(className) + n.innerHTML = msg + document.getElementById('notifications').appendChild(n) +} + +function mediumFetch(e) { + if(e) { e.preventDefault() } + + const postSlug = document.querySelector('[data-post-slug]').dataset.postSlug + const mediumUrl = document.querySelector('[data-post-mediumurl]').dataset.postMediumurl + console.log('Fetching ' + postSlug) + const socketProtocol = (window.location.protocol === 'https:' ? 'wss:' : 'ws:') + const socketUrl = socketProtocol + '//' + window.location.host + '/ws/fetch-medium/' + const socket = new WebSocket(socketUrl) + + // set up the div + statusDiv = document.getElementById('statusbar') + statusText = document.createElement('p') + statusText.innerText = 'Fetching post' + statusText.classList.add('card-footer-item') + + // hide buttons + for (i=0;i { + socket.send('fetch ' + postSlug + ' ' + mediumUrl) + } + + socket.onmessage = m => { + console.log('Got message: ' + m.data) + + console.log(m.data) + if (m.data.indexOf(': ') != -1) { + var command = m.data.slice(0, m.data.indexOf(': ')) + var commandData = m.data.slice(m.data.indexOf(': ')+2) + + statusText.innerHTML = `${command}: ${commandData}` + + // if it's done, clean up + if (command == 'done') { + // remove statusbar + statusDiv.removeChild(statusText) + + // show alert + addNotification(`${postSlug} has been fetched successfully. You can now push it to Ghost`, 'is-success') + + // show buttons, change text and save slug + for (i=0;i { + socket.send('push ' + postSlug) + } + + socket.onmessage = m => { + console.log('Got message: ' + m.data) + + console.log(m.data) + + if (m.data.indexOf(': ') != -1) { + var command = m.data.slice(0, m.data.indexOf(': ')) + var commandData = m.data.slice(m.data.indexOf(': ')+2) + + statusText.innerHTML = `${command}: ${commandData}` + + // if it's done, clean up + if (command[0] == 'done') { + // remove statusbar + statusDiv.removeChild(statusText) + + // show alert + addNotification(`${postSlug} has been pushed to Ghost`, 'is-success') + + // show buttons and change text + for (i=0;i
-

{{ title }}

-

Fetch from Medium

@@ -41,11 +39,12 @@

{{ post.title }}

{{ post.subtitle }}

by {{ post.author }}

+
-
diff --git a/yarn.lock b/yarn.lock index 71ddaaa..bf9926b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -388,6 +388,11 @@ async-foreach@^0.1.3: resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542" integrity sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI= +async-limiter@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" + integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== + async@^2.3.0: version "2.6.3" resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" @@ -2263,6 +2268,13 @@ expand-brackets@^2.1.4: snapdragon "^0.8.1" to-regex "^3.0.1" +express-ws@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/express-ws/-/express-ws-4.0.0.tgz#dabd8dc974516418902a41fe6e30ed949b4d36c4" + integrity sha512-KEyUw8AwRET2iFjFsI1EJQrJ/fHeGiJtgpYgEWG3yDv4l/To/m3a2GaYfeGyB3lsWdvbesjF5XCMx+SVBgAAYw== + dependencies: + ws "^5.2.0" + express@^4.17.1: version "4.17.1" resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" @@ -6740,6 +6752,13 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= +ws@^5.2.0: + version "5.2.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f" + integrity sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA== + dependencies: + async-limiter "~1.0.0" + xml2js@^0.4.19: version "0.4.22" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.22.tgz#4fa2d846ec803237de86f30aa9b5f70b6600de02"