aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2020-06-13 21:39:22 -0700
committerAdam Soltys <[email protected]>2020-06-13 21:39:22 -0700
commit74b7d2a0d614f170ba5aea3a1a172a7a68356d80 (patch)
treebe2a73a9917616c0b49c24dc99642378e5a47549
parentdcb21adb40a44c8108f1d652cc6ddd59ae7c4024 (diff)
downloadcoinos-server-74b7d2a0d614f170ba5aea3a1a172a7a68356d80.tar.gz
coinos-server-74b7d2a0d614f170ba5aea3a1a172a7a68356d80.zip
push notifications
-rw-r--r--.gitignore1
-rw-r--r--db/models/users.js15
-rw-r--r--index.js3
-rw-r--r--lib/notifications.js23
-rw-r--r--lib/sockets.js93
-rw-r--r--package.json1
-rw-r--r--routes/bitcoin/receive.js10
-rw-r--r--routes/info.js (renamed from routes/balances.js)11
-rw-r--r--routes/lightning/receive.js6
-rw-r--r--routes/liquid/receive.js8
-rw-r--r--routes/send.js3
-rw-r--r--routes/users.js41
-rw-r--r--yarn.lock68
13 files changed, 234 insertions, 49 deletions
diff --git a/.gitignore b/.gitignore
index 411939f1..afccb712 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@ config.js
.env
config/config.json
config/index.js
+config/clientversion
yarn-error.log
.pnp*
.yarn*
diff --git a/db/models/users.js b/db/models/users.js
index 11331cf7..1c16f737 100644
--- a/db/models/users.js
+++ b/db/models/users.js
@@ -127,6 +127,21 @@ const attributes = {
comment: null,
field: "updatedAt"
},
+ subscriptions: {
+ type: DataTypes.TEXT,
+ get: function() {
+ return JSON.parse(this.getDataValue("subscriptions"));
+ },
+ set: function(value) {
+ return this.setDataValue("subscriptions", JSON.stringify(value));
+ },
+ allowNull: true,
+ defaultValue: null,
+ primaryKey: false,
+ autoIncrement: false,
+ comment: null,
+ field: "subscriptions"
+ },
twofa: {
type: DataTypes.INTEGER(1),
allowNull: true,
diff --git a/index.js b/index.js
index 3b1c687f..8372f49b 100644
--- a/index.js
+++ b/index.js
@@ -30,9 +30,10 @@ require("./lib/utils");
require("./lib/sockets");
require("./lib/passport");
require("./lib/rates");
+require("./lib/notifications");
require("./routes/assets");
-require("./routes/balances");
+require("./routes/info");
require("./routes/invoices");
require("./routes/payments");
require("./routes/users");
diff --git a/lib/notifications.js b/lib/notifications.js
new file mode 100644
index 00000000..710a5bfe
--- /dev/null
+++ b/lib/notifications.js
@@ -0,0 +1,23 @@
+const webpush = require("web-push");
+
+if (config.vapid) {
+ webpush.setVapidDetails(
+ config.vapid.url,
+ config.vapid.publicKey,
+ config.vapid.privateKey
+ );
+}
+
+notify = (user, payload) => {
+ let { subscriptions } = user;
+
+ if (subscriptions) {
+ subscriptions.map(async subscription => {
+ try {
+ await webpush.sendNotification(subscription, payload);
+ } catch (e) {
+ l.error("problem sending notification", e);
+ }
+ });
+ }
+};
diff --git a/lib/sockets.js b/lib/sockets.js
index 89beffb3..07af88d8 100644
--- a/lib/sockets.js
+++ b/lib/sockets.js
@@ -37,47 +37,66 @@ const names = {};
wss.on("connection", function(ws, req) {
const id = uuidv4();
- setTimeout(() => ws.ok || ws.close(), 2000);
+ setTimeout(() => ws.user || ws.close(), 2000);
- ws.on("message", async token => {
- if (!token || token === "null") return;
+ ws.on("message", async (message) => {
+ let type, data;
try {
- const username = jwt.decode(token).username;
- if (!sockets[username]) sockets[username] = {};
- sockets[username][id] = ws;
- names[id] = username;
+ ({ type, data } = JSON.parse(message));
+ } catch (e) {
+ l.error("coudn't parse socket message");
+ }
- let user = await getUser(username);
- let payments = await user.getPayments({
- where: {
- account_id: user.account_id,
- },
- order: [["id", "DESC"]],
- limit: 12,
- include: {
- model: db.Account,
- as: "account"
- }
- });
- let accounts = await user.getAccounts();
- user = user.get({ plain: true });
- user.accounts = accounts;
- user.payments = payments;
+ let token;
+ switch (type) {
+ case "login":
+ token = data;
+ if (!token || token === "null") return;
+ try {
+ const username = jwt.decode(token).username;
+ if (!sockets[username]) sockets[username] = {};
+ sockets[username][id] = ws;
+ names[id] = username;
- ws.ok = true;
- ws.send(JSON.stringify({ type: "nodes", data: networks }));
- ws.send(JSON.stringify({ type: "rates", data: app.get("rates") }));
- ws.send(JSON.stringify({ type: "login", data: user }));
- l.info("socket open", username, id);
- } catch (e) {
- ws.close();
- l.error(
- "problem opening socket",
- token,
- e.message,
- id,
- req.connection.remoteAddress
- );
+ let user = await getUser(username);
+ let payments = await user.getPayments({
+ where: {
+ account_id: user.account_id
+ },
+ order: [["id", "DESC"]],
+ limit: 12,
+ include: {
+ model: db.Account,
+ as: "account"
+ }
+ });
+ let accounts = await user.getAccounts();
+ user = user.get({ plain: true });
+ user.accounts = accounts;
+ user.payments = payments;
+
+ ws.user = user;
+ ws.send(JSON.stringify({ type: "login", data: user }));
+ ws.send(JSON.stringify({ type: "version", data: clientVersion }));
+ l.info("socket open", username, id);
+ } catch (e) {
+ ws.close();
+ l.error(
+ "problem opening socket",
+ token,
+ e.message,
+ id,
+ req.connection.remoteAddress
+ );
+ }
+ break;
+ case "token":
+ token = data;
+ if (!ws.user) return;
+ ws.token = token;
+ break;
+ default:
+ l.warn("received socket message of unknown type", type, data);
}
});
diff --git a/package.json b/package.json
index 9a582d46..d982b218 100644
--- a/package.json
+++ b/package.json
@@ -40,6 +40,7 @@
"sequelize": "^5.21.3",
"text-to-image": "^2.3.0",
"uuid": "^3.3.2",
+ "web-push": "^3.4.4",
"ws": "^7.1.2",
"zeromq": "^5.1.0"
},
diff --git a/routes/bitcoin/receive.js b/routes/bitcoin/receive.js
index 6928dd5c..0593584e 100644
--- a/routes/bitcoin/receive.js
+++ b/routes/bitcoin/receive.js
@@ -106,7 +106,8 @@ zmqRawTx.on("message", async (topic, message, sequence) => {
emit(user.username, "account", account);
emit(user.username, "payment", payment);
- l.info("bitcoin detected", user.username, o.value);
+ l.info("bitcoin detected", user.username, value);
+ notify(user, `${value} SAT payment detected`);
}
})
);
@@ -149,9 +150,11 @@ setInterval(async () => {
const { user } = p;
if (p) {
+ let total = p.amount + p.tip;
+
p.confirmed = 1;
- p.account.balance += p.amount + p.tip;
- p.account.pending -= Math.min(p.account.pending, p.amount + p.tip);
+ p.account.balance += total;
+ p.account.pending -= Math.min(p.account.pending, total);
await p.account.save();
await p.save();
@@ -159,6 +162,7 @@ setInterval(async () => {
emit(user.username, "account", p.account);
emit(user.username, "payment", p);
l.info("bitcoin confirmed", user.username, p.amount, p.tip);
+ notify(user, `${total} SAT payment confirmed`);
} else {
l.warn("couldn't find payment", hash);
}
diff --git a/routes/balances.js b/routes/info.js
index ba834306..81cb2095 100644
--- a/routes/balances.js
+++ b/routes/info.js
@@ -1,6 +1,14 @@
const sequelize = require("sequelize");
const { Op } = sequelize;
+const fs = require("fs");
+try {
+ clientVersion = fs.readFileSync('config/clientversion').toString();
+ l.info("client version", clientVersion);
+} catch(e) {
+ l.warn("no client version found");
+}
+
app.get("/info", async (req, res) => {
const accounts = await db.Account.findAll({
attributes: [
@@ -49,6 +57,9 @@ app.get("/info", async (req, res) => {
liquid,
lnchannel,
lnwallet,
+ rates: app.get("rates"),
+ nodes: networks,
+ clientVersion,
};
info.total =
diff --git a/routes/lightning/receive.js b/routes/lightning/receive.js
index 6a9c95c8..292f19e2 100644
--- a/routes/lightning/receive.js
+++ b/routes/lightning/receive.js
@@ -48,8 +48,9 @@ const handlePayment = async msg => {
},
{ transaction });
- invoice.received += amount + tip;
- account.balance += amount + tip;
+ let total = amount + tip;
+ invoice.received += total;
+ account.balance += total;
await account.save({ transaction });
await invoice.save({ transaction });
@@ -60,6 +61,7 @@ const handlePayment = async msg => {
payment.account = account.get({ plain: true });
emit(user.username, "payment", payment);
emit(user.username, "account", payment.account);
+ notify(user, `Received ${total} SAT`);
l.info(
"lightning payment received",
diff --git a/routes/liquid/receive.js b/routes/liquid/receive.js
index b3f82a2d..9a97aa47 100644
--- a/routes/liquid/receive.js
+++ b/routes/liquid/receive.js
@@ -121,6 +121,7 @@ zmqRawTx.on("message", async (topic, message, sequence) => {
emit(user.username, "payment", payment);
l.info("liquid detected", user.username, asset, value);
+ notify(user, `${value} SAT payment detected`);
}
})
);
@@ -231,9 +232,10 @@ setInterval(async () => {
const { user } = p;
if (p) {
+ let total = p.amount + p.tip;
p.confirmed = 1;
- p.account.balance += p.amount + p.tip;
- p.account.pending -= Math.min(p.account.pending, p.amount + p.tip);
+ p.account.balance += total;
+ p.account.pending -= Math.min(p.account.pending, total);
await p.account.save();
await p.save();
@@ -247,6 +249,8 @@ setInterval(async () => {
p.amount,
p.tip
);
+
+ notify(user, `${total} SAT payment confirmed`);
} else {
l.warn("couldn't find payment", hash);
}
diff --git a/routes/send.js b/routes/send.js
index 837a9e45..8b4137d8 100644
--- a/routes/send.js
+++ b/routes/send.js
@@ -122,7 +122,8 @@ module.exports = async (req, res) => {
emit(user.username, "account", account);
emit(user.username, "payment", payment);
- l.info("received internal", user.username, payment.amount);
+ l.info("received internal", user.username, amount);
+ notify(user, `Received ${amount} SAT`);
});
} catch (e) {
l.error(
diff --git a/routes/users.js b/routes/users.js
index fc2f2975..9de84b83 100644
--- a/routes/users.js
+++ b/routes/users.js
@@ -101,7 +101,10 @@ app.post("/register", async (req, res) => {
let ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress;
- if (!challenge[ip] || user.response.toLowerCase() !== challenge[ip].toLowerCase()) {
+ if (
+ !challenge[ip] ||
+ user.response.toLowerCase() !== challenge[ip].toLowerCase()
+ ) {
l.info("failed challenge", ip, user.response, challenge[ip]);
return res.status(500).send("Failed challenge");
}
@@ -203,7 +206,8 @@ app.post("/user", auth, async (req, res) => {
twofa,
pin,
password,
- passconfirm
+ passconfirm,
+ tokens
} = req.body;
let exists = await db.User.findOne({
@@ -231,6 +235,7 @@ app.post("/user", auth, async (req, res) => {
user.currency = currency;
user.currencies = currencies;
+ user.tokens = tokens;
user.twofa = twofa;
user.pin = pin;
@@ -290,6 +295,19 @@ app.post("/login", async (req, res) => {
}
});
+
+app.post("/logout", auth, async (req, res) => {
+ let { subscription } = req.body;
+ l.info("logging out", req.user.username);
+ if (!subscription) return res.end();
+ let i = req.user.subscriptions.findIndex(s => JSON.stringify(s) === subscription);
+ if (i > -1) {
+ req.user.subscriptions.splice(i, 1);
+ }
+ await req.user.save();
+ res.end();
+});
+
app.post("/address", auth, async (req, res) => {
if (!config.bitcoin) return res.status(500).send("Bitcoin not configured");
@@ -363,3 +381,22 @@ app.post("/shiftAccount", auth, async (req, res) => {
});
setInterval(() => (faucet = 2000), DAY);
+
+app.get("/vapidPublicKey", function(req, res) {
+ res.send(config.vapid.publicKey);
+});
+
+app.post("/subscribe", auth, async function(req, res) {
+ let { subscriptions } = req.user;
+ let { subscription } = req.body;
+ if (!subscriptions) subscriptions = [];
+ if (
+ !subscriptions.find(s => JSON.stringify(s) === JSON.stringify(subscription))
+ )
+ subscriptions.push(subscription);
+ req.user.subscriptions = subscriptions;
+ l.info("subscribing", req.user.username);
+ await req.user.save();
+ res.sendStatus(201);
+});
+
diff --git a/yarn.lock b/yarn.lock
index 9330d519..34ad1fce 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -278,6 +278,13 @@ agent-base@4, agent-base@^4.2.0, agent-base@^4.3.0:
dependencies:
es6-promisify "^5.0.0"
+agent-base@6:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.0.tgz#5d0101f19bbfaed39980b22ae866de153b93f09a"
+ integrity sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==
+ dependencies:
+ debug "4"
+
agent-base@~4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9"
@@ -420,6 +427,16 @@ ascli@~1:
colour "~0.7.1"
optjs "~3.2.2"
+asn1.js@^5.3.0:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.3.0.tgz#439099fe9174e09cff5a54a9dda70260517e8689"
+ integrity sha512-WHnQJFcOrIWT1RLOkFFBQkFVvyt9BPOOrH+Dp152Zk4R993rSzXUGPmkybIcUFhHE2d/iHH+nCaOWVCDbO8fgA==
+ dependencies:
+ bn.js "^4.0.0"
+ inherits "^2.0.1"
+ minimalistic-assert "^1.0.0"
+ safer-buffer "^2.1.0"
+
asn1@~0.2.3:
version "0.2.4"
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
@@ -656,7 +673,7 @@ bluebird@^3.5.0:
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
-bn.js@^4.11.8, bn.js@^4.4.0:
+bn.js@^4.0.0, bn.js@^4.11.8, bn.js@^4.4.0:
version "4.11.9"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828"
integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==
@@ -2117,6 +2134,13 @@ http-signature@~1.2.0:
jsprim "^1.2.2"
sshpk "^1.7.0"
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/http_ece/-/http_ece-1.1.0.tgz#74780c6eb32d8ddfe9e36a83abcd81fe0cd4fb75"
+ integrity sha512-bptAfCDdPJxOs5zYSe7Y3lpr772s1G346R4Td5LgRUeCwIGpCGDUTJxRrhTNcAXbx37spge0kWEIH7QAYWNTlA==
+ dependencies:
+ urlsafe-base64 "~1.0.0"
+
https-proxy-agent@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz#b8c286433e87602311b01c8ea34413d856a4af81"
@@ -2125,6 +2149,14 @@ https-proxy-agent@^3.0.0:
agent-base "^4.3.0"
debug "^3.1.0"
+https-proxy-agent@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
+ integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==
+ dependencies:
+ agent-base "6"
+ debug "4"
+
[email protected], iconv-lite@^0.4.4:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@@ -2444,6 +2476,15 @@ jwa@^1.4.1:
ecdsa-sig-formatter "1.0.11"
safe-buffer "^5.0.1"
+jwa@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.0.tgz#a7e9c3f29dae94027ebcaf49975c9345593410fc"
+ integrity sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==
+ dependencies:
+ buffer-equal-constant-time "1.0.1"
+ ecdsa-sig-formatter "1.0.11"
+ safe-buffer "^5.0.1"
+
jws@^3.2.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
@@ -2452,6 +2493,14 @@ jws@^3.2.2:
jwa "^1.4.1"
safe-buffer "^5.0.1"
+jws@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.0.tgz#2d4e8cf6a318ffaa12615e9dec7e86e6c97310f4"
+ integrity sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==
+ dependencies:
+ jwa "^2.0.0"
+ safe-buffer "^5.0.1"
+
keyv@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9"
@@ -4436,6 +4485,11 @@ url-parse-lax@^3.0.0:
dependencies:
prepend-http "^2.0.0"
+urlsafe-base64@^1.0.0, urlsafe-base64@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/urlsafe-base64/-/urlsafe-base64-1.0.0.tgz#23f89069a6c62f46cf3a1d3b00169cefb90be0c6"
+ integrity sha1-I/iQaabGL0bPOh07ABac77kL4MY=
+
util-deprecate@^1.0.1, util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
@@ -4484,6 +4538,18 @@ [email protected]:
dependencies:
async "1.5"
+web-push@^3.4.4:
+ version "3.4.4"
+ resolved "https://registry.yarnpkg.com/web-push/-/web-push-3.4.4.tgz#b11523ada0f4b8c2481f65d1d059acd45ba27ca0"
+ integrity sha512-tB0F+ccobsfw5jTWBinWJKyd/YdCdRbKj+CFSnsJeEgFYysOULvWFYyeCxn9KuQvG/3UF1t3cTAcJzBec5LCWA==
+ dependencies:
+ asn1.js "^5.3.0"
+ http_ece "1.1.0"
+ https-proxy-agent "^5.0.0"
+ jws "^4.0.0"
+ minimist "^1.2.5"
+ urlsafe-base64 "^1.0.0"
+
which-module@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"