This commit is contained in:
Troplo 2022-08-03 19:11:19 +10:00
parent 344fce43ab
commit e2cb2adff6
22 changed files with 436 additions and 532 deletions

View file

@ -4,8 +4,6 @@ let express = require("express")
let app = express()
let bodyParser = require("body-parser")
let os = require("os")
const cookieParser = require("cookie-parser")
app.use(cookieParser())
app.set("trust proxy", true)
const socket = require("./lib/socket")
const server = require("http").createServer(app)
@ -45,7 +43,6 @@ app.all("/api/*", (req, res) => {
console.log(os.hostname())
app.use(require("./lib/errorHandler"))
server.listen(23998, () => {
console.log("Initialized")
console.log("Listening on port 0.0.0.0:" + 23998)

View file

@ -1,10 +1,18 @@
const { User, Theme, Session } = require("../models")
const Errors = require("./errors")
const { Op } = require("sequelize")
module.exports = async function (req, res, next) {
try {
if (req.header("Authorization") && req.header("Authorization") !== "null") {
const token = req.header("Authorization")
const session = await Session.findOne({ where: { session: token } })
const session = await Session.findOne({
where: {
session: token,
expiredAt: {
[Op.gt]: new Date()
}
}
})
if (session) {
const user = await User.findOne({
where: { id: session.userId },

View file

@ -1,53 +1,37 @@
const { User, Theme, Session } = require("../models")
const { Op } = require("sequelize")
const parseCookie = (str) =>
str
.split(";")
.map((v) => v.split("="))
.reduce((acc, v) => {
acc[decodeURIComponent(v[0].trim())] = decodeURIComponent(v[1].trim())
return acc
}, {})
module.exports = async function (socket, next) {
try {
const cookies = parseCookie(socket.handshake.headers.cookie)
if (cookies["session"]) {
const token = socket.handshake.auth.token
const session = await Session.findOne({
where: { session: cookies["session"] }
where: {
session: token,
expiredAt: {
[Op.gt]: new Date()
}
}
})
if (session) {
const user = await User.findOne({
where: { id: session.userId },
attributes: {
exclude: ["totp", "password", "emailToken"]
exclude: ["totp", "compassSession", "password"]
},
include: [
{
model: Theme,
as: "themeObject"
}
],
raw: true
]
})
if (user) {
if (user.banned) {
socket.user = {
id: null,
username: "Not Authenticated"
}
next()
} else {
await user.update({
lastSeenAt: new Date().toISOString()
})
socket.user = user
next()
}
}
} else {
socket.user = {
id: null,
username: "Not Authenticated"
}
next()
}
} else {
socket.user = {
id: null,

View file

@ -45,7 +45,8 @@ let Errors = {
401
],
mailFail: ["There was an error sending the verification email.", 400],
invalidToken: ["The token you provided is invalid.", 400]
invalidToken: ["The token you provided is invalid.", 400],
cannotFriendYourself: ["You cannot friend yourself.", 400]
}
function processErrors(errorName) {

View file

@ -122,126 +122,10 @@ module.exports = {
socket.emit("unauthorized", {
message: "Please reauth."
})
socket.on("token", async (token) => {
const session = await Session.findOne({ where: { session: token } })
if (session) {
const user = await User.findOne({
where: { id: session.userId },
attributes: {
exclude: ["totp", "password", "emailToken"]
},
include: [
{
model: Theme,
as: "themeObject"
}
]
socket.on("token", async () => {
socket.emit("unsupported", {
message: "This authentication method is unsupported."
})
if (user) {
socket.user = user
socket.join(user.id)
socket.emit("authorized")
socket.join(user.id)
socket.emit("siteState", {
release: process.env.RELEASE,
notification: process.env.NOTIFICATION,
notificationType: process.env.NOTIFICATION_TYPE,
latestVersion: require("../../frontend/package.json").version
})
const friends = await Friend.findAll({
where: {
userId: user.id,
status: "accepted"
}
})
await user.update({
status:
user.storedStatus === "invisible"
? "offline"
: user.storedStatus
})
friends.forEach((friend) => {
io.to(friend.friendId).emit("userStatus", {
userId: user.id,
status:
user.storedStatus === "invisible"
? "offline"
: user.storedStatus
})
})
socket.on("ping", () => {
socket.emit("pong")
})
socket.on("bcBots/deleteMessage", (e) => {
if (socket.user.bot) {
socket.to(e.userId).emit("deleteMessage", e)
} else {
socket.emit("bcBots/deleteMessage", {
error: "You cannot perform this action."
})
}
})
socket.on("idle", async () => {
const user = await User.findOne({
where: {
id: socket.user.id
}
})
if (user.storedStatus === "online") {
friends.forEach((friend) => {
io.to(friend.friendId).emit("userStatus", {
userId: user.id,
status: "away"
})
})
io.to(user.id).emit("userStatus", {
userId: user.id,
status: "away"
})
await user.update({
status: "away"
})
}
})
socket.on("online", async () => {
const user = await User.findOne({
where: {
id: socket.user.id
}
})
if (user.storedStatus === "online") {
friends.forEach((friend) => {
io.to(friend.friendId).emit("userStatus", {
userId: user.id,
status: "online"
})
})
io.to(user.id).emit("userStatus", {
userId: user.id,
status: "online"
})
await user.update({
status: "online"
})
}
})
socket.on("disconnect", async function () {
const clients =
io.sockets.adapter.rooms.get(user.id) || new Set()
if (!clients.size || clients.size === 0) {
friends.forEach((friend) => {
io.to(friend.friendId).emit("userStatus", {
userId: user.id,
status: "offline"
})
})
await user.update({
status: "offline"
})
}
})
}
}
})
console.log("Unauthenticated user")
socket.on("reAuth", async () => {

View file

@ -50,6 +50,9 @@ router.post("/", auth, async (req, res, next) => {
}
})
if (user) {
if (user.id === req.user.id) {
throw Errors.cannotFriendYourself
}
const friend = await Friend.findOne({
where: {
userId: req.user.id,

View file

@ -133,27 +133,29 @@ router.post("/verify/resend", auth, mailLimiter, async (req, res, next) => {
}
})
router.post("/verify/confirm/:token", auth, async (req, res, next) => {
router.post("/verify/confirm/:token", async (req, res, next) => {
try {
const user = await User.findOne({
where: {
id: req.user.id
}
})
if (process.env.EMAIL_VERIFICATION !== "true") {
throw Errors.invalidParameter("Email verification is disabled")
}
if (!req.params.token) {
throw Errors.invalidToken
}
if (req.params.token !== user.emailToken) {
throw Errors.invalidToken
const user = await User.findOne({
where: {
emailToken: req.params.token,
emailVerified: false
}
})
if (user) {
await user.update({
emailVerified: true,
emailToken: null
})
res.json({ success: true })
} else {
throw Errors.invalidToken
}
} catch (e) {
next(e)
}
@ -198,12 +200,6 @@ router.post("/login", async (req, res, next) => {
osVersion: ua.os.version
}
})
res.cookie("session", session.session, {
maxAge: 1000 * 60 * 60 * 24 * 365,
httpOnly: true,
secure: false,
sameSite: "strict"
})
res.json({
session: session.session,
success: true,
@ -287,12 +283,6 @@ router.post("/register", limiter, async (req, res, next) => {
osVersion: ua.os.version
}
})
res.cookie("session", session.session, {
maxAge: 1000 * 60 * 60 * 24 * 365,
httpOnly: true,
secure: false,
sameSite: "strict"
})
res.json({
session: session.session,
success: true,
@ -371,15 +361,6 @@ router.delete("/sessions/:id", auth, async (req, res, next) => {
}
})
router.post("/logout", (req, res, next) => {
try {
res.clearCookie("session")
res.sendStatus(204)
} catch (e) {
next(e)
}
})
router.post(
"/settings/avatar",
auth,

View file

@ -1,8 +1,26 @@
{
"name": "colubrina",
"version": "1.0.12",
"version": "1.0.13",
"private": true,
"author": "Troplo <troplo@troplo.com>",
"build": {
"appId": "com.troplo.colubrina",
"win": {
"publish": [
"github"
]
},
"linux": {
"publish": [
"github"
]
},
"mac": {
"publish": [
"github"
]
}
},
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
@ -12,9 +30,11 @@
"postinstall": "patch-package && electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps"
},
"main": "background.js",
"repository": "https://github.com/Troplo/Colubrina",
"dependencies": {
"@babel/preset-env": "^7.17.10",
"@mdi/font": "6.5.95",
"@mdi/js": "^6.6.95",
"axios": "^0.26.1",
"brace": "^0.11.1",
@ -23,8 +43,6 @@
"dayjs": "^1.11.0",
"ejs": "^3.1.7",
"glob-parent": "^5.1.2",
"graphql": "^16.3.0",
"graphql-tag": "^2.12.6",
"highlight.js": "^11.6.0",
"markdown-it": "^13.0.1",
"markdown-it-emoji": "git+https://github.com/Troplo/markdown-it-unicode-emoji",
@ -39,16 +57,13 @@
"socket.io-client": "^4.5.1",
"twemoji": "^14.0.2",
"vue": "^2.6.11",
"vue-apollo": "^3.1.0",
"vue-axios": "^3.4.1",
"vue-chartjs": "^4.0.1",
"vue-final-modal": "^2.4.1",
"vue-matomo": "^4.1.0",
"vue-native-notification": "^1.1.1",
"vue-router": "^3.2.0",
"vue-sanitize": "^0.2.1",
"vue-shortkey": "^3.1.7",
"vue-swatches": "^2.1.1",
"vue-toastification": "^1.7.14",
"vue2-ace-editor": "^0.0.15",
"vuetify": "^2.6.4",
@ -56,7 +71,6 @@
},
"devDependencies": {
"@babel/plugin-proposal-optional-chaining": "^7.16.7",
"@mdi/font": "6.5.95",
"@vue/cli-plugin-babel": "~4.5.15",
"@vue/cli-plugin-eslint": "~4.5.15",
"@vue/cli-plugin-pwa": "~4.5.15",

View file

@ -437,12 +437,15 @@
is limited.
</v-alert>
</v-container>
<div @touchstart="touchStart" @touchend="touchEnd">
<router-view
id="main"
:style="
'background-color: ' +
$vuetify.theme.themes[$vuetify.theme.dark ? 'dark' : 'light'].bg
"
/>
</div>
</v-main>
</v-app>
</template>
@ -451,7 +454,7 @@
import AjaxErrorHandler from "@/lib/errorHandler"
import { VueFinalModal } from "vue-final-modal"
import Header from "@/components/Header"
import Vue from "vue"
export default {
name: "App",
components: {
@ -479,7 +482,9 @@ export default {
results: [],
searchInput: null,
themes: [],
cssTips: true
cssTips: true,
touchStartX: null,
touchEndX: null
}),
computed: {
creatorJSON: {
@ -702,6 +707,19 @@ export default {
})
})
},
touchEnd(event) {
this.touchEndX = event.changedTouches[0].screenX
if (this.touchEndX > this.touchStartX) {
if (this.touchEndX - this.touchStartX > 100) {
this.touchStartX = null
this.touchEndX = null
this.$store.state.drawer = true
}
}
},
touchStart(event) {
this.touchStartX = event.changedTouches[0].screenX
},
setTheme(theme) {
const name = theme.id
const dark = theme.dark
@ -728,23 +746,8 @@ export default {
})
}
},
mounted() {
Vue.axios.defaults.headers.common["X-Colubrina"] = true
Vue.axios.defaults.headers.common["Authorization"] =
localStorage.getItem("session")
console.log(localStorage.getItem("instance"))
if (process.env.IS_ELECTRON) {
this.axios.defaults.baseURL = localStorage.getItem("instance")
this.$store.state.baseURL = localStorage.getItem("instance")
}
if (localStorage.getItem("customHeaders")) {
console.log(JSON.parse(localStorage.getItem("customHeaders")))
for (let header in JSON.parse(localStorage.getItem("customHeaders"))) {
Vue.axios.defaults.headers[header] = JSON.parse(
localStorage.getItem("customHeaders")
)[header]
}
}
async mounted() {
await this.$store.dispatch("doInit")
if (this.$vuetify.breakpoint.mobile) {
this.$store.state.drawer = false
}
@ -772,7 +775,8 @@ export default {
})
this.$socket.connect()
this.$socket.on("unauthorized", () => {
this.$socket.emit("token", localStorage.getItem("session"))
console.log("Reauth requested")
this.$socket.emit("token", localStorage.getItem("token"))
})
document.title = this.$route.name
? this.$route.name + " - " + this.$store.state.site.name
@ -780,13 +784,10 @@ export default {
this.$store.commit("setLoading", true)
this.$vuetify.theme.dark = this.$store.state.user?.theme === "dark" || true
this.$store.dispatch("getState")
this.getThemes()
this.communicationsIdleCheck()
this.$store
.dispatch("getUserInfo")
.then(() => {
console.log(window.location.pathname)
// check if its /email/confirm/<token>
if (
!window.location.pathname.includes("/email/confirm/") &&
!window.location.pathname.includes("/email/verify") &&
@ -797,10 +798,10 @@ export default {
}
})
.catch(() => {
if (!["/login", "/register"].includes(this.$route.path)) {
this.$router.push("/login")
}
})
this.getThemes()
this.communicationsIdleCheck()
this.registerSocket()
},
watch: {

View file

@ -1,5 +1,5 @@
.offset-message {
padding-left: 47px;
padding-left: 53px;
}
.message-action-card {
position: absolute;

View file

@ -1,6 +1,6 @@
"use strict"
import { app, protocol, BrowserWindow } from "electron"
import { app, protocol, BrowserWindow, screen } from "electron"
import { createProtocol } from "vue-cli-plugin-electron-builder/lib"
import installExtension, { VUEJS_DEVTOOLS } from "electron-devtools-installer"
const isDevelopment = process.env.NODE_ENV !== "production"
@ -12,15 +12,25 @@ protocol.registerSchemesAsPrivileged([
async function createWindow() {
// Create the browser window.
const { bounds } = screen.getDisplayNearestPoint(
screen.getCursorScreenPoint()
)
const height = Math.floor(bounds.height * (2 / 3))
const width = Math.floor(bounds.width * (2 / 3))
const y = Math.floor(bounds.y + (bounds.height - height) / 2)
const x = Math.floor(bounds.x + (bounds.width - width) / 2)
const win = new BrowserWindow({
width: 1400,
height: 1000,
width,
height,
x,
y,
webPreferences: {
// Use pluginOptions.nodeIntegration, leave this alone
// See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
preload: "window-preload.js",
// required for multi-instance support
webSecurity: false
}
})

View file

@ -21,8 +21,8 @@
<v-textarea
:style="
!$vuetify.breakpoint.mobile
? 'margin-bottom: 3px'
: 'margin-bottom: -17px'
? 'margin-bottom: 5px'
: 'margin-bottom: 0px'
"
autofocus
label="Type a message"

View file

@ -307,7 +307,7 @@
</v-card-actions>
</v-card>
</v-dialog>
<v-app-bar app color="bg">
<v-app-bar app color="dark" elevation="5" style="z-index: 15">
<v-app-bar-nav-icon
@click.stop="$store.state.drawer = !$store.state.drawer"
v-if="$vuetify.breakpoint.mobile || !$store.state.drawer"
@ -381,10 +381,11 @@
</template>
</v-app-bar>
<v-navigation-drawer
color="bg"
floating
color="dark"
app
style="max-height: 100%"
floating
style="max-height: 100%; z-index: 15"
class="elevation-5"
v-model="$store.state.drawer"
:width="$vuetify.breakpoint.mobile ? 270 : 320"
>
@ -414,7 +415,7 @@
</template>
<template v-else>
<v-btn
color="toolbar"
color="sheet"
to="/communications/friends"
block
class="mb-3 rounded-xl"
@ -429,12 +430,12 @@
solo
label="Search..."
append-icon="mdi-magnify"
background-color="toolbar"
background-color="sheet"
style="margin-bottom: -18px"
elevation="2"
v-model="search"
></v-text-field>
<v-toolbar color="toolbar" class="rounded-xl mb-3" elevation="2">
<v-toolbar color="sheet" class="rounded-xl mb-3" elevation="2">
<v-toolbar-title class="subtitle-1">
CHATS ({{ chats.length }})
</v-toolbar-title>
@ -522,6 +523,7 @@
</v-container>
<template v-slot:append>
<v-card tile color="bg" elevation="0">
<v-divider></v-divider>
<v-overlay :value="!$store.state.wsConnected" absolute>
<v-progress-circular indeterminate size="48"></v-progress-circular>
</v-overlay>
@ -985,7 +987,7 @@ export default {
},
mounted() {
Vue.axios.defaults.headers.common["Authorization"] =
localStorage.getItem("session")
localStorage.getItem("token")
this.searchUsers()
this.searchUsersForGroupAdmin()
this.$store.dispatch("getChats")

View file

@ -37,12 +37,15 @@
</v-toolbar>
<v-list-item
:key="message.keyId"
:class="{ 'message-hover': hover }"
:class="{
'message-hover': hover,
'pa-0': $vuetify.breakpoint.mobile
}"
:id="'message-' + index"
@contextmenu="show($event, 'message', message)"
:style="lastMessage ? 'margin-bottom: -5px; margin-top: -5px;' : ''"
>
<v-avatar size="40" class="mr-2" v-if="!lastMessage">
<v-avatar size="45" class="mr-2" v-if="!lastMessage">
<v-img
:src="
$store.state.baseURL + '/usercontent/' + message.user.avatar
@ -124,7 +127,7 @@
no-gutters
>
<v-card
elevaion="0"
elevation="0"
:color="
embed.type === 'embed-v1' ? embed.backgroundColor : 'bg'
"
@ -308,7 +311,7 @@
:id="'attachment-' + index"
:max-width="500"
:min-width="!$vuetify.breakpoint.mobile ? 400 : 0"
elevaion="0"
elevation="0"
color="card"
>
<v-hover

View file

@ -69,7 +69,10 @@ Vue.use({
{
transports: ["websocket", "polling"],
headers: {
Authorization: localStorage.getItem("session")
Authorization: localStorage.getItem("token")
},
auth: {
token: localStorage.getItem("token")
}
}
)

View file

@ -25,6 +25,7 @@ function getDirectRecipient(context, item) {
}
export default new Vuex.Store({
state: {
desktop: !!process.env.IS_ELECTRON,
online: true,
selectedChat: null,
chats: [],
@ -142,7 +143,7 @@ export default new Vuex.Store({
actions: {
getChats(context) {
Vue.axios.defaults.headers.common["Authorization"] =
localStorage.getItem("session")
localStorage.getItem("token")
Vue.axios
.get("/api/v1/communications")
.then((res) => {
@ -266,7 +267,7 @@ export default new Vuex.Store({
checkAuth() {
return new Promise((resolve, reject) => {
Vue.axios.defaults.headers.common["Authorization"] =
localStorage.getItem("session")
localStorage.getItem("token")
Vue.axios
.get("/api/v1/user")
.then(() => {
@ -282,21 +283,13 @@ export default new Vuex.Store({
})
},
logout(context) {
Vue.axios
.post("/api/v1/user/logout")
.then(() => {
context.commit("setToken", null)
localStorage.removeItem("userCache")
localStorage.removeItem("session")
localStorage.removeItem("token")
Vue.axios.defaults.headers.common["Authorization"] = null
context.commit("setUser", {
bcUser: null,
loggedIn: false
})
})
.catch(() => {
Vue.$toast.error("Failed to logout.")
})
},
generateCache() {
// todo
@ -366,56 +359,26 @@ export default new Vuex.Store({
}
})
},
getUserInfo(context) {
doInit(context) {
if (context.state.desktop) {
Vue.axios.defaults.baseURL = localStorage.getItem("instance")
context.state.baseURL = localStorage.getItem("instance")
}
Vue.axios.defaults.headers.common["X-Colubrina"] = true
Vue.axios.defaults.headers.common["X-Colubrina-Version"] =
context.state.versioning.version
Vue.axios.defaults.headers.common["Authorization"] =
localStorage.getItem("session")
return new Promise((resolve, reject) => {
try {
const user = JSON.parse(localStorage.getItem("userCache"))
if (user?.id) {
context.commit("setUser", user)
const name = user.themeObject.id
const dark = user.themeObject.theme.dark
const light = user.themeObject.theme.light
if (user.accentColor) {
user.themeObject.theme.dark.primary = user.accentColor
user.themeObject.theme.light.primary = user.accentColor
}
Vuetify.framework.theme.themes.dark = dark
Vuetify.framework.theme.themes.light = light
Vuetify.framework.theme.themes.name = name
Vuetify.framework.theme.themes.primaryType =
user.themeObject.theme.primaryType
const themeElement = document.getElementById("user-theme")
if (!themeElement) {
const style = document.createElement("style")
style.id = "user-theme"
style.innerHTML = user.themeObject.theme.css
document.head.appendChild(style)
}
const fontElement = document.getElementById("user-font")
if (!fontElement) {
const style = document.createElement("style")
style.id = "user-font"
style.innerHTML = `/* Stop from font breaking CSS code editor */
.ace_editor div {
font-family: "JetBrains Mono" !important;
}
div {
font-family: "${user.font}", sans-serif;
}
`
document.head.appendChild(style)
localStorage.getItem("token")
if (localStorage.getItem("customHeaders")) {
for (let header in JSON.parse(localStorage.getItem("customHeaders"))) {
Vue.axios.defaults.headers[header] = JSON.parse(
localStorage.getItem("customHeaders")
)[header]
}
}
} catch {
//
}
Vue.axios
.get("/api/v1/user")
.then((res) => {
context.commit("setUser", res.data)
},
getUserInfo(context) {
function setUser(user) {
try {
Vue.$socket.emit("connection", {
message: true
@ -423,48 +386,7 @@ div {
} catch {
console.log("Socket not connected")
}
localStorage.setItem("userCache", JSON.stringify(res.data))
const name = res.data.themeObject.id
const dark = res.data.themeObject.theme.dark
const light = res.data.themeObject.theme.light
if (res.data.accentColor) {
res.data.themeObject.theme.dark.primary = res.data.accentColor
res.data.themeObject.theme.light.primary = res.data.accentColor
}
Vuetify.framework.theme.themes.dark = dark
Vuetify.framework.theme.themes.light = light
Vuetify.framework.theme.themes.name = name
Vuetify.framework.theme.themes.primaryType =
res.data.themeObject.theme.primaryType
const themeElement = document.getElementById("user-theme")
if (!themeElement) {
const style = document.createElement("style")
style.id = "user-theme"
style.innerHTML = res.data.themeObject.theme.css
document.head.appendChild(style)
}
const fontElement = document.getElementById("user-font")
if (!fontElement) {
const style = document.createElement("style")
style.id = "user-font"
style.innerHTML = `/* Stop from font breaking CSS code editor */
.ace_editor div {
font-family: "JetBrains Mono" !important;
}
div {
font-family: "${res.data.font}", sans-serif;
}
`
document.head.appendChild(style)
}
context.commit("setLoading", false)
context.commit("setOnline", true)
resolve(res.data)
})
.catch((e) => {
if (JSON.parse(localStorage.getItem("userCache"))?.id) {
const user = JSON.parse(localStorage.getItem("userCache"))
localStorage.setItem("userCache", JSON.stringify(user))
const name = user.themeObject.id
const dark = user.themeObject.theme.dark
const light = user.themeObject.theme.light
@ -501,8 +423,128 @@ div {
}
context.commit("setLoading", false)
context.commit("setUser", user)
resolve(user)
}
Vue.axios.defaults.headers.common["Authorization"] =
localStorage.getItem("token")
return new Promise((resolve, reject) => {
try {
const user = JSON.parse(localStorage.getItem("userCache"))
if (user?.id) {
setUser(user)
}
} catch {
//
}
Vue.axios
.get("/api/v1/user")
.then((res) => {
setUser(res.data)
context.commit("setLoading", false)
context.commit("setOnline", true)
resolve(res.data)
})
.catch((e) => {
try {
const user = JSON.parse(localStorage.getItem("userCache"))
if (user?.id && !e?.response?.data?.errors?.length) {
setUser(user)
} else {
const theme = {
id: 1,
name: "Colubrina Classic",
primaryType: "all",
dark: {
primary: "#0190ea",
secondary: "#757575",
accent: "#000000",
error: "#ff1744",
info: "#2196F3",
success: "#4CAF50",
warning: "#ff9800",
card: "#151515",
toolbar: "#191919",
sheet: "#181818",
text: "#000000",
dark: "#151515",
bg: "#151515"
},
light: {
primary: "#0190ea",
secondary: "#757575",
accent: "#000000",
error: "#ff1744",
info: "#2196F3",
success: "#4CAF50",
warning: "#ff9800",
card: "#f8f8f8",
toolbar: "#f8f8f8",
sheet: "#f8f8f8",
text: "#000000",
dark: "#f8f8f8",
bg: "#f8f8f8"
}
}
const name = theme.id
const dark = theme.dark
const light = theme.light
Vuetify.framework.theme.themes.dark = dark
Vuetify.framework.theme.themes.light = light
Vuetify.framework.theme.themes.name = name
this.name = name
console.log("Failed to load Colubrina Account")
context.user = null
localStorage.removeItem("userCache")
reject(e)
}
} catch {
const theme = {
id: 1,
name: "Colubrina Classic",
primaryType: "all",
dark: {
primary: "#0190ea",
secondary: "#757575",
accent: "#000000",
error: "#ff1744",
info: "#2196F3",
success: "#4CAF50",
warning: "#ff9800",
card: "#151515",
toolbar: "#191919",
sheet: "#181818",
text: "#000000",
dark: "#151515",
bg: "#151515"
},
light: {
primary: "#0190ea",
secondary: "#757575",
accent: "#000000",
error: "#ff1744",
info: "#2196F3",
success: "#4CAF50",
warning: "#ff9800",
card: "#f8f8f8",
toolbar: "#f8f8f8",
sheet: "#f8f8f8",
text: "#000000",
dark: "#f8f8f8",
bg: "#f8f8f8"
}
}
const name = theme.id
const dark = theme.dark
const light = theme.light
Vuetify.framework.theme.themes.dark = dark
Vuetify.framework.theme.themes.light = light
Vuetify.framework.theme.themes.name = name
this.name = name
console.log("Failed to load Colubrina Account")
localStorage.removeItem("userCache")
context.user = null
reject(e)
}
if (!localStorage.getItem("userCache")) {
const theme = {
id: 1,
name: "Colubrina Classic",

View file

@ -1,17 +1,11 @@
<template>
<div id="communications">
<v-container fluid>
<v-row>
<v-col>
<router-view
v-if="$route.params.id !== 'home'"
:chat="selectedChat"
:loading="false"
:items="$store.state.chats"
></router-view>
</v-col>
</v-row>
</v-container>
</div>
</template>

View file

@ -169,8 +169,8 @@
<v-row v-if="!loading" @drop="handleDrag" no-gutters>
<v-col class="flex-grow-1 flex-shrink-1 pb-0" id="chat-col">
<v-card
class="d-flex flex-column fill-height rounded-xl"
style="overflow: auto; height: calc(100vh - 24px - 40px - 40px)"
class="d-flex flex-column fill-height rounded-xl mb-n3"
style="overflow: auto; height: calc(100vh - 24px - 40px)"
color="card"
elevation="0"
>
@ -202,7 +202,6 @@
style="display: block; width: 100px; margin: 0 auto"
></v-progress-circular>
<template v-for="(message, index) in messages">
<div :key="'div-' + message.keyId">
<Message
:key="message.keyId"
:message="message"
@ -227,7 +226,6 @@
!message.replyId
"
></Message>
</div>
<div
:key="'div2-' + message.keyId"
v-if="message.readReceipts.length"
@ -277,6 +275,7 @@
</template>
<v-tooltip top>
<template v-slot:activator="{ on }">
<span>
<v-btn
icon
small
@ -302,6 +301,7 @@
}}</span>
</v-avatar>
</v-btn>
</span>
</template>
<span>
{{ $store.state.user.username }} has read up to this point.
@ -368,25 +368,18 @@
v-model="usersTyping.length"
v-if="$vuetify.breakpoint.mobile"
>
<v-toolbar
height="22"
color="toolbar"
elevation="0"
<div
style="
border-radius: 20px 20px 0 0;
cursor: pointer;
border-radius: 0 0 20px 20px;
position: relative;
top: -30px;
margin-bottom: -27px;
margin-bottom: -22px;
"
width="100%"
v-if="usersTyping.length"
>
<div>
{{ usersTyping.map((user) => getName(user)).join(", ") }}
{{ usersTyping.length > 1 ? " are" : " is" }} typing...
</div>
</v-toolbar>
</v-fade-transition>
<CommsInput
:chat="chat"
@ -402,9 +395,9 @@
<div
style="
border-radius: 0 0 20px 20px;
position: relative;
margin-top: -22px;
bottom: -16px;
position: absolute;
margin-top: -2px;
bottom: 1px;
"
v-if="usersTyping.length"
>
@ -415,14 +408,6 @@
</v-card-text>
</v-card>
</v-col>
<v-divider
vertical
style="z-index: 2; padding-right: 3px; padding-left: 3px"
v-if="
($store.state.userPanel && !$vuetify.breakpoint.mobile) ||
($store.state.searchPanel && !$vuetify.breakpoint.mobile)
"
></v-divider>
<v-col
cols="3"
class=""
@ -431,7 +416,7 @@
>
<v-card
class="d-flex flex-column fill-height"
style="overflow: scroll; height: calc(100vh - 24px - 40px - 40px)"
style="overflow: scroll; height: calc(100vh - 24px - 40px)"
color="card"
elevation="0"
>
@ -484,7 +469,6 @@
</v-col>
<v-col
:cols="$vuetify.breakpoint.xl ? 2 : 3"
class="ml-2"
id="user-col"
v-if="
$store.state.userPanel &&
@ -493,10 +477,10 @@
"
>
<v-card
class="d-flex flex-column fill-height rounded-xl"
class="d-flex flex-column fill-height rounded-0"
elevation="0"
style="overflow: scroll; height: calc(100vh - 24px - 40px - 40px)"
color="card"
style="overflow: scroll; height: calc(100vh - 24px - 40px)"
color="sheet"
>
<v-menu
v-model="context.user.value"
@ -520,7 +504,7 @@
</v-list-item>
</v-list>
</v-menu>
<v-list two-line color="card">
<v-list two-line color="sheet">
<v-list-item-group class="rounded-xl">
<template v-for="item in associations">
<v-list-item

View file

@ -373,7 +373,8 @@ export default {
})
}
},
mounted() {
async mounted() {
await this.$store.dispatch("doInit")
this.getFriends()
this.getUsers()
this.$socket.on("friendRequest", () => {

View file

@ -123,7 +123,8 @@ export default {
totp: "",
totpDialog: false,
loading: false,
instance: "https://colubrina.troplo.com",
instance:
localStorage.getItem("instance") || "https://colubrina.troplo.com",
customHeaders: {}
}
},
@ -149,14 +150,17 @@ export default {
})
.then(async (res) => {
const session =
res.data.session || res.data.bcToken || res.data.cookieToken
localStorage.setItem("session", session)
res.data.session ||
res.data.token ||
res.data.bcToken ||
res.data.cookieToken
localStorage.setItem("token", session)
Vue.axios.defaults.headers.common["Authorization"] = session
this.$store.commit("setToken", session)
await this.$store.dispatch("getUserInfo")
this.$store.dispatch("getChats")
this.loading = false
this.$socket.auth.token = session
this.$socket.disconnect()
this.$socket.connect()
if (this.isElectron()) {
@ -204,9 +208,17 @@ export default {
}
},
mounted() {
if (this.$store.state.user?.id) {
this.$store
.dispatch("getUserInfo")
.then(() => {
this.$router.push("/")
})
.catch(() => {
this.$store.state.user = {
bcUser: null,
loggedIn: false
}
})
this.testInstance()
},
watch: {

View file

@ -96,7 +96,8 @@ export default {
totp: "",
totpDialog: false,
loading: false,
instance: "https://colubrina.troplo.com",
instance:
localStorage.getItem("instance") || "https://colubrina.troplo.com",
instanceString: ""
}
},
@ -131,11 +132,18 @@ export default {
totp: this.totp
})
.then(async (res) => {
localStorage.setItem("session", res.data.session)
Vue.axios.defaults.headers.common["Authorization"] = res.data.session
this.$store.commit("setToken", res.data.session)
const session =
res.data.session ||
res.data.token ||
res.data.bcToken ||
res.data.cookieToken
localStorage.setItem("token", session)
Vue.axios.defaults.headers.common["Authorization"] = session
this.$store.commit("setToken", session)
await this.$store.dispatch("getUserInfo")
this.$store.dispatch("getChats")
this.loading = false
this.$socket.auth.token = session
this.$socket.disconnect()
this.$socket.connect()
if (this.isElectron()) {
@ -169,9 +177,17 @@ export default {
}
},
mounted() {
if (this.$store.state.user?.id) {
this.$store
.dispatch("getUserInfo")
.then(() => {
this.$router.push("/")
})
.catch(() => {
this.$store.state.user = {
bcUser: null,
loggedIn: false
}
})
this.testInstance()
},
watch: {

View file

@ -5311,18 +5311,6 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
integrity sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w==
graphql-tag@^2.12.6:
version "2.12.6"
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.12.6.tgz#d441a569c1d2537ef10ca3d1633b48725329b5f1"
integrity sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==
dependencies:
tslib "^2.1.0"
graphql@^16.3.0:
version "16.5.0"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.5.0.tgz#41b5c1182eaac7f3d47164fb247f61e4dfb69c85"
integrity sha512-qbHgh8Ix+j/qY+a/ZcJnFQ+j8ezakqPiHwPiZhV/3PgGlgf96QMBB5/f2rkiC9sgLoy/xvT6TSiaf2nTHJh5iA==
gzip-size@^5.0.0:
version "5.1.1"
resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274"
@ -9802,11 +9790,6 @@ thread-loader@^2.1.3:
loader-utils "^1.1.0"
neo-async "^2.6.0"
throttle-debounce@^2.1.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-2.3.0.tgz#fd31865e66502071e411817e241465b3e9c372e2"
integrity sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ==
through2-filter@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-3.0.0.tgz#700e786df2367c2c88cd8aa5be4cf9c1e7831254"
@ -10393,15 +10376,6 @@ vm-browserify@^1.0.1:
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
vue-apollo@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/vue-apollo/-/vue-apollo-3.1.0.tgz#d0370b64f928f488b7ad98177cca8e1a82fa725e"
integrity sha512-TkXMpgypbqM/27vKIpWNhL347QRtcu9kWkEmxplPGGMnZb+hSbbIwU07LjQVzFwrLBer0ha9zJP4hIW/fCQDuw==
dependencies:
chalk "^2.4.2"
serialize-javascript "^4.0.0"
throttle-debounce "^2.1.0"
vue-axios@^3.4.1:
version "3.4.1"
resolved "https://registry.yarnpkg.com/vue-axios/-/vue-axios-3.4.1.tgz#e092ec4060b42e941ea5059df9b8f09058c3eea9"
@ -10490,11 +10464,6 @@ vue-loader@^15.9.2:
vue-hot-reload-api "^2.3.0"
vue-style-loader "^4.1.0"
vue-matomo@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/vue-matomo/-/vue-matomo-4.1.0.tgz#17b513bc8ac60e168bfa8190ad05b3257a3c441e"
integrity sha512-y+tdmhY835Ip3EAGfIAgA33+aBYrvRT7fNnBnA7bSM459XpoWXgqJKdbopVpEUrxCPIq8IkuF7g4KqSLc0Fa3w==
vue-native-notification@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/vue-native-notification/-/vue-native-notification-1.1.1.tgz#8a57950dded83b05b9e261f45bc2e027107eb9cd"
@ -10528,11 +10497,6 @@ vue-style-loader@^4.1.0, vue-style-loader@^4.1.2:
hash-sum "^1.0.2"
loader-utils "^1.0.2"
vue-swatches@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/vue-swatches/-/vue-swatches-2.1.1.tgz#26c467fb7648ff4ee0887aea36d1e03b15032b83"
integrity sha512-YugkNbByxMz1dnx1nZyHSL3VSf/TnBH3/NQD+t8JKxPSqUmX87sVGBxjEaqH5IMraOLfVmU0pHCHl2BfXNypQg==
vue-template-babel-compiler@^1.1.3:
version "1.2.0"
resolved "https://registry.yarnpkg.com/vue-template-babel-compiler/-/vue-template-babel-compiler-1.2.0.tgz#f77126886e868f64d2acefe5c0ecfd9c323bf943"