brought back firebase analytics and authentication

This commit is contained in:
lukew3 2021-05-31 23:47:58 -04:00
parent b44ac814cf
commit fd30b5aa36
34 changed files with 6247 additions and 2085 deletions

5
.firebaserc_example Normal file
View file

@ -0,0 +1,5 @@
{
"projects": {
"default": "your-firebase-project-id"
}
}

View file

@ -1,16 +0,0 @@
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const analyticsSchema = new Schema(
{
event: { type: String, required: true },
data: { type: Schema.Types.Mixed },
},
{
timestamps: true,
}
);
const Analytics = mongoose.model("Analytics", analyticsSchema);
module.exports = { Analytics };

View file

@ -16,6 +16,7 @@ const userSchema = new Schema(
zen: { type: Schema.Types.Mixed, default: {} },
},
name: { type: String, required: true },
uid: { type: String, required: true },
presets: [{ type: presetSchema, default: {} }],
tags: [{ type: tagSchema, default: {} }],
favouriteThemes: [],
@ -40,9 +41,7 @@ const userSchema = new Schema(
started: { type: Number, default: 0 }, //number of started tests
completed: { type: Number, default: 0 },
},
email: { type: String, required: true },
password: { type: String, required: true },
refreshTokens: [{ type: String, required: true }],
email: { type: String },
config: { type: configSchema, default: {} },
bananas: {
t60bananas: { type: Number, default: 0 },

View file

@ -1,19 +1,15 @@
# Todo
- Get google login working
- Account data should be updated when new result is added/test completed
- Fix localhost, production, development server detection
- Should be a setting in the .env
- Maybe it could be set through package.json
- When a specific script is run, a certain mode will be activated
- Tags and leaderboard are still buggy
- Creating the first tag shows error "Unknown error, cannot read property \_id of undefined"
- Check for tag pb doesn't always work
- Leaderboard doesn't show the time until the daily reset
- User's Leaderboard history is not edited, and therefore distance moved on leaderboard does not work properly
- Account data should be updated when new result is added/test completed
- Graph bugs out when new result is added but page is not refreshed
- Graph loops back from earliest point to the new points
- Results list isn't updated either
- Save config doesn't actually return data?
### leaderboard
@ -25,7 +21,6 @@
## After beta is ready
- Get somebody else to check over security due to my lack of expertise
- Work on transfering data from firebase to mongo
- Make sure that development can be done on mac and windows computers as well
- directories in server.js might cause issues
@ -33,19 +28,6 @@
- Could reverse processing of results, but that would add more complexity to code
- Figure out why if (page == "account") pageTransition = false; gets rid of endless account loading bug when accessing via url
### Analytics / Admin panel
- Create admin panel or public stats page to make use of analytics data
- What data needs to be in the analytics table
- New Account, sessions, number of tests started, which tests people take and how fast they are taking them
- Things like theme, popular languages, etc can be derived from user models
- Wouldn't be able to see change over time if you went with this method
- Could check and save once a day
- Could use google analytics for easy data analysis
- Result is duplicated in analytics
- Does entire result need to be stored in analytics
- Should result be stored in seperate collection and then referenced in user doc and analytics?
## User transfer
- Create a script to pull all data from monkeytype and move it to the new mongo server

View file

@ -1,39 +1,29 @@
require("dotenv").config();
const express = require("express");
const bodyParser = require("body-parser");
const mongoose = require("mongoose");
const jwt = require("jsonwebtoken");
const nodemailer = require("nodemailer");
const bcrypt = require("bcrypt");
const saltRounds = 10;
const cors = require("cors");
const admin = require("firebase-admin");
const { User } = require("./models/user");
const { Analytics } = require("./models/analytics");
const { Leaderboard } = require("./models/leaderboard");
// Firebase admin setup
//currently uses account key in functions to prevent repetition
const serviceAccount = require("../functions/serviceAccountKey.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
});
// MIDDLEWARE & SETUP
const app = express();
app.use(cors());
const port = process.env.PORT || "5000";
const port = process.env.PORT || "5005";
mongoose.connect("mongodb://localhost:27017/monkeytype", {
useNewUrlParser: true,
useUnifiedTopology: true,
});
let transporter = nodemailer.createTransport({
service: "gmail",
auth: {
//should use OAuth in production
//type: 'OAuth2',
user: process.env.MAIL_ADDRESS,
pass: process.env.MAIL_PASSWORD,
//clientId: process.env.OAUTH_CLIENTID,
//clientSecret: process.env.OAUTH_CLIENT_SECRET,
//refreshToken: process.env.OAUTH_REFRESH_TOKEN
},
});
const mtRootDir = __dirname.substring(0, __dirname.length - 8); //will this work for windows and mac computers?
app.use(express.static(mtRootDir + "/dist"));
app.use(bodyParser.json());
@ -78,16 +68,18 @@ Leaderboard.findOne((err, lb) => {
clearDailyLeaderboards();
});
function authenticateToken(req, res, next) {
async function authenticateToken(req, res, next) {
const authHeader = req.headers["authorization"];
const token = authHeader && authHeader.split(" ")[1];
if (token == null) return res.sendStatus(401);
jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, identity) => {
if (err) return res.sendStatus(403);
req.name = identity.name;
const token = await admin
.auth()
.verifyIdToken(req.headers.authorization.split(" ")[1]);
if (token == null) {
return res.sendStatus(401);
} else {
req.name = token.name;
req.uid = token.user_id;
next();
});
}
}
// NON-ROUTE FUNCTIONS
@ -174,7 +166,7 @@ async function checkIfPB(obj, userdata) {
throw new Error("pb is undefined");
}
} catch (e) {
User.findOne({ name: userdata.name }, (err, user) => {
User.findOne({ uid: userdata.uid }, (err, user) => {
user.personalBests = {
[obj.mode]: {
[obj.mode2]: [
@ -260,7 +252,7 @@ async function checkIfPB(obj, userdata) {
}
if (toUpdate) {
User.findOne({ name: userdata.name }, (err, user) => {
User.findOne({ uid: userdata.uid }, (err, user) => {
user.personalBests = pbs;
user.save();
});
@ -282,7 +274,7 @@ async function checkIfTagPB(obj, userdata) {
let restags = obj.tags; //result tags
try {
let snap;
await User.findOne({ name: userdata.name }, (err, user) => {
await User.findOne({ uid: userdata.uid }, (err, user) => {
snap = user.tags;
});
snap.forEach((doc) => {
@ -312,7 +304,7 @@ async function checkIfTagPB(obj, userdata) {
} catch (e) {
console.log("PBs undefined");
//undefined personal best = new personal best
await User.findOne({ name: userdata.name }, (err, user) => {
await User.findOne({ uid: userdata.uid }, (err, user) => {
//it might be more convenient if tags was an object with ids as the keys
for (let j = 0; j < user.tags.length; j++) {
console.log(user.tags[j]);
@ -407,7 +399,7 @@ async function checkIfTagPB(obj, userdata) {
if (toUpdate) {
console.log("Adding new pb at end");
await User.findOne({ name: userdata.name }, (err, user) => {
await User.findOne({ uid: userdata.uid }, (err, user) => {
//it might be more convenient if tags was an object with ids as the keys
for (let j = 0; j < user.tags.length; j++) {
console.log(user.tags[j]);
@ -426,7 +418,7 @@ async function checkIfTagPB(obj, userdata) {
return ret;
}
async function stripAndSave(username, obj) {
async function stripAndSave(uid, obj) {
if (obj.bailedOut === false) delete obj.bailedOut;
if (obj.blindMode === false) delete obj.blindMode;
if (obj.difficulty === "normal") delete obj.difficulty;
@ -438,13 +430,13 @@ async function stripAndSave(username, obj) {
if (obj.numbers === false) delete obj.numbers;
if (obj.punctuation === false) delete obj.punctuation;
await User.findOne({ name: username }, (err, user) => {
await User.findOne({ uid: uid }, (err, user) => {
user.results.push(obj);
user.save();
});
}
function incrementT60Bananas(username, result, userData) {
function incrementT60Bananas(uid, result, userData) {
try {
let best60;
try {
@ -464,7 +456,7 @@ function incrementT60Bananas(username, result, userData) {
} else {
//increment
// console.log("checking");
User.findOne({ name: username }, (err, user) => {
User.findOne({ uid: uid }, (err, user) => {
if (user.bananas === undefined) {
user.bananas.t60bananas = 1;
} else {
@ -524,7 +516,7 @@ async function incrementGlobalTypingStats(userData, resultObj) {
// timeTyping: roundTo2(newTime),
// });
incrementPublicTypingStats(resultObj.restartCount + 1, 1, tt);
User.findOne({ name: userData.name }, (err, user) => {
User.findOne({ uid: userData.uid }, (err, user) => {
user.globalStats = {
started: newStarted,
completed: newCompleted,
@ -538,8 +530,6 @@ async function incrementGlobalTypingStats(userData, resultObj) {
}
async function incrementPublicTypingStats(started, completed, time) {
//maybe this should be added to analytics
//analytics should be able to track usage over time and show a graph
/*
try {
time = roundTo2(time);
@ -562,8 +552,54 @@ function isTagPresetNameValid(name) {
return /^[0-9a-zA-Z_.-]+$/.test(name);
}
function isUsernameValid(name) {
if (name === null || name === undefined || name === "") return false;
if (/miodec/.test(name.toLowerCase())) return false;
if (/bitly/.test(name.toLowerCase())) return false;
if (name.length > 14) return false;
if (/^\..*/.test(name.toLowerCase())) return false;
return /^[0-9a-zA-Z_.-]+$/.test(name);
}
// API
app.get("/api/nameCheck/:name", (req, res) => {
if (!isUsernameValid(req.params.name)) {
res.status(200).send({
resultCode: -2,
message: "Username is not valid",
});
return;
}
User.findOne({ name: req.params.name }, (err, user) => {
if (user) {
res.status(200).send({
resultCode: -1,
message: "Username is taken",
});
return;
} else {
res.status(200).send({
resultCode: 1,
message: "Username is available",
});
return;
}
});
});
app.post("/api/signUp", (req, res) => {
const newuser = new User({
name: req.body.name,
email: req.body.email,
uid: req.body.uid,
});
newuser.save();
res.status(200);
res.json({ user: newuser });
return;
});
app.post("/api/updateName", (req, res) => {
//this might be a put/patch request
//update the name of user with given uid
@ -571,177 +607,6 @@ app.post("/api/updateName", (req, res) => {
const name = req.body.name;
});
function sendVerificationEmail(username, email) {
const host = "localhost:5000";
const hash = Math.random().toString(16).substr(2, 12);
const link = `http://${host}/verifyEmail?name=${username}&hash=${hash}`;
User.findOne({ name: username }, (err, user) => {
user.verificationHashes.push(hash);
user.save();
});
const mailOptions = {
from: process.env.MAIL_ADDRESS,
to: email,
subject: "Monkeytype User Verification",
text: `Hello ${username},\nFollow this link to verify your email address:\n${link}\nIf you didnt ask to verify this address, you can ignore this email.\nThanks,\nYour monkeytype team`,
};
transporter.sendMail(mailOptions, function (error, info) {
if (error) {
console.log(error);
} else {
console.log("Email sent: " + info.response);
}
});
}
app.get("/verifyEmail", (req, res) => {
let success = false;
User.findOne({ name: req.query.name }, (err, user) => {
if (user.verificationHashes.includes(req.query.hash)) {
success = true;
user.verificationHashes = [];
user.verified = true;
user.emailVerified = true;
user.save();
}
}).then(() => {
if (success) {
res.send(
"<h3>Email verified successfully</h3><p>Go back to <a href='https://monkeytype.com'>monkeytype</a></p>"
);
} else {
res.send(
"<h3>Email verification failed</h3><p>Go back to <a href='https://monkeytype.com'>monkeytype</a></p>"
);
}
});
});
app.post("/api/sendEmailVerification", authenticateToken, (req, res) => {
User.findOne({ name: req.name }, (err, user) => {
sendVerificationEmail(req.name, user.email);
});
res.sendStatus(200);
});
app.post("/api/signIn", (req, res) => {
/* Takes email and password */
//Login and send tokens
User.findOne({ email: req.body.email }, (err, user) => {
if (err) res.status(500).send({ error: err });
if (user === null) {
res.status(500).send({ error: "No user found with that email" });
return;
}
bcrypt.compare(req.body.password, user.password, (err, result) => {
if (err)
res.status(500).send({ error: "Error during password validation" });
if (result) {
//if password matches hash
const accessToken = jwt.sign(
{ name: user.name },
process.env.ACCESS_TOKEN_SECRET
);
const refreshToken = jwt.sign(
{ name: user.name },
process.env.REFRESH_TOKEN_SECRET
);
user.refreshTokens.push(refreshToken);
user.save();
const retUser = {
uid: user._id,
name: user.name,
email: user.email,
emailVerified: user.emailVerified,
metadata: { creationTime: user.createdAt },
};
res.json({
accessToken: accessToken,
refreshToken: refreshToken,
user: retUser,
});
} else {
//if password doesn't match hash
res.status(500).send({ error: "Password invalid" });
}
});
});
});
app.post("/api/signUp", (req, res) => {
/* Takes name, email, password */
//check if name has been taken
User.exists({ name: req.body.name }).then((exists) => {
//should also check if email is used
if (exists) {
//user with that name already exists
res.status(500).send({ error: "Username taken" });
}
bcrypt.hash(req.body.password, saltRounds, function (err, hash) {
if (err) console.log(err);
const newuser = new User({
name: req.body.name,
email: req.body.email,
emailVerified: false,
password: hash,
});
newuser
.save()
.then((user) => {
//send email verification
sendVerificationEmail(user.name, user.email);
//add account created event to analytics
//return user data and access token
const accessToken = jwt.sign(
{ name: req.body.name },
process.env.ACCESS_TOKEN_SECRET
);
const refreshToken = jwt.sign(
{ name: user.name },
process.env.REFRESH_TOKEN_SECRET
);
user.refreshTokens.push(refreshToken);
user.save();
const retUser = {
uid: user._id,
name: user.name,
email: user.email,
emailVerified: user.emailVerified,
metadata: { creationTime: user.createdAt },
};
res.json({
accessToken: accessToken,
refreshToken: refreshToken,
user: retUser,
});
})
.catch((e) => {
console.log(e);
res.status(500).send({ error: "Error when adding user" });
});
});
});
});
app.post("/api/refreshToken", (req, res) => {
const authHeader = req.headers["authorization"];
const token = authHeader && authHeader.split(" ")[1];
if (token == null) return res.sendStatus(401);
jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, identity) => {
if (err) return res.sendStatus(403);
User.findOne({ name: identity.name }, (err, user) => {
if (!user.refreshTokens.includes(token)) return res.sendStatus(403);
const accessToken = jwt.sign(
{ name: identity.name },
process.env.ACCESS_TOKEN_SECRET
);
res.json({ accessToken: accessToken });
});
});
});
app.post("/api/passwordReset", (req, res) => {
const email = req.body.email;
//send email to the passed email requesting password reset
@ -750,13 +615,15 @@ app.post("/api/passwordReset", (req, res) => {
app.get("/api/fetchSnapshot", authenticateToken, (req, res) => {
/* Takes token and returns snap */
User.findOne({ name: req.name }, (err, user) => {
console.log("UID: " + req.uid);
User.findOne({ uid: req.uid }, (err, user) => {
if (err) res.status(500).send({ error: err });
if (!user) res.status(200).send({ message: "No user found" }); //client doesn't do anything with this
//populate snap object with data from user document
let snap = user;
delete snap.password;
//return user data
res.send({ snap: snap });
return;
});
});
@ -769,10 +636,7 @@ function stdDev(array) {
}
app.post("/api/testCompleted", authenticateToken, (req, res) => {
//return createdId
//return user data
//this is actually REALLY hard
User.findOne({ name: req.name }, (err, user) => {
User.findOne({ uid: req.uid }, (err, user) => {
if (err) res.status(500).send({ error: err });
request = req.body;
if (request === undefined) {
@ -780,7 +644,7 @@ app.post("/api/testCompleted", authenticateToken, (req, res) => {
return;
}
try {
if (req.name === undefined || request.obj === undefined) {
if (req.uid === undefined || request.obj === undefined) {
console.error(`error saving result for - missing input`);
res.status(200).send({ data: { resultCode: -999 } });
return;
@ -790,7 +654,7 @@ app.post("/api/testCompleted", authenticateToken, (req, res) => {
if (obj.incompleteTestSeconds > 500)
console.log(
`FUCK, HIGH INCOMPLETE TEST SECONDS ${req.name}: ${JSON.stringify(
`FUCK, HIGH INCOMPLETE TEST SECONDS ${req.uid}: ${JSON.stringify(
obj
)}`
);
@ -817,7 +681,7 @@ app.post("/api/testCompleted", authenticateToken, (req, res) => {
if (errCount > 0) {
console.error(
`error saving result for ${
req.name
req.uid
} error count ${errCount} - bad input - ${JSON.stringify(
request.obj
)}`
@ -896,7 +760,7 @@ app.post("/api/testCompleted", authenticateToken, (req, res) => {
};
} catch (e) {
console.error(
`cant verify key spacing or duration for user ${req.name}! - ${e} - ${obj.keySpacing} ${obj.keyDuration}`
`cant verify key spacing or duration for user ${req.uid}! - ${e} - ${obj.keySpacing} ${obj.keyDuration}`
);
}
@ -911,7 +775,7 @@ app.post("/api/testCompleted", authenticateToken, (req, res) => {
// emailVerified = await admin
// .auth()
// .getUser(req.name)
// .getUser(req.uid)
// .then((user) => {
// return user.emailVerified;
// });
@ -956,7 +820,7 @@ app.post("/api/testCompleted", authenticateToken, (req, res) => {
console.error(
`very close to bot detected threshold by user (${obj.wpm} ${
obj.rawWpm
} ${obj.acc}) ${req.name} ${name} - spacing ${JSON.stringify(
} ${obj.acc}) ${req.uid} ${name} - spacing ${JSON.stringify(
keySpacing
)} duration ${JSON.stringify(keyDuration)}`
);
@ -979,7 +843,7 @@ app.post("/api/testCompleted", authenticateToken, (req, res) => {
} catch (e) {}
// return db
// .collection(`users/${req.name}/results`)
// .collection(`users/${req.uid}/results`)
// .add(obj)
// .then((e) => {
@ -1012,7 +876,7 @@ app.post("/api/testCompleted", authenticateToken, (req, res) => {
// console.log(values);
if (obj.mode === "time" && String(obj.mode2) === "60") {
incrementT60Bananas(req.name, obj, userdata);
incrementT60Bananas(req.uid, obj, userdata);
}
await incrementGlobalTypingStats(userdata, obj);
@ -1032,9 +896,7 @@ app.post("/api/testCompleted", authenticateToken, (req, res) => {
logobj.keySpacing = "removed";
logobj.keyDuration = "removed";
console.log(
`saved result for ${req.name} (new PB) - ${JSON.stringify(
logobj
)}`
`saved result for ${req.uid} (new PB) - ${JSON.stringify(logobj)}`
);
/*
User.findOne({ name: userdata.name }, (err, user2) => {
@ -1053,7 +915,7 @@ app.post("/api/testCompleted", authenticateToken, (req, res) => {
) {
if (verified !== false) {
console.log(
`sending command to the bot to update the role for user ${req.name} with wpm ${obj.wpm}`
`sending command to the bot to update the role for user ${req.uid} with wpm ${obj.wpm}`
);
updateDiscordRole(userdata.discordId, Math.round(obj.wpm));
}
@ -1065,17 +927,17 @@ app.post("/api/testCompleted", authenticateToken, (req, res) => {
logobj.keyDuration = "removed";
request.obj.isPb = false;
console.log(
`saved result for ${req.name} - ${JSON.stringify(logobj)}`
`saved result for ${req.uid} - ${JSON.stringify(logobj)}`
);
returnobj.resultCode = 1;
}
stripAndSave(req.name, request.obj);
stripAndSave(req.uid, request.obj);
res.status(200).send({ data: returnobj });
return;
})
.catch((e) => {
console.error(
`error saving result when checking for PB / checking leaderboards for ${req.name} - ${e.message}`
`error saving result when checking for PB / checking leaderboards for ${req.uid} - ${e.message}`
);
res
.status(200)
@ -1084,7 +946,7 @@ app.post("/api/testCompleted", authenticateToken, (req, res) => {
});
} catch (e) {
console.error(
`error saving result for ${req.name} - ${JSON.stringify(
`error saving result for ${req.uid} - ${JSON.stringify(
request.obj
)} - ${e}`
);
@ -1095,7 +957,7 @@ app.post("/api/testCompleted", authenticateToken, (req, res) => {
});
app.get("/api/userResults", authenticateToken, (req, res) => {
User.findOne({ name: req.name }, (err, user) => {
User.findOne({ uid: req.uid }, (err, user) => {
if (err) res.status(500).send({ error: err });
});
//return list of results
@ -1110,8 +972,8 @@ function isConfigKeyValid(name) {
app.post("/api/saveConfig", authenticateToken, (req, res) => {
try {
if (req.name === undefined || req.body.obj === undefined) {
console.error(`error saving config for ${req.name} - missing input`);
if (req.uid === undefined || req.body.obj === undefined) {
console.error(`error saving config for ${req.uid} - missing input`);
return {
resultCode: -1,
message: "Missing input",
@ -1150,7 +1012,7 @@ app.post("/api/saveConfig", authenticateToken, (req, res) => {
});
if (err) {
console.error(
`error saving config for ${req.name} - bad input - ${JSON.stringify(
`error saving config for ${req.uid} - bad input - ${JSON.stringify(
request.obj
)}`
);
@ -1160,7 +1022,7 @@ app.post("/api/saveConfig", authenticateToken, (req, res) => {
};
}
User.findOne({ name: req.name }, (err, user) => {
User.findOne({ uid: req.uid }, (err, user) => {
if (err) res.status(500).send({ error: err });
user.config = obj;
//what does {merge: true} do in firebase
@ -1174,7 +1036,7 @@ app.post("/api/saveConfig", authenticateToken, (req, res) => {
})
.catch((e) => {
console.error(
`error saving config to DB for ${req.name} - ${e.message}`
`error saving config to DB for ${req.uid} - ${e.message}`
);
return {
resultCode: -1,
@ -1182,7 +1044,7 @@ app.post("/api/saveConfig", authenticateToken, (req, res) => {
};
});
} catch (e) {
console.error(`error saving config for ${req.name} - ${e}`);
console.error(`error saving config for ${req.uid} - ${e}`);
return {
resultCode: -999,
message: e,
@ -1194,8 +1056,8 @@ app.post("/api/addPreset", authenticateToken, (req, res) => {
try {
if (!isTagPresetNameValid(req.body.obj.name)) {
return { resultCode: -1 };
} else if (req.name === undefined || req.body.obj === undefined) {
console.error(`error saving config for ${req.name} - missing input`);
} else if (req.uid === undefined || req.body.obj === undefined) {
console.error(`error saving config for ${req.uid} - missing input`);
res.json({
resultCode: -1,
message: "Missing input",
@ -1233,7 +1095,7 @@ app.post("/api/addPreset", authenticateToken, (req, res) => {
});
if (err) {
console.error(
`error adding preset for ${req.name} - bad input - ${JSON.stringify(
`error adding preset for ${req.uid} - bad input - ${JSON.stringify(
req.body.obj
)}`
);
@ -1243,7 +1105,7 @@ app.post("/api/addPreset", authenticateToken, (req, res) => {
});
}
User.findOne({ name: req.name }, (err, user) => {
User.findOne({ uid: req.uid }, (err, user) => {
if (user.presets.length >= 10) {
res.json({
resultCode: -2,
@ -1263,7 +1125,7 @@ app.post("/api/addPreset", authenticateToken, (req, res) => {
})
.catch((e) => {
console.error(
`error adding preset to DB for ${req.name} - ${e.message}`
`error adding preset to DB for ${req.uid} - ${e.message}`
);
res.json({
resultCode: -1,
@ -1272,7 +1134,7 @@ app.post("/api/addPreset", authenticateToken, (req, res) => {
});
}
} catch (e) {
console.error(`error adding preset for ${req.name} - ${e}`);
console.error(`error adding preset for ${req.uid} - ${e}`);
res.json({
resultCode: -999,
message: e,
@ -1285,7 +1147,7 @@ app.post("/api/editPreset", authenticateToken, (req, res) => {
if (!isTagPresetNameValid(req.body.presetName)) {
return { resultCode: -1 };
} else {
User.findOne({ name: req.name }, (err, user) => {
User.findOne({ uid: req.uid }, (err, user) => {
for (i = 0; i < user.presets.length; i++) {
if (user.presets[i]._id.toString() == req.body.presetid.toString()) {
user.presets[i] = {
@ -1299,7 +1161,7 @@ app.post("/api/editPreset", authenticateToken, (req, res) => {
})
.then((e) => {
console.log(
`user ${req.name} updated a preset: ${req.body.presetName}`
`user ${req.uid} updated a preset: ${req.body.presetName}`
);
res.send({
resultCode: 1,
@ -1307,20 +1169,20 @@ app.post("/api/editPreset", authenticateToken, (req, res) => {
})
.catch((e) => {
console.error(
`error while updating preset for user ${req.name}: ${e.message}`
`error while updating preset for user ${req.uid}: ${e.message}`
);
res.send({ resultCode: -999, message: e.message });
});
}
} catch (e) {
console.error(`error updating preset for ${req.name} - ${e}`);
console.error(`error updating preset for ${req.uid} - ${e}`);
return { resultCode: -999, message: e.message };
}
});
app.post("/api/removePreset", authenticateToken, (req, res) => {
try {
User.findOne({ name: req.name }, (err, user) => {
User.findOne({ uid: req.uid }, (err, user) => {
for (i = 0; i < user.presets.length; i++) {
if (user.presets[i]._id.toString() == req.body.presetid.toString()) {
user.presets.splice(i, 1);
@ -1330,17 +1192,17 @@ app.post("/api/removePreset", authenticateToken, (req, res) => {
user.save();
})
.then((e) => {
console.log(`user ${req.name} deleted a preset`);
console.log(`user ${req.uid} deleted a preset`);
res.send({ resultCode: 1 });
})
.catch((e) => {
console.error(
`error deleting preset for user ${req.name}: ${e.message}`
`error deleting preset for user ${req.uid}: ${e.message}`
);
res.send({ resultCode: -999 });
});
} catch (e) {
console.error(`error deleting preset for ${req.name} - ${e}`);
console.error(`error deleting preset for ${req.uid} - ${e}`);
res.send({ resultCode: -999 });
}
});
@ -1355,7 +1217,7 @@ function isTagPresetNameValid(name) {
app.post("/api/addTag", authenticateToken, (req, res) => {
try {
if (!isTagPresetNameValid(req.body.tagName)) return { resultCode: -1 };
User.findOne({ name: req.name }, (err, user) => {
User.findOne({ uid: req.uid }, (err, user) => {
if (err) res.status(500).send({ error: err });
if (user.tags.includes(req.body.tagName)) {
return { resultCode: -999, message: "Duplicate tag" };
@ -1365,7 +1227,7 @@ app.post("/api/addTag", authenticateToken, (req, res) => {
user.save();
})
.then((updatedUser) => {
console.log(`user ${req.name} created a tag: ${req.body.tagName}`);
console.log(`user ${req.uid} created a tag: ${req.body.tagName}`);
res.json({
resultCode: 1,
id: updatedUser.tags[updatedUser.tags.length - 1]._id,
@ -1373,12 +1235,12 @@ app.post("/api/addTag", authenticateToken, (req, res) => {
})
.catch((e) => {
console.error(
`error while creating tag for user ${req.name}: ${e.message}`
`error while creating tag for user ${req.uid}: ${e.message}`
);
res.json({ resultCode: -999, message: e.message });
});
} catch (e) {
console.error(`error adding tag for ${req.name} - ${e}`);
console.error(`error adding tag for ${req.uid} - ${e}`);
res.json({ resultCode: -999, message: e.message });
}
});
@ -1386,7 +1248,7 @@ app.post("/api/addTag", authenticateToken, (req, res) => {
app.post("/api/editTag", authenticateToken, (req, res) => {
try {
if (!isTagPresetNameValid(req.body.tagName)) return { resultCode: -1 };
User.findOne({ name: req.name }, (err, user) => {
User.findOne({ uid: req.uid }, (err, user) => {
if (err) res.status(500).send({ error: err });
for (var i = 0; i < user.tags.length; i++) {
if (user.tags[i]._id == req.body.tagId) {
@ -1396,24 +1258,24 @@ app.post("/api/editTag", authenticateToken, (req, res) => {
user.save();
})
.then((updatedUser) => {
console.log(`user ${req.name} updated a tag: ${req.name}`);
console.log(`user ${req.uid} updated a tag: ${req.body.tagName}`);
res.json({ resultCode: 1 });
})
.catch((e) => {
console.error(
`error while updating tag for user ${req.name}: ${e.message}`
`error while updating tag for user ${req.uid}: ${e.message}`
);
res.json({ resultCode: -999, message: e.message });
});
} catch (e) {
console.error(`error updating tag for ${req.name} - ${e}`);
console.error(`error updating tag for ${req.uid} - ${e}`);
res.json({ resultCode: -999, message: e.message });
}
});
app.post("/api/removeTag", authenticateToken, (req, res) => {
try {
User.findOne({ name: req.name }, (err, user) => {
User.findOne({ uid: req.uid }, (err, user) => {
if (err) res.status(500).send({ error: err });
for (var i = 0; i < user.tags.length; i++) {
if (user.tags[i]._id == req.body.tagId) {
@ -1423,22 +1285,22 @@ app.post("/api/removeTag", authenticateToken, (req, res) => {
user.save();
})
.then((updatedUser) => {
console.log(`user ${req.name} deleted a tag`);
console.log(`user ${req.uid} deleted a tag`);
res.json({ resultCode: 1 });
})
.catch((e) => {
console.error(`error deleting tag for user ${req.name}: ${e.message}`);
console.error(`error deleting tag for user ${req.uid}: ${e.message}`);
res.json({ resultCode: -999 });
});
} catch (e) {
console.error(`error deleting tag for ${req.name} - ${e}`);
console.error(`error deleting tag for ${req.uid} - ${e}`);
res.json({ resultCode: -999 });
}
});
app.post("/api/resetPersonalBests", authenticateToken, (req, res) => {
try {
User.findOne({ name: req.name }, (err, user) => {
User.findOne({ uid: req.uid }, (err, user) => {
if (err) res.status(500).send({ error: err });
user.personalBests = {};
user.save();
@ -1522,9 +1384,11 @@ app.post("/api/attemptAddToLeaderboards", authenticateToken, (req, res) => {
lb.board.length < lb.size ||
result.wpm > lb.board.slice(-1)[0].wpm
) {
lb, (lbPosData = addToLeaderboard(lb, result, req.name));
retData[lb.type] = lbPosData;
lb.save();
User.findOne({ uid: req.uid }, (err, user) => {
lb, (lbPosData = addToLeaderboard(lb, result, user.name)); //should uid be added instead of name
retData[lb.type] = lbPosData;
lb.save();
});
}
});
}
@ -1549,81 +1413,6 @@ app.get("/api/getLeaderboard/:type/:mode/:mode2", (req, res) => {
);
});
// ANALYTICS API
function newAnalyticsEvent(event, data) {
let newEvent = {
event: event,
};
if (data) newEvent.data = data;
const newEventObj = new Analytics(newEvent);
newEventObj.save();
}
app.post("/api/analytics/usedCommandLine", (req, res) => {
//save command used from command line to analytics
newAnalyticsEvent("usedCommandLine", { command: req.body.command });
res.sendStatus(200);
});
app.post("/api/analytics/changedLanguage", (req, res) => {
//save what a user changed their language to
newAnalyticsEvent("changedLanguage", { language: req.body.language });
res.sendStatus(200);
});
app.post("/api/analytics/changedTheme", (req, res) => {
//save what a user changed their theme to
newAnalyticsEvent("changedTheme", { theme: req.body.theme });
res.sendStatus(200);
});
app.post("/api/analytics/testStarted", (req, res) => {
//log that a test was started
newAnalyticsEvent("testStarted");
res.sendStatus(200);
});
app.post("/api/analytics/testStartedNoLogin", (req, res) => {
//log that a test was started without login
newAnalyticsEvent("testStartedNoLogin");
res.sendStatus(200);
});
app.post("/api/analytics/testCompleted", (req, res) => {
//log that a test was completed
newAnalyticsEvent("testCompleted", {
completedEvent: req.body.completedEvent,
});
res.sendStatus(200);
});
app.post("/api/analytics/testCompletedNoLogin", (req, res) => {
//log that a test was completed and user was not logged in
newAnalyticsEvent("testCompletedNoLogin", {
completedEvent: req.body.completedEvent,
});
res.sendStatus(200);
});
app.post("/api/analytics/testCompletedInvalid", (req, res) => {
//log that a test was completed and is invalid
newAnalyticsEvent("testCompletedInvalid", {
completedEvent: req.body.completedEvent,
});
res.sendStatus(200);
});
// STATIC FILES
app.get("/privacy-policy", (req, res) => {
res.sendFile(mtRootDir + "/dist/privacy-policy.html");
});
app.use((req, res, next) => {
//sends index.html if the route is not found above
res.sendFile(mtRootDir + "/dist/index.html");
});
// LISTENER
app.listen(port, () => {
console.log(`Listening to requests on http://localhost:${port}`);

24
firebase.json Normal file
View file

@ -0,0 +1,24 @@
{
"hosting": {
"public": "dist",
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
"rewrites": [
{
"source": "/privacy-policy",
"destination": "/privacy-policy.html"
},
{
"source": "**",
"destination": "/index.html"
}
],
"cleanUrls": true,
"trailingSlash": false
}
// },
// "functions": {
// "predeploy": [
// "npm --prefix \"$RESOURCE_DIR\" run lint"
// ]
// }
}

File diff suppressed because it is too large Load diff

View file

@ -1,372 +0,0 @@
exports.requestTest = functions.https.onRequest((request, response) => {
response.set("Access-Control-Allow-Origin", origin);
response.set("Access-Control-Allow-Headers", "*");
response.set("Access-Control-Allow-Credentials", "true");
response.status(200).send({ data: "test" });
});
exports.getPatreons = functions.https.onRequest(async (request, response) => {
response.set("Access-Control-Allow-Origin", origin);
response.set("Access-Control-Allow-Headers", "*");
response.set("Access-Control-Allow-Credentials", "true");
if (request.method === "OPTIONS") {
// Send response to OPTIONS requests
response.set("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
response.set("Access-Control-Allow-Headers", "Authorization,Content-Type");
response.set("Access-Control-Max-Age", "3600");
response.status(204).send("");
return;
}
request = request.body.data;
try {
let patreon = await db.collection("patreon").doc("patreons").get();
let data = patreon.data().list;
data = data.sort((a, b) => {
return b.value - a.value;
});
let ret = [];
data.forEach((pdoc) => {
ret.push(pdoc.name);
});
response.status(200).send({ data: ret });
return;
} catch (e) {
response.status(200).send({ e });
return;
}
});
exports.clearTagPb = functions.https.onCall((request, response) => {
//It looks like this button is not used anymore
try {
return db
.collection(`users/${request.uid}/tags`)
.doc(request.tagid)
.update({
pb: 0,
})
.then((e) => {
return {
resultCode: 1,
};
})
.catch((e) => {
console.error(
`error deleting tag pb for user ${request.uid}: ${e.message}`
);
return {
resultCode: -999,
message: e.message,
};
});
} catch (e) {
console.error(`error deleting tag pb for ${request.uid} - ${e}`);
return { resultCode: -999 };
}
});
exports.removeSmallTestsAndQPB = functions.https.onCall(
async (request, response) => {
let uid = request.uid;
try {
let docs = await db
.collection(`users/${uid}/results`)
.where("mode", "==", "time")
.where("mode2", "<", 15)
.get();
docs.forEach(async (doc) => {
db.collection(`users/${uid}/results`).doc(doc.id).delete();
});
let docs2 = await db
.collection(`users/${uid}/results`)
.where("mode", "==", "words")
.where("mode2", "<", 10)
.get();
docs2.forEach(async (doc) => {
db.collection(`users/${uid}/results`).doc(doc.id).delete();
});
let docs3 = await db
.collection(`users/${uid}/results`)
.where("mode", "==", "custom")
.where("testDuration", "<", 10)
.get();
docs3.forEach(async (doc) => {
db.collection(`users/${uid}/results`).doc(doc.id).delete();
});
// console.log(`removing small tests for ${uid}: ${docs.size} time, ${docs2.size} words, ${docs3.size} custom`);
let userdata = await db.collection(`users`).doc(uid).get();
userdata = userdata.data();
try {
pbs = userdata.personalBests;
// console.log(`removing ${Object.keys(pbs.quote).length} quote pb`);
delete pbs.quote;
await db.collection("users").doc(uid).update({ personalBests: pbs });
} catch {}
db.collection("users")
.doc(uid)
.set({ refactored: true }, { merge: true });
console.log("removed small tests for " + uid);
} catch (e) {
console.log(`something went wrong for ${uid}: ${e.message}`);
}
}
);
async function getAllNames() {
// return admin
// .auth()
// .listUsers()
// .then((data) => {
// let names = [];
// data.users.forEach((user) => {
// names.push(user.name);
// });
// return names;
// });
let ret = [];
async function getAll(nextPageToken) {
// List batch of users, 1000 at a time.
let listUsersResult = await admin.auth().listUsers(1000, nextPageToken);
for (let i = 0; i < listUsersResult.users.length; i++) {
ret.push(listUsersResult.users[i].name);
}
if (listUsersResult.pageToken) {
// List next batch of users.
await getAll(listUsersResult.pageToken);
}
}
await getAll();
return ret;
}
async function getAllUsers() {
// return admin
// .auth()
// .listUsers()
// .then((data) => {
// let names = [];
// data.users.forEach((user) => {
// names.push(user.name);
// });
// return names;
// });
let ret = [];
async function getAll(nextPageToken) {
// List batch of users, 1000 at a time.
let listUsersResult = await auth.listUsers(1000, nextPageToken);
for (let i = 0; i < listUsersResult.users.length; i++) {
let loopuser = listUsersResult.users[i];
//if custom claim is undefined check, if its true then ignore
// if (loopuser === undefined || loopuser.customClaims === undefined || loopuser.customClaims['nameChecked'] === undefined) {
ret.push(listUsersResult.users[i]);
// }
// console.log(loopuser.customClaims['asd']);
// let userdata = await db.collection('users').doc(listUsersResult.users[i].uid).get();
// let data = userdata.data();
// if (data === undefined || data.needsToChangeName === undefined) {
// // console.log(data);
// ret.push(listUsersResult.users[i]);
// // console.log('user added');
// } else {
// // console.log('user already added');
// }
}
if (listUsersResult.pageToken) {
// List next batch of users.
await getAll(listUsersResult.pageToken);
}
}
await getAll();
return ret;
}
function isUsernameValid(name) {
if (name === null || name === undefined || name === "") return false;
if (/miodec/.test(name.toLowerCase())) return false;
if (/bitly/.test(name.toLowerCase())) return false;
if (name.length > 14) return false;
if (/^\..*/.test(name.toLowerCase())) return false;
return /^[0-9a-zA-Z_.-]+$/.test(name);
}
async function incrementTestCounter(uid, userData) {
try {
if (userData.completedTests === undefined) {
let results = await db.collection(`users/${uid}/results`).get();
let count = results.docs.length;
db.collection("users")
.doc(uid)
.update({
completedTests: admin.firestore.FieldValue.increment(count),
});
db.collection("public")
.doc("stats")
.update({
completedTests: admin.firestore.FieldValue.increment(count),
});
} else {
db.collection("users")
.doc(uid)
.update({ completedTests: admin.firestore.FieldValue.increment(1) });
db.collection("public")
.doc("stats")
.update({ completedTests: admin.firestore.FieldValue.increment(1) });
}
} catch (e) {
console.error(
`Error while incrementing completed tests for user ${uid}: ${e}`
);
}
}
async function incrementStartedTestCounter(uid, num, userData) {
try {
if (userData.startedTests === undefined) {
let stepSize = 1000;
let results = [];
let query = await db
.collection(`users/${uid}/results`)
.orderBy("timestamp", "desc")
.limit(stepSize)
.get();
let lastDoc;
while (query.docs.length > 0) {
lastDoc = query.docs[query.docs.length - 1];
query.docs.forEach((doc) => {
results.push({ restartCount: doc.data().restartCount });
});
query = await db
.collection(`users/${uid}/results`)
.orderBy("timestamp", "desc")
.limit(stepSize)
.startAfter(lastDoc)
.get();
}
let count = 0;
results.forEach((result) => {
try {
let rc = result.restartCount;
if (rc === undefined) {
rc = 0;
}
count += parseInt(rc);
} catch (e) {}
});
count += results.length;
db.collection("users")
.doc(uid)
.update({
startedTests: admin.firestore.FieldValue.increment(count),
});
db.collection("public")
.doc("stats")
.update({
startedTests: admin.firestore.FieldValue.increment(count),
});
} else {
db.collection("users")
.doc(uid)
.update({ startedTests: admin.firestore.FieldValue.increment(num) });
db.collection("public")
.doc("stats")
.update({ startedTests: admin.firestore.FieldValue.increment(num) });
}
} catch (e) {
console.error(
`Error while incrementing started tests for user ${uid}: ${e}`
);
}
}
exports.scheduledFunctionCrontab = functions.pubsub
.schedule("00 00 * * *")
.timeZone("Africa/Abidjan")
.onRun((context) => {
try {
console.log("moving daily leaderboards to history");
db.collection("leaderboards")
.where("type", "==", "daily")
.get()
.then(async (res) => {
for (let i = 0; i < res.docs.length; i++) {
let doc = res.docs[i];
let lbdata = doc.data();
let winnerUid = lbdata.board[0].uid;
await db
.collection("users")
.doc(winnerUid)
.get()
.then(async (userDoc) => {
let userData = userDoc.data();
let lbwins = userData.dailyLbWins;
let lbname = lbdata.mode + lbdata.mode2;
if (lbwins === undefined) {
//first win ever
lbwins = {
[lbname]: 1,
};
} else {
//object already exists
if (lbwins[lbname] === undefined) {
lbwins[lbname] = 1;
} else {
lbwins[lbname] = lbwins[lbname] + 1;
}
}
await db.collection("users").doc(winnerUid).update({
dailyLbWins: lbwins,
});
});
announceDailyLbResult(lbdata);
t = new Date();
// db.collection("leaderboards_history")
// .doc(
// `${t.getUTCDate()}_${t.getUTCMonth()}_${t.getUTCFullYear()}_${
// lbdata.mode
// }_${lbdata.mode2}`
// )
// .set(lbdata);
db.collection("leaderboards").doc(doc.id).set(
{
board: [],
},
{ merge: true }
);
}
});
return null;
} catch (e) {
console.error(`error while moving daily leaderboards to history - ${e}`);
}
});
async function announceDailyLbResult(lbdata) {
db.collection("bot-commands").add({
command: "announceDailyLbResult",
arguments: [lbdata],
executed: false,
requestTimestamp: Date.now(),
});
}

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
const { task, src, dest, series, watch } = require("gulp");
const browserify = require("browserify");
const axios = require("axios");
const browserify = require("browserify");
const babelify = require("babelify");
const concat = require("gulp-concat");
const del = require("del");
@ -13,7 +13,14 @@ sass.compiler = require("dart-sass");
let eslintConfig = {
parser: "babel-eslint",
globals: ["jQuery", "$", "moment", "html2canvas", "ClipboardItem"],
globals: [
"jQuery",
"$",
"firebase",
"moment",
"html2canvas",
"ClipboardItem",
],
envs: ["es6", "browser", "node"],
rules: {
"constructor-super": "error",

3586
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -6,7 +6,7 @@
"scripts": {
"postinstall": "cd functions && npm install",
"build": "npx gulp build",
"start:dev": "npm run build && concurrently --kill-others \"npx gulp watch\" \"nodemon ./backend/server.js\"",
"start:dev": "npm run build && concurrently --kill-others \"npx gulp watch\" \"nodemon ./backend/server.js\" \"firebase serve\"",
"deploy:live:hosting": "npm run build && firebase deploy -P monkey-type --only hosting",
"deploy:live:functions": "npm run build && firebase deploy -P monkey-type --only functions",
"deploy:live": "npm run build && firebase deploy -P live"
@ -30,7 +30,6 @@
"gulp-eslint": "^6.0.0",
"gulp-sass": "^4.1.0",
"husky": "^4.3.0",
"nodemon": "^2.0.7",
"prettier": "2.1.2",
"pretty-quick": "^3.1.0",
"vinyl-buffer": "^1.0.1",
@ -45,16 +44,15 @@
"dependencies": {
"@babel/runtime": "^7.12.5",
"axios": "^0.21.1",
"bcrypt": "^5.0.1",
"chart.js": "^2.9.4",
"chartjs-plugin-annotation": "^0.5.7",
"chartjs-plugin-trendline": "^0.2.2",
"dotenv": "^9.0.2",
"cors": "^2.8.5",
"express": "^4.17.1",
"firebase-admin": "^9.9.0",
"howler": "^2.2.1",
"jsonwebtoken": "^8.5.1",
"mongoose": "^5.12.8",
"nodemailer": "^6.6.1",
"mongoose": "^5.12.12",
"nodemon": "^2.0.7",
"tinycolor2": "^1.4.2"
}
}

View file

@ -14,42 +14,52 @@ import * as DB from "./db";
import * as TestLogic from "./test-logic";
import * as UI from "./ui";
import axiosInstance from "./axios-instance";
//var gmailProvider = new firebase.auth.GoogleAuthProvider();
var gmailProvider = new firebase.auth.GoogleAuthProvider();
export function signIn() {
$(".pageLogin .preloader").removeClass("hidden");
let email = $(".pageLogin .login input")[0].value;
let password = $(".pageLogin .login input")[1].value;
axiosInstance
.post("/api/signIn", {
email: email,
password: password,
})
.then((response) => {
// UI.changePage("test");
if ($(".pageLogin .login #rememberMe input").prop("checked")) {
// TODO: set user login cookie that persists after session
window.localStorage.setItem("accessToken", response.data.accessToken);
window.localStorage.setItem("refreshToken", response.data.refreshToken);
window.localStorage.setItem("user", JSON.stringify(response.data.user));
} else {
//set user login cookie to persist only as long as the session lives
window.localStorage.setItem("accessToken", response.data.accessToken);
window.localStorage.setItem("refreshToken", response.data.refreshToken);
window.localStorage.setItem("user", JSON.stringify(response.data.user));
}
userStateChanged(response.data.user);
})
.catch((e) => {
console.log("Could not be signed in");
Notifications.add(e.message, -1);
$(".pageLogin .preloader").addClass("hidden");
});
if ($(".pageLogin .login #rememberMe input").prop("checked")) {
//remember me
firebase
.auth()
.setPersistence(firebase.auth.Auth.Persistence.LOCAL)
.then(function () {
return firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then((e) => {
// UI.changePage("test");
})
.catch(function (error) {
Notifications.add(error.message, -1);
$(".pageLogin .preloader").addClass("hidden");
});
});
} else {
//dont remember
firebase
.auth()
.setPersistence(firebase.auth.Auth.Persistence.SESSION)
.then(function () {
return firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then((e) => {
// UI.changePage("test");
})
.catch(function (error) {
Notifications.add(error.message, -1);
$(".pageLogin .preloader").addClass("hidden");
});
});
}
}
export async function signInWithGoogle() {
console.log("Login with google temporarily unavailable");
/*
$(".pageLogin .preloader").removeClass("hidden");
if ($(".pageLogin .login #rememberMe input").prop("checked")) {
@ -81,12 +91,9 @@ export async function signInWithGoogle() {
$(".pageLogin .preloader").addClass("hidden");
});
}
*/
}
export function linkWithGoogle() {
console.log("Link with google temporarily unavailable");
/*
firebase
.auth()
.currentUser.linkWithPopup(gmailProvider)
@ -96,21 +103,23 @@ export function linkWithGoogle() {
.catch(function (error) {
console.log(error);
});
*/
}
export function signOut() {
//don't think I need an axios request here if I'm using jwt
window.localStorage.removeItem("accessToken");
window.localStorage.removeItem("refreshToken");
window.localStorage.removeItem("user");
Notifications.add("Signed out", 0, 2);
AllTimeStats.clear();
Settings.hideAccountSection();
AccountButton.update();
UI.changePage("login");
DB.setSnapshot(null);
userStateChanged(null);
firebase
.auth()
.signOut()
.then(function () {
Notifications.add("Signed out", 0, 2);
AllTimeStats.clear();
Settings.hideAccountSection();
AccountButton.update();
UI.changePage("login");
DB.setSnapshot(null);
})
.catch(function (error) {
Notifications.add(error.message, -1);
});
}
function signUp() {
@ -127,65 +136,131 @@ function signUp() {
$(".pageLogin .register .button").removeClass("disabled");
return;
}
axiosInstance
.post("/api/signUp", {
name: nname,
email: email,
password: password,
})
.then((response) => {
let usr = response.data.user;
window.localStorage.setItem("accessToken", response.data.accessToken);
window.localStorage.setItem("refreshToken", response.data.refreshToken);
window.localStorage.setItem("user", JSON.stringify(response.data.user));
//Cookies.set('refreshToken', response.data.refreshToken);
AllTimeStats.clear();
Notifications.add("Account created", 1, 3);
$("#menu .icon-button.account .text").text(nname);
$(".pageLogin .preloader").addClass("hidden");
DB.setSnapshot({
results: [],
personalBests: {},
tags: [],
globalStats: {
time: undefined,
started: undefined,
completed: undefined,
},
});
if (TestLogic.notSignedInLastResult !== null) {
TestLogic.setNotSignedInUid(usr.uid);
CloudFunctions.testCompleted({
uid: usr.uid,
obj: TestLogic.notSignedInLastResult,
});
DB.getSnapshot().results.push(TestLogic.notSignedInLastResult);
}
UI.changePage("account");
$(".pageLogin .register .button").removeClass("disabled");
userStateChanged(usr);
})
.catch((error) => {
//Notifications.add("Account not created. " + error.message, -1);
Notifications.add(error, -1);
axiosInstance.get(`/api/nameCheck/${nname}`).then((d) => {
console.log(d.data);
if (d.data.resultCode === -1) {
Notifications.add("Name unavailable", -1);
$(".pageLogin .preloader").addClass("hidden");
$(".pageLogin .register .button").removeClass("disabled");
return;
});
} else if (d.data.resultCode === -2) {
Notifications.add(
"Name cannot contain special characters or contain more than 14 characters. Can include _ . and -",
-1
);
$(".pageLogin .preloader").addClass("hidden");
$(".pageLogin .register .button").removeClass("disabled");
return;
} else if (d.data.resultCode === 1) {
firebase
.auth()
.createUserWithEmailAndPassword(email, password)
.then((user) => {
// Account has been created here.
// dontCheckUserName = true;
let usr = user.user;
//maybe there's a better place for the api call
axiosInstance.post("/api/signUp", {
name: nname,
uid: usr.uid,
email: email,
});
usr
.updateProfile({
displayName: nname,
})
.then(async function () {
// Update successful.
await firebase
.firestore()
.collection("users")
.doc(usr.uid)
.set({ name: nname }, { merge: true });
usr.sendEmailVerification();
AllTimeStats.clear();
Notifications.add("Account created", 1, 3);
$("#menu .icon-button.account .text").text(nname);
try {
firebase.analytics().logEvent("accountCreated", usr.uid);
} catch (e) {
console.log("Analytics unavailable");
}
$(".pageLogin .preloader").addClass("hidden");
DB.setSnapshot({
results: [],
personalBests: {},
tags: [],
globalStats: {
time: undefined,
started: undefined,
completed: undefined,
},
});
if (TestLogic.notSignedInLastResult !== null) {
TestLogic.setNotSignedInUid(usr.uid);
CloudFunctions.testCompleted({
uid: usr.uid,
obj: TestLogic.notSignedInLastResult,
});
DB.getSnapshot().results.push(TestLogic.notSignedInLastResult);
}
UI.changePage("account");
usr.sendEmailVerification();
$(".pageLogin .register .button").removeClass("disabled");
})
.catch(function (error) {
// An error happened.
$(".pageLogin .register .button").removeClass("disabled");
console.error(error);
usr
.delete()
.then(function () {
// User deleted.
Notifications.add(
"Account not created. " + error.message,
-1
);
$(".pageLogin .preloader").addClass("hidden");
})
.catch(function (error) {
// An error happened.
$(".pageLogin .preloader").addClass("hidden");
Notifications.add(
"Something went wrong. " + error.message,
-1
);
console.error(error);
});
});
})
.catch(function (error) {
// Handle Errors here.
$(".pageLogin .register .button").removeClass("disabled");
Notifications.add(error.message, -1);
$(".pageLogin .preloader").addClass("hidden");
});
} else {
$(".pageLogin .preloader").addClass("hidden");
Notifications.add(
"Something went wrong when checking name: " + d.data.message,
-1
);
}
});
}
$(".pageLogin #forgotPasswordButton").click((e) => {
let email = prompt("Email address");
if (email) {
axiosInstance
.post("/api/passwordReset", {
email: email,
})
.then(() => {
firebase
.auth()
.sendPasswordResetEmail(email)
.then(function () {
// Email sent.
Notifications.add("Email sent", 1, 2);
})
.catch((error) => {
.catch(function (error) {
// An error happened.
Notifications.add(error.message, -1);
});
}
@ -212,7 +287,7 @@ $(".signOut").click((e) => {
signOut();
});
export function userStateChanged(user) {
firebase.auth().onAuthStateChanged(function (user) {
if (user) {
// User is signed in.
$(".pageAccount .content p.accountVerificatinNotice").remove();
@ -224,7 +299,7 @@ export function userStateChanged(user) {
AccountButton.update();
AccountButton.loading(true);
Account.getDataAndInit();
// var name = user.name;
// var displayName = user.displayName;
// var email = user.email;
// var emailVerified = user.emailVerified;
// var photoURL = user.photoURL;
@ -236,8 +311,7 @@ export function userStateChanged(user) {
// showFavouriteThemesAtTheTop();
CommandlineLists.updateThemeCommands();
let text =
"Account created on " + user.metadata.creationTime.substring(0, 10);
let text = "Account created on " + user.metadata.creationTime;
const date1 = new Date(user.metadata.creationTime);
const date2 = new Date();
@ -275,7 +349,7 @@ export function userStateChanged(user) {
ChallengeController.setup(challengeName);
}, 1000);
}
}
});
$(".pageLogin .register input").keyup((e) => {
if ($(".pageLogin .register .button").hasClass("disabled")) return;

View file

@ -27,7 +27,7 @@ export function getDataAndInit() {
if (snap === null) {
throw "Missing db snapshot. Client likely could not connect to the backend.";
}
let user = DB.currentUser(); // I think that this should be stored in cookie
let user = firebase.auth().currentUser; // I think that this should be stored in cookie
if (snap.name === undefined) {
//verify username
if (Misc.isUsernameValid(user.name)) {

View file

@ -1,14 +1,21 @@
import axios from "axios";
const axiosInstance = axios.create();
const axiosInstance = axios.create({
baseURL: "http://localhost:5005",
});
// Request interceptor for API calls
axiosInstance.interceptors.request.use(
async (config) => {
const accessToken = window.localStorage.getItem("accessToken");
if (accessToken) {
let idToken;
if (firebase.auth().currentUser != null) {
idToken = await firebase.auth().currentUser.getIdToken();
} else {
idToken = null;
}
if (idToken) {
config.headers = {
Authorization: `Bearer ${accessToken}`,
Authorization: `Bearer ${idToken}`,
Accept: "application/json",
"Content-Type": "application/json",
};
@ -25,35 +32,4 @@ axiosInstance.interceptors.request.use(
}
);
// Response interceptor for API calls
axiosInstance.interceptors.response.use(
(response) => {
return response;
},
async function (error) {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
const refreshToken = window.localStorage.getItem("refreshToken");
await axios
.post(
`/api/refreshToken`,
{},
{ headers: { Authorization: `Bearer ${refreshToken}` } }
)
.then((response) => {
window.localStorage.setItem("accessToken", response.data.accessToken);
axios.defaults.headers.common["Authorization"] =
"Bearer " + response.data.accessToken;
})
.catch((error) => {
console.log(error);
axios.defaults.headers.common["Authorization"] = "Bearer failed";
});
return axiosInstance(originalRequest);
}
return Promise.reject(error);
}
);
export default axiosInstance;

View file

@ -4,7 +4,6 @@ import Config, * as UpdateConfig from "./config";
import * as Focus from "./focus";
import * as CommandlineLists from "./commandline-lists";
import * as TestUI from "./test-ui";
import axiosInstance from "./axios-instance";
let commandLineMouseMode = false;
@ -162,11 +161,13 @@ function trigger(command) {
}
});
if (!subgroup && !input && !sticky) {
axiosInstance
.post("/api/analytics/usedCommandLine", { command: command })
.catch(() => {
console.log("Analytics unavailable");
try {
firebase.analytics().logEvent("usedCommandLine", {
command: command,
});
} catch (e) {
console.log("Analytics unavailable");
}
hide();
}
}
@ -322,11 +323,13 @@ $("#commandInput input").keydown((e) => {
}
}
});
axiosInstance
.post("/api/analytics/usedCommandLine", { command: command })
.catch(() => {
console.log("Analytics unavailable");
try {
firebase.analytics().logEvent("usedCommandLine", {
command: command,
});
} catch (e) {
console.log("Analytics unavailable");
}
hide();
}
return;

View file

@ -17,7 +17,6 @@ import * as UI from "./ui";
import * as CommandlineLists from "./commandline-lists";
import * as BackgroundFilter from "./custom-background-filter";
import LayoutList from "./layouts";
import axiosInstance from "./axios-instance";
export let localStorageConfig = null;
export let dbConfigLoaded = false;
@ -485,7 +484,7 @@ export function setPaceCaret(val, nosave) {
val = "off";
}
if (document.readyState === "complete") {
if (val == "pb" && DB.currentUser() === null) {
if (val == "pb" && firebase.auth().currentUser === null) {
Notifications.add("PB pace caret is unavailable without an account", 0);
return;
}
@ -1244,11 +1243,13 @@ export function setLanguage(language, nosave) {
language = "english";
}
config.language = language;
axiosInstance
.post("/api/analytics/changedLanguage", { language: language })
.catch(() => {
console.log("Analytics unavailable");
try {
firebase.analytics().logEvent("changedLanguage", {
language: language,
});
} catch (e) {
console.log("Analytics unavailable");
}
if (!nosave) saveToLocalStorage();
}
@ -1563,8 +1564,10 @@ export function apply(configObj) {
try {
setEnableAds(configObj.enableAds, true);
let addemo = false;
//if the app is not running on production, play advertisement demo
if (window.location.hostname === "localhost") {
if (
firebase.app().options.projectId === "monkey-type-dev-67af4" ||
window.location.hostname === "localhost"
) {
addemo = true;
}

View file

@ -27,19 +27,9 @@ export function setSnapshot(newSnapshot) {
dbSnapshot = newSnapshot;
}
export function currentUser() {
const token = window.localStorage.getItem("accessToken");
if (token) {
const user = JSON.parse(window.localStorage.getItem("user"));
return user;
} else {
return null;
}
}
export async function initSnapshot() {
//send api request with token that returns tags, presets, and data needed for snap
if (currentUser() == null) return false;
if (firebase.auth().currentUser == null) return false;
await axiosInstance
.get("/api/fetchSnapshot")
.then((response) => {
@ -53,7 +43,7 @@ export async function initSnapshot() {
}
export async function getUserResults() {
let user = currentUser();
let user = firebase.auth().currentUser;
if (user == null) return false;
if (dbSnapshot === null) return false;
if (dbSnapshot.results !== undefined) {
@ -418,7 +408,7 @@ export function updateLbMemory(mode, mode2, type, value) {
}
export async function saveConfig(config) {
if (currentUser() !== null) {
if (firebase.auth().currentUser !== null) {
AccountButton.loading(true);
axiosInstance
.post("/api/saveConfig", {

View file

@ -1,5 +1,4 @@
import * as UI from "./ui";
import * as DB from "./db";
export function loading(truefalse) {
if (truefalse) {
@ -14,7 +13,7 @@ export function loading(truefalse) {
}
export function update() {
if (DB.currentUser() != null) {
if (firebase.auth().currentUser != null) {
UI.swapElements(
$("#menu .icon-button.login"),
$("#menu .icon-button.account"),

View file

@ -1,4 +1,3 @@
import * as CloudFunctions from "./cloud-functions";
import * as Loader from "./loader";
import * as Notifications from "./notifications";
import * as DB from "./db";
@ -82,7 +81,8 @@ function update() {
dailyData.board.forEach((entry) => {
if (entry.hidden) return;
let meClassString = "";
if (entry.name == DB.currentUser().name) {
//hacky way to get username because auth().currentUser.name isn't working after mongo switch
if (DB.getSnapshot() && entry.name == DB.getSnapshot().name) {
meClassString = ' class="me"';
$("#leaderboardsWrapper table.daily tfoot").html(`
<tr>
@ -165,7 +165,7 @@ function update() {
globalData.board.forEach((entry) => {
if (entry.hidden) return;
let meClassString = "";
if (entry.name == DB.currentUser().name) {
if (DB.getSnapshot() && entry.name == DB.getSnapshot().name) {
meClassString = ' class="me"';
$("#leaderboardsWrapper table.global tfoot").html(`
<tr>

View file

@ -1,10 +1,9 @@
import * as Loader from "./loader";
import * as DB from "./db";
import axiosInstance from "./axios-instance";
export function getuid() {
console.error("Only share this uid with Miodec and nobody else!");
console.log(DB.currentUser().uid);
console.log(firebase.auth().currentUser.uid);
console.error("Only share this uid with Miodec and nobody else!");
}
@ -315,7 +314,7 @@ export function migrateFromCookies() {
export function sendVerificationEmail() {
Loader.show();
let cu = DB.currentUser();
let cu = firebase.auth().currentUser;
axiosInstance
.post("/api/sendEmailVerification", {})
.then(() => {

View file

@ -86,7 +86,7 @@ $("#resultEditTagsPanel .confirmButton").click((e) => {
Loader.show();
hide();
CloudFunctions.updateResultTags({
uid: DB.currentUser().uid,
uid: firebase.auth().currentUser.uid,
tags: newtags,
resultid: resultid,
}).then((r) => {

View file

@ -7,9 +7,7 @@ import * as RouteController from "./route-controller";
import * as UI from "./ui";
import * as SignOutButton from "./sign-out-button";
import * as AccountController from "./account-controller";
import * as DB from "./db";
console.log("redy loaded");
ManualRestart.set();
Misc.migrateFromCookies();
UpdateConfig.loadFromLocalStorage();
@ -61,6 +59,5 @@ $(document).ready(() => {
}
});
Settings.settingsFillPromise.then(Settings.update);
let user = DB.currentUser();
if (user) AccountController.userStateChanged(user);
let user = firebase.auth().currentUser;
});

View file

@ -1,6 +1,5 @@
import * as Funbox from "./funbox";
import * as UI from "./ui";
import * as DB from "./db";
let mappedRoutes = {
"/": "pageTest",
@ -36,7 +35,7 @@ $(window).on("popstate", (e) => {
// show about
UI.changePage("about");
} else if (state == "account" || state == "login") {
if (DB.currentUser()) {
if (firebase.auth().currentUser) {
UI.changePage("account");
} else {
UI.changePage("login");

View file

@ -422,7 +422,7 @@ function showActiveTags() {
export function updateDiscordSection() {
//no code and no discord
if (DB.currentUser() == null) {
if (firebase.auth().currentUser == null) {
$(".pageSettings .section.discordIntegration").addClass("hidden");
} else {
if (DB.getSnapshot() == null) return;
@ -453,7 +453,7 @@ function setActiveFunboxButton() {
}
function refreshTagsSettingsSection() {
if (DB.currentUser() !== null && DB.getSnapshot() !== null) {
if (firebase.auth().currentUser !== null && DB.getSnapshot() !== null) {
let tagsEl = $(".pageSettings .section.tags .tagsList").empty();
DB.getSnapshot().tags.forEach((tag) => {
let tagPbString = "No PB found";
@ -480,7 +480,7 @@ function refreshTagsSettingsSection() {
}
function refreshPresetsSettingsSection() {
if (DB.currentUser() !== null && DB.getSnapshot() !== null) {
if (firebase.auth().currentUser !== null && DB.getSnapshot() !== null) {
let presetsEl = $(".pageSettings .section.presets .presetsList").empty();
DB.getSnapshot().presets.forEach((preset) => {
presetsEl.append(`
@ -649,7 +649,7 @@ $(
).click((e) => {
Loader.show();
CloudFunctions.generatePairingCode({
uid: DB.currentUser().uid,
uid: firebase.auth().currentUser.uid,
})
.then((ret) => {
Loader.hide();
@ -675,7 +675,7 @@ $(".pageSettings .section.discordIntegration #unlinkDiscordButton").click(
if (confirm("Are you sure?")) {
Loader.show();
CloudFunctions.unlinkDiscord({
uid: DB.currentUser().uid,
uid: firebase.auth().currentUser.uid,
}).then((ret) => {
Loader.hide();
console.log(ret);

View file

@ -153,7 +153,7 @@ list.updateEmail = new SimplePopup(
try {
Loader.show();
CloudFunctions.updateEmail({
uid: DB.currentUser().uid,
uid: firebase.auth().currentUser.uid,
previousEmail: previousEmail,
newEmail: newEmail,
}).then((data) => {
@ -190,7 +190,7 @@ list.clearTagPb = new SimplePopup(
let tagid = eval("this.parameters[0]");
Loader.show();
CloudFunctions.clearTagPb({
uid: DB.currentUser().uid,
uid: firebase.auth().currentUser.uid,
tagid: tagid,
})
.then((res) => {

View file

@ -1,4 +1,3 @@
import * as CloudFunctions from "./cloud-functions";
import * as DB from "./db";
import * as Notifications from "./notifications";
import Config from "./config";
@ -115,7 +114,7 @@ export function show(data, mode2) {
string = globalLbString + "<br>" + dailyLbString;
// CloudFunctions.saveLbMemory({
// uid: DB.currentUser().uid,
// uid: firebase.auth().currentUser.uid,
// obj: DB.getSnapshot().lbMemory,
// }).then((d) => {
// if (d.data.returnCode === 1) {

View file

@ -316,10 +316,10 @@ export function startTest() {
UpdateConfig.setChangedBeforeDb(true);
}
try {
if (DB.currentUser() != null) {
axiosInstance.post("/api/analytics/testStarted");
if (firebase.auth().currentUser != null) {
firebase.analytics().logEvent("testStarted");
} else {
axiosInstance.post("/api/analytics/testStartedNoLogin");
firebase.analytics().logEvent("testStartedNoLogin");
}
} catch (e) {
console.log("Analytics unavailable");
@ -1404,8 +1404,8 @@ export function finish(difficultyFailed = false) {
stats.acc > 50 &&
stats.acc <= 100
) {
if (DB.currentUser() != null) {
completedEvent.uid = DB.currentUser().uid;
if (firebase.auth().currentUser != null) {
completedEvent.uid = firebase.auth().currentUser.uid;
//check local pb
AccountButton.loading(true);
let dontShowCrown = false;
@ -1638,13 +1638,13 @@ export function finish(difficultyFailed = false) {
}
}
axiosInstance
.post("/api/analytics/testCompleted", {
completedEvent: completedEvent,
})
.catch(() => {
console.log("Analytics unavailable");
});
try {
firebase
.analytics()
.logEvent("testCompleted", completedEvent);
} catch (e) {
console.log("Analytics unavailable");
}
if (e.data.resultCode === 2) {
//new pb
@ -1679,29 +1679,25 @@ export function finish(difficultyFailed = false) {
});
});
} else {
axiosInstance
.post("/api/analytics/testCompletedNoLogin", {
completedEvent: completedEvent,
})
.catch((e) => {
console.log("Analytics unavailable");
});
try {
firebase.analytics().logEvent("testCompletedNoLogin", completedEvent);
} catch (e) {
console.log("Analytics unavailable");
}
notSignedInLastResult = completedEvent;
}
} else {
Notifications.add("Test invalid", 0);
TestStats.setInvalid();
axiosInstance
.post("/api/analytics/testCompletedInvalid", {
completedEvent: completedEvent,
})
.catch((e) => {
console.log("Analytics unavailable");
});
try {
firebase.analytics().logEvent("testCompletedInvalid", completedEvent);
} catch (e) {
console.log("Analytics unavailable");
}
}
}
if (DB.currentUser() != null) {
if (firebase.auth().currentUser != null) {
$("#result .loginTip").addClass("hidden");
} else {
$("#result .stats .leaderboards").addClass("hidden");

View file

@ -192,7 +192,7 @@ export function screenshot() {
$(".pageTest .ssWatermark").addClass("hidden");
$(".pageTest .buttons").removeClass("hidden");
if (revealReplay) $("#resultReplay").removeClass("hidden");
if (DB.currentUser() == null)
if (firebase.auth().currentUser == null)
$(".pageTest .loginTip").removeClass("hidden");
}

View file

@ -96,13 +96,13 @@ export function apply(themeName) {
});
}
axiosInstance
.post("/api/analytics/changedTheme", {
try {
firebase.analytics().logEvent("changedTheme", {
theme: themeName,
})
.catch((e) => {
console.log("Analytics unavailable");
});
} catch (e) {
console.log("Analytics unavailable");
}
setTimeout(() => {
$(".keymap-key").attr("style", "");
ChartController.updateAllChartColors();

View file

@ -14,7 +14,6 @@ import * as Settings from "./settings";
import * as Account from "./account";
import * as Leaderboards from "./leaderboards";
import * as Funbox from "./funbox";
import * as DB from "./db";
export let pageTransition = false;
@ -164,8 +163,10 @@ export function changePage(page) {
TestConfig.hide();
SignOutButton.hide();
} else if (page == "account") {
if (!DB.currentUser()) {
console.log(`current user is ${DB.currentUser()}, going back to login`);
if (!firebase.auth().currentUser) {
console.log(
`current user is ${firebase.auth().currentUser}, going back to login`
);
changePage("login");
} else {
setPageTransition(true);
@ -188,7 +189,7 @@ export function changePage(page) {
TestConfig.hide();
}
} else if (page == "login") {
if (DB.currentUser() != null) {
if (firebase.auth().currentUser != null) {
changePage("account");
} else {
setPageTransition(true);

View file

@ -4258,7 +4258,18 @@
<div id="nitropay_ad_right" class="hidden"></div>
</div>
</body>
<!-- The core Firebase JS SDK is always required and must be listed first -->
<script src="/__/firebase/8.0.2/firebase-app.js"></script>
<!-- TODO: Add SDKs for Firebase products that you want to use
https://firebase.google.com/docs/web/setup#available-libraries -->
<script src="/__/firebase/8.0.2/firebase-analytics.js"></script>
<script src="/__/firebase/8.0.2/firebase-auth.js"></script>
<script src="/__/firebase/8.0.2/firebase-firestore.js"></script>
<script src="/__/firebase/8.0.2/firebase-functions.js"></script>
<!-- Initialize Firebase -->
<script src="/__/firebase/init.js"></script>
<script src="js/jquery-3.5.1.min.js"></script>
<script src="js/jquery.color.min.js"></script>
<script src="js/easing.js"></script>

View file

@ -32896,6 +32896,12 @@
"source": "Steins;Gate",
"length": 247,
"id": 5538
},
{
"text": "Any fool can write code that a computer can understand. Good programmers write code that humans can understand.",
"source": "Martin Fowler",
"length": 111,
"id": 5539
}
]
}

Binary file not shown.