Set up WebSocket for email sending
The actual sending doesn't happen yet, but all the frontend infrastructure is set up all right!
This commit is contained in:
parent
66056cbf0b
commit
9b630ae021
4 changed files with 220 additions and 75 deletions
|
@ -21,6 +21,7 @@
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
|
"express-ws": "^4.0.0",
|
||||||
"importabular": "^0.2.9",
|
"importabular": "^0.2.9",
|
||||||
"showdown": "^1.9.1",
|
"showdown": "^1.9.1",
|
||||||
"sirv-cli": "^1.0.0"
|
"sirv-cli": "^1.0.0"
|
||||||
|
|
37
server.js
37
server.js
|
@ -3,6 +3,8 @@ const express = require('express')
|
||||||
require('dotenv').config()
|
require('dotenv').config()
|
||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
|
var expressWs = require('express-ws')(app)
|
||||||
|
|
||||||
const port = process.env.port || 5000
|
const port = process.env.port || 5000
|
||||||
|
|
||||||
const cors = require('cors')
|
const cors = require('cors')
|
||||||
|
@ -18,10 +20,43 @@ app.get('/air', (req, res) => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
app.ws('/hit-send', (ws, request) => {
|
||||||
|
console.log('Hitting send on some emails!')
|
||||||
|
|
||||||
|
ws.on('message', (message) => {
|
||||||
|
try {
|
||||||
|
message = JSON.parse(message)
|
||||||
|
} catch(err) {
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
success: false,
|
||||||
|
error: "We can't understand what you're saying! Me speak JSON only.",
|
||||||
|
}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n\n-------------------------- BEGIN EMAIL --------------------------\n`)
|
||||||
|
console.log(`From: ${message.from}\nTo:${message.to}\n\n${message.text}`)
|
||||||
|
console.log(`\n--------------------------- END EMAIL ---------------------------`)
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
from: message.from,
|
||||||
|
to: message.to,
|
||||||
|
}))
|
||||||
|
}, 2000)
|
||||||
|
})
|
||||||
|
|
||||||
|
ws.on('close', () => {
|
||||||
|
console.log('socket closed')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
app.use(express.static('public'))
|
app.use(express.static('public'))
|
||||||
app.get('*', (req, res) => {
|
app.get('/', (req, res) => {
|
||||||
res.sendFile(path.resolve(__dirname, 'public', 'index.html'))
|
res.sendFile(path.resolve(__dirname, 'public', 'index.html'))
|
||||||
})
|
})
|
||||||
|
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
console.log(`Server is up at port ${port}`)
|
console.log(`Server is up at port ${port}`)
|
||||||
})
|
})
|
||||||
|
|
238
src/App.svelte
238
src/App.svelte
|
@ -195,88 +195,178 @@
|
||||||
function prev() {
|
function prev() {
|
||||||
step -= 1
|
step -= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let nowSending
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hit Send
|
||||||
|
*
|
||||||
|
* collates all the data for sending, and passes it
|
||||||
|
* on to the backend for actual processing. Note that here we
|
||||||
|
* cookie-cut the emails on the frontend itself; the backend
|
||||||
|
* just blindly takes what it gets. We're doing this so that we
|
||||||
|
* can use the same getPreview() function and be consistent: we
|
||||||
|
* don't want the final to suddenly end up looking very different
|
||||||
|
* from the preview!
|
||||||
|
*/
|
||||||
|
function hitSend() {
|
||||||
|
step = 4
|
||||||
|
let emails = []
|
||||||
|
|
||||||
|
// process the 'i'th row of the sheet, one by one
|
||||||
|
for (let i=0; i<sheetOne.getData().length; i++) {
|
||||||
|
let previewData = getPreview(i)
|
||||||
|
|
||||||
|
// skip if email is blank
|
||||||
|
if (!previewData.email) continue
|
||||||
|
|
||||||
|
// add it to the list of emails to be sent
|
||||||
|
emails.push({
|
||||||
|
from: fromEmail,
|
||||||
|
to: previewData.email,
|
||||||
|
text: previewData.text,
|
||||||
|
html: converter.makeHtml(previewData.text),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// okay, now let's open a socket to the server!
|
||||||
|
let socketProtocol = (window.location.protocol === 'https:' ? 'wss:' : 'ws:')
|
||||||
|
let socketUrl = socketProtocol + '//' + window.location.host + window.location.pathname + 'hit-send/'
|
||||||
|
let socket = new WebSocket(socketUrl)
|
||||||
|
|
||||||
|
socket.onopen = function() {
|
||||||
|
nowSending = emails.pop()
|
||||||
|
socket.send(JSON.stringify(nowSending))
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.onmessage = (message) => {
|
||||||
|
try {
|
||||||
|
message = JSON.parse(message.data)
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Received malformed response from server: ${message.data}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.success) {
|
||||||
|
console.log(`${message.to}'s email sent successfully!`)
|
||||||
|
|
||||||
|
// send the next one
|
||||||
|
nowSending = emails.pop()
|
||||||
|
if (!!nowSending) {
|
||||||
|
socket.send(JSON.stringify(nowSending))
|
||||||
|
console.log(`Sending: ${nowSending.to}`)
|
||||||
|
} else {
|
||||||
|
socket.close()
|
||||||
|
step = 2
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('Something went wrong :(')
|
||||||
|
step = 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<img src="/assets/email-baker.png" class="main-pic" alt="Email Oven"/>
|
{#if step < 4}
|
||||||
<h1>Chip <span class="highlight">Choc</span></h1>
|
<img src="/assets/email-baker.png" class="main-pic" alt="Email Oven"/>
|
||||||
<h2>Cookie-cutter emails made easy.</h2>
|
<h1>Chip <span class="highlight">Choc</span></h1>
|
||||||
|
<h2>Cookie-cutter emails made easy.</h2>
|
||||||
|
|
||||||
{#if step >= 1}
|
{#if step >= 1}
|
||||||
<h3 class="mt-3">Step 1</h3>
|
<h3 class="mt-3">Step 1</h3>
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if step == 0}
|
|
||||||
<button on:click={showEmail}>Start Drafting</button>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if step >= 1}
|
|
||||||
<div class="instructions">
|
|
||||||
<p>Compose your email below, leaving [TK stuff] to be replaced in the table. Make sure each TK is a single word: no spaces allowed!</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="email-content">
|
|
||||||
<p>From: <input type="text" bind:value={fromEmail}/></p>
|
|
||||||
<textarea bind:value={emailContent}/>
|
|
||||||
<p>
|
|
||||||
<strong>Detected fields:</strong>
|
|
||||||
{#each tkList as tk (tk)}
|
|
||||||
<span class="tag">{tk}</span>
|
|
||||||
{/each}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if step >= 2}
|
|
||||||
<h3 class="mt-3">Step 1</h3>
|
|
||||||
<div class="instructions">
|
|
||||||
<p>Now, fill in the fields for each user. (You can also copy-paste rows and columns directly from Air, if the ordering is right!)</p>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if step == 1}
|
|
||||||
<button on:click={showSheet}>Next</button>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if step > 1}
|
|
||||||
<button on:click={refreshSheet}>Refresh</button>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div id="editor" bind:this={editor}></div>
|
|
||||||
|
|
||||||
{#if step >= 2 && !!cookiePreviewData}
|
|
||||||
<h4 class="mt-3">Preview</h4>
|
|
||||||
|
|
||||||
<div class="cookie-preview">
|
|
||||||
<div class="preview-header">
|
|
||||||
<p>From: {fromEmail || '[NOBODY!!!]'}</p>
|
|
||||||
<p>To: {cookiePreviewData.email || '[NOBODY!!!]'}</p>
|
|
||||||
<hr />
|
|
||||||
</div>
|
|
||||||
{@html converter.makeHtml(cookiePreviewData.text)}
|
|
||||||
</div>
|
|
||||||
<button on:click={decrementPreviewRow}>< Prev</button>
|
|
||||||
<button on:click={incrementPreviewRow}>Next ></button>
|
|
||||||
|
|
||||||
<h3>Step 3</h3>
|
|
||||||
|
|
||||||
{#if step == 2}
|
|
||||||
<div class="instructions">
|
|
||||||
<p>If everything looks okay, then we're done! Click on the button below to put the cutter in action.</p>
|
|
||||||
</div>
|
|
||||||
<button on:click={next}>Yup, send 'em out!</button>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if step == 3}
|
{#if step == 0}
|
||||||
<div class="instructions">
|
<button on:click={showEmail}>Start Drafting</button>
|
||||||
<p>Are you <strong>sure</strong> you want to send them out?</p>
|
|
||||||
<p>Are you ready?</p>
|
|
||||||
<p>Did you look through all the previews?</p>
|
|
||||||
<p>Have you double-checked the names and email IDs to make sure they match?</p>
|
|
||||||
</div>
|
|
||||||
<button>Hit send!</button> <button on:click={prev}>Ummmm..</button>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if step >= 1}
|
||||||
|
<div class="instructions">
|
||||||
|
<p>Compose your email below, leaving [TK stuff] to be replaced in the table. Make sure each TK is a single word: no spaces allowed!</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="email-content">
|
||||||
|
<p>From: <input type="text" bind:value={fromEmail}/></p>
|
||||||
|
<textarea bind:value={emailContent}/>
|
||||||
|
<p>
|
||||||
|
<strong>Detected fields:</strong>
|
||||||
|
{#each tkList as tk (tk)}
|
||||||
|
<span class="tag">{tk}</span>
|
||||||
|
{/each}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if step >= 2}
|
||||||
|
<h3 class="mt-3">Step 2</h3>
|
||||||
|
<div class="instructions">
|
||||||
|
<p>Now, fill in the fields for each user. (You can also copy-paste rows and columns directly from Air, if the ordering is right!)</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if step == 1}
|
||||||
|
<button on:click={showSheet}>Next</button>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if step > 1}
|
||||||
|
<button on:click={refreshSheet}>Refresh</button>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div id="editor" bind:this={editor}></div>
|
||||||
|
|
||||||
|
{#if step >= 2 && !!cookiePreviewData}
|
||||||
|
<h4 class="mt-3">Preview</h4>
|
||||||
|
|
||||||
|
<div class="cookie-preview">
|
||||||
|
<div class="preview-header">
|
||||||
|
<p>From: {fromEmail || '[NOBODY!!!]'}</p>
|
||||||
|
<p>To: {cookiePreviewData.email || '[NOBODY!!!]'}</p>
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
{@html converter.makeHtml(cookiePreviewData.text)}
|
||||||
|
</div>
|
||||||
|
<button on:click={decrementPreviewRow}>< Prev</button>
|
||||||
|
<button on:click={incrementPreviewRow}>Next ></button>
|
||||||
|
|
||||||
|
<h3>Step 3</h3>
|
||||||
|
|
||||||
|
{#if step == 2}
|
||||||
|
<div class="instructions">
|
||||||
|
<p>If everything looks okay, then we're done! Click on the button below to put the cutter in action.</p>
|
||||||
|
</div>
|
||||||
|
<button on:click={next}>Yup, send 'em out!</button>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if step == 3}
|
||||||
|
<div class="instructions">
|
||||||
|
<p>Are you <strong>sure</strong> you want to send them out?</p>
|
||||||
|
<p>Are you ready?</p>
|
||||||
|
<p>Did you look through all the previews?</p>
|
||||||
|
<p>Have you double-checked the names and email IDs to make sure they match?</p>
|
||||||
|
</div>
|
||||||
|
<button on:click={hitSend}>Hit send!</button> <button on:click={prev}>Ummmm..</button>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if step == 4}
|
||||||
|
<div class="instructions">
|
||||||
|
{#if !!nowSending }
|
||||||
|
<div class="cookie-preview">
|
||||||
|
<div class="preview-header">
|
||||||
|
<p>From: {nowSending.from || '[NOBODY!!!]'}</p>
|
||||||
|
<p>To: {nowSending.to || '[NOBODY!!!]'}</p>
|
||||||
|
<hr />
|
||||||
|
{@html nowSending.html}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<h1>Chip <span class="highlight">Choc</span></h1>
|
||||||
|
<h2>Your cookies are being cut...</h2>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
19
yarn.lock
19
yarn.lock
|
@ -125,6 +125,11 @@ array-flatten@1.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
|
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
|
||||||
integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
|
integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
|
||||||
|
|
||||||
|
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==
|
||||||
|
|
||||||
balanced-match@^1.0.0:
|
balanced-match@^1.0.0:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
||||||
|
@ -358,6 +363,13 @@ etag@~1.8.1:
|
||||||
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
|
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
|
||||||
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
|
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
|
||||||
|
|
||||||
|
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:
|
express@^4.17.1:
|
||||||
version "4.17.1"
|
version "4.17.1"
|
||||||
resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
|
resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
|
||||||
|
@ -1148,6 +1160,13 @@ wrappy@1:
|
||||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||||
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
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"
|
||||||
|
|
||||||
ws@^7.4.3:
|
ws@^7.4.3:
|
||||||
version "7.4.5"
|
version "7.4.5"
|
||||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.5.tgz#a484dd851e9beb6fdb420027e3885e8ce48986c1"
|
resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.5.tgz#a484dd851e9beb6fdb420027e3885e8ce48986c1"
|
||||||
|
|
Loading…
Reference in a new issue