From 43533787927f1eb345018111d5200b73f475275c Mon Sep 17 00:00:00 2001 From: unenglishable Date: Fri, 20 May 2022 10:13:23 -1000 Subject: [PATCH 001/149] refactor(backend-upgrade): duplicate axios and http these "2" versions will be used to switch out routes to use new backend --- src/api/index.js | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/api/index.js b/src/api/index.js index 2cd98535..40c73e2b 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -9,6 +9,11 @@ export const $axios = axios.create({ timeout: 3000, crossDomain: true }) +export const $axios2 = axios.create({ + baseURL: 'http://localhost:4000', + timeout: 3000, + crossDomain: true +}) const $auth = localStorageCache(0, 'app').get('auth') const initUser = $auth ? $auth.data : undefined @@ -43,6 +48,34 @@ const $http = (path, opts, handleErrors) => { } else { return reqPromise } } +const $http2 = (path, opts, handleErrors) => { + opts = opts || {} + const method = (opts.method || 'get').toLowerCase() + delete opts.method + const data = opts.data + delete opts.data + + let req = (m => { + switch(m) { + case 'post': + case 'put': + case 'patch': + return $axios2[method](path, data, opts) + default: return $axios2[method](path, opts) + } + })(method) + + const reqPromise = req.then(res => res.status === 200 ? res.data : res) + + if (handleErrors) { + return reqPromise.catch(err => { + const msg = get(err, 'response.data.message') + if (msg && msg !== 'Unauthorized') { alertStore.error(msg) } + return Promise.reject(err) + }) + } + else { return reqPromise } +} export const motdApi = { get: () => $http('/api/motd'), From f9430834f0d6a86c691b7b30833e0fd20cc96c90 Mon Sep 17 00:00:00 2001 From: unenglishable Date: Fri, 20 May 2022 10:14:35 -1000 Subject: [PATCH 002/149] refactor(backend-upgrade): use http2 for email and username checks --- src/api/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/index.js b/src/api/index.js index 40c73e2b..3ad0913f 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -194,8 +194,8 @@ export const authApi = { confirmRegistration: data => $http('/api/confirm', { method: 'POST', data }, true), inviteRegistration: data => $http('/api/join', { method: 'POST', data }, true), resetPassword: data => $http(`/api/reset`, { method: 'POST', data }, true), - emailAvailable: email => $http(`/api/register/email/${email}`), - usernameAvailable: username => $http(`/api/register/username/${username}`), + emailAvailable: email => $http2(`/api/register/email/${email}`), + usernameAvailable: username => $http2(`/api/register/username/${username}`), inviteExists: email => $http(`/api/invites/exists?email=${email}`), invite: email => $http('/api/invites', { method: 'POST', data: { email }}) } From 6b0a99ff9c568cff7700ba383d348a6389ffb8ae Mon Sep 17 00:00:00 2001 From: unenglishable Date: Fri, 21 Oct 2022 15:28:42 -0700 Subject: [PATCH 003/149] refactor(backend-upgrade): use http2 for login and logout --- src/api/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/index.js b/src/api/index.js index 3ad0913f..97b4f6de 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -175,12 +175,12 @@ export const watchlistApi = { } export const authApi = { - login: data => $http('/api/login', { method: 'POST', data }, true) + login: data => $http2('/api/login', { method: 'POST', data }, true) .then(user => { $axios.defaults.headers.common['Authorization'] = `BEARER ${user.token}` return user }), - logout: () => $http('/api/logout', { method: 'DELETE' }, true) + logout: () => $http2('/api/logout', { method: 'DELETE' }, true) .then(user => { delete $axios.defaults.headers.common['Authorization'] return user From 91f56cf83f31243f91332d20d2fa9d59ff04693c Mon Sep 17 00:00:00 2001 From: Anthony Kinsey Date: Fri, 21 Oct 2022 12:32:33 -1000 Subject: [PATCH 004/149] refactor(backend-upgrade): use http2 for authenticate, register, confirmRegistration --- src/api/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/api/index.js b/src/api/index.js index 97b4f6de..36910220 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -185,13 +185,13 @@ export const authApi = { delete $axios.defaults.headers.common['Authorization'] return user }), - register: data => $http('/api/register', { method: 'POST', data }, true) + register: data => $http2('/api/register', { method: 'POST', data }, true) .then(user => { $axios.defaults.headers.common['Authorization'] = `BEARER ${user.token}` return user }), - authenticate: () => $http('/api/authenticate'), - confirmRegistration: data => $http('/api/confirm', { method: 'POST', data }, true), + authenticate: () => $http2('/api/authenticate'), + confirmRegistration: data => $http2('/api/confirm', { method: 'POST', data }, true), inviteRegistration: data => $http('/api/join', { method: 'POST', data }, true), resetPassword: data => $http(`/api/reset`, { method: 'POST', data }, true), emailAvailable: email => $http2(`/api/register/email/${email}`), From acc730fb381c52335c5ed4bed7bc07fa36a1ad54 Mon Sep 17 00:00:00 2001 From: Anthony Kinsey Date: Fri, 21 Oct 2022 12:35:51 -1000 Subject: [PATCH 005/149] refactor(config-api): use config for backend url --- src/api/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/index.js b/src/api/index.js index 09ae677d..12bb3423 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -9,7 +9,7 @@ export const $axios = axios.create({ crossDomain: true }) export const $axios2 = axios.create({ - baseURL: 'http://localhost:4000', + baseURL: process.env.VUE_APP_BACKEND_URL, timeout: 3000, crossDomain: true }) From 30a9265e530edf34956fa655695aeec0f1c4e898 Mon Sep 17 00:00:00 2001 From: Anthony Kinsey Date: Mon, 24 Oct 2022 19:18:28 -1000 Subject: [PATCH 006/149] feat(phoenix-sockets): proof of concept, connect to phoenix socket and join channel --- package.json | 1 + src/composables/services/websocket.js | 100 ++++++++++++++++++-------- yarn.lock | 5 ++ 3 files changed, 75 insertions(+), 31 deletions(-) diff --git a/package.json b/package.json index 872e2778..adfe7512 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "nestable": "git+https://github.com/slickage/Nestable.git", "normalize.css": "^8.0.1", "nprogress": "^0.2.0", + "phoenix": "^1.3.5", "sass": "^1.55.0", "slugify": "^1.6.1", "socketcluster-client": "^14.3.2", diff --git a/src/composables/services/websocket.js b/src/composables/services/websocket.js index 45a5ed70..2741caf1 100644 --- a/src/composables/services/websocket.js +++ b/src/composables/services/websocket.js @@ -2,12 +2,21 @@ import alertStore from '@/composables/stores/alert' import NotificationStore from '@/composables/stores/notifications' import { clearUser, AuthStore } from '@/composables/stores/auth' import { provide, inject, reactive } from 'vue' +import { Socket as PhoenixSocket } from 'phoenix' + +const socketUrl = process.env.VUE_APP_BACKEND_URL.replace('http://', 'ws://') + '/socket' + +const socketInstance = new PhoenixSocket(socketUrl, { + logger: (kind, msg, data) => { + if (window.websocket_logs) console.log(`${kind}: ${msg}`, data) + } +}) const socketcluster = require('socketcluster-client') // Public channel idenitfier and general options let options = { waitForAuth: true } -let userChannel +let userChannel, userChannelNew let publicChannel let session = reactive({ user: {} }) @@ -25,6 +34,27 @@ export const WebsocketService = Symbol(WEBSOCKET_KEY) // API Functions export const socketLogin = socketUser => { + console.log("HERERERERERERE") + console.log(socketInstance.connectionState() + ) + let connAvaiable = socketInstance && (socketInstance.connectionState() === 'open' || + socketInstance.connectionState() === 'connecting') + console.log(connAvaiable) + if (socketInstance.connectionState() === 'closed') { + socketInstance.params.token = socketUser.token + console.log(socketInstance.connect()) + socketInstance.connect() + console.log(socketInstance) + + console.log('Phoenix Socket connected!') + } else if (!connAvaiable) { + socketInstance.connect() + console.log('PhoenixSocket reconnected!') + } else { + console.warn('Trying to connect to a connected socket.') + } +console.log(socketInstance) + Object.assign(session.user, socketUser) socket.authenticate(socketUser.token) NotificationStore.refresh() @@ -48,7 +78,7 @@ export const watchPublicChannel = handler => { export const watchUserChannel = handler => { if (window.websocket_logs) console.log('Watching user channel.') - if (userChannel) userChannel.watch(handler) + if (userChannel && userChannelNew) userChannel.watch(handler) else setTimeout(() => watchUserChannel(handler), 1000) } @@ -71,34 +101,34 @@ export default { // Channel Subscribe socket.on('subscribe', channelName => { - if (JSON.parse(channelName).type === 'role') { - socket.watch(channelName, d => { - if (window.websocket_logs) console.log('Received role channel message.', d) - $auth.reauthenticate() - }) - } - else if (JSON.parse(channelName).type === 'user') { - socket.watch(channelName, d => { - if (window.websocket_logs) console.log('Received user channel message', d) - if (d.action === 'reauthenticate') $auth.reauthenticate() - else if (d.action === 'logout' && d.sessionId === socket.getAuthToken().sessionId) { - $auth.logout() - clearUser() // Handles clearing authed user from a out of focus tab - alertStore.warn('You have been logged out from another window.') - } - else if (d.action === 'newMessage') { NotificationStore.refresh() } - else if (d.action === 'refreshMentions') { - NotificationStore.refresh() - NotificationStore.refreshMentionsList() - } - }) - } - else if (JSON.parse(channelName).type === 'public') { + if (JSON.parse(channelName).type === 'role') { + socket.watch(channelName, d => { + if (window.websocket_logs) console.log('Received role channel message.', d) + $auth.reauthenticate() + }) + } + else if (JSON.parse(channelName).type === 'user') { + socket.watch(channelName, d => { + if (window.websocket_logs) console.log('Received user channel message', d) + if (d.action === 'reauthenticate') $auth.reauthenticate() + else if (d.action === 'logout' && d.sessionId === socket.getAuthToken().sessionId) { + $auth.logout() + clearUser() // Handles clearing authed user from a out of focus tab + alertStore.warn('You have been logged out from another window.') + } + else if (d.action === 'newMessage') { NotificationStore.refresh() } + else if (d.action === 'refreshMentions') { + NotificationStore.refresh() + NotificationStore.refreshMentionsList() + } + }) + } + else if (JSON.parse(channelName).type === 'public') { // Placeholder for future public notifications if necessary - } - else window.websocket_logs ? console.log('Not watching', channelName) : null + } + else window.websocket_logs ? console.log('Not watching', channelName) : null - if (window.websocket_logs) console.log('Websocket subscribed to', channelName, 'with watchers', socket.watchers(channelName)) + if (window.websocket_logs) console.log('Websocket subscribed to', channelName, 'with watchers', socket.watchers(channelName)) }) // Channel Unsubscribe @@ -106,12 +136,12 @@ export default { if (window.websocket_logs) console.log('Websocket unsubscribed from', channelName, socket.watchers(channelName)) // disconnect all watchers from the channel - socket.unwatch(channelName) + socket.unwatch(channelName) }) // Socket Authentication socket.on('authenticate', () => { - if (window.websocket_logs) console.log('Authenticated WebSocket Connection') + if (window.websocket_logs) console.log('Authenticated WebSocket Connection') // Emit LoggedIn event to socket server socket.emit('loggedIn') @@ -129,8 +159,16 @@ export default { } }) + socket.on('connect', status => status.isAuthenticated ? socket.emit('loggedIn') : null) + socketInstance.onOpen(() => { + socketInstance.channel('user:notifications', {}).join() + .receive("ok", resp => { console.log("Joined successfully", resp) }) + .receive("error", resp => { console.log("Unable to join", resp) }) + + }) + // always subscribe to the public channel publicChannel = socket.subscribe(publicChannelKey, { waitForAuth: false }) @@ -141,7 +179,7 @@ export default { watchUserChannel, unwatchUserChannel, watchPublicChannel, - isOnline, + isOnline }) }, render() { return this.$slots.default() } // renderless component diff --git a/yarn.lock b/yarn.lock index 76bc1073..321a25a7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4753,6 +4753,11 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +phoenix@^1.3.5: + version "1.3.5" + resolved "https://registry.yarnpkg.com/phoenix/-/phoenix-1.3.5.tgz#63f3a565cefedd22fb767f7e31b6e003a541b434" + integrity sha512-Bo2jr98jsAJAwlUQ1n8MeBjG0ZoDYWeY6upnPCZdJX7xKvdVMeF/mj+TEZdBME1l8ZftdWKenBTGzATNGGl5RA== + picocolors@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-0.2.1.tgz#570670f793646851d1ba135996962abad587859f" From 94d0614cf85653eaf37595cf400ff71fd846da4e Mon Sep 17 00:00:00 2001 From: Anthony Kinsey Date: Tue, 25 Oct 2022 15:59:03 -1000 Subject: [PATCH 007/149] fix(axios2): update axios2 to match axios --- src/api/index.js | 11 +++++------ src/components/layout/Header.vue | 8 ++++---- src/router/index.js | 27 +++++++++++++++++++++++++-- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/api/index.js b/src/api/index.js index 12bb3423..dcb8493d 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -16,8 +16,7 @@ export const $axios2 = axios.create({ const $auth = localStorageCache(0, 'app').get('auth') const initUser = $auth ? $auth.data : undefined -if (initUser) { $axios.defaults.headers.common['Authorization'] = `BEARER ${initUser.token}` } - +if (initUser) { $axios2.defaults.headers.common['Authorization'] = `BEARER ${initUser.token}` } /* provided methods */ const $http = (path, opts, handleErrors) => { opts = opts || {} @@ -176,20 +175,20 @@ export const watchlistApi = { export const authApi = { login: data => $http2('/api/login', { method: 'POST', data }, true) .then(user => { - $axios.defaults.headers.common['Authorization'] = `BEARER ${user.token}` + $axios2.defaults.headers.common['Authorization'] = `BEARER ${user.token}` return user }), logout: () => $http2('/api/logout', { method: 'DELETE' }, true) .then(user => { - delete $axios.defaults.headers.common['Authorization'] + delete $axios2.defaults.headers.common['Authorization'] return user }), register: data => $http2('/api/register', { method: 'POST', data }, true) .then(user => { - $axios.defaults.headers.common['Authorization'] = `BEARER ${user.token}` + $axios2.defaults.headers.common['Authorization'] = `BEARER ${user.token}` return user }), - authenticate: () => $http2('/api/authenticate'), + authenticate: () => $http2('/api/authenticate', {}, false), confirmRegistration: data => $http2('/api/confirm', { method: 'POST', data }, true), inviteRegistration: data => $http('/api/join', { method: 'POST', data }, true), resetPassword: data => $http(`/api/reset`, { method: 'POST', data }, true), diff --git a/src/components/layout/Header.vue b/src/components/layout/Header.vue index 09f96700..a54872e2 100644 --- a/src/components/layout/Header.vue +++ b/src/components/layout/Header.vue @@ -56,8 +56,8 @@ Watchlist -
  • - +
  • + Invite Users
  • @@ -211,8 +211,8 @@
  • Watchlist
  • -
  • - +
  • + Invite User
  • diff --git a/src/router/index.js b/src/router/index.js index 80744ecf..ef0e28d9 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -1,5 +1,5 @@ import { createWebHistory, createRouter } from 'vue-router' -import { usersApi, boardsApi, threadsApi, $axios } from '@/api' +import { usersApi, boardsApi, threadsApi, $axios, $axios2 } from '@/api' import Boards from '@/views/Boards.vue' import Threads from '@/views/Threads.vue' import ThreadsPostedIn from '@/views/ThreadsPostedIn.vue' @@ -386,7 +386,7 @@ router.afterEach(to => { else { document.body.className = '' } }) -$axios.interceptors.response.use(res => res, err => { +$axios2.interceptors.response.use(res => res, err => { console.log(err) if(err.response) { // Server still responding, just getting errors from api calls switch (err.response.status) { @@ -410,4 +410,27 @@ $axios.interceptors.response.use(res => res, err => { return Promise.reject(err) }) +$axios.interceptors.response.use(res => res, err => { + if(err.response) { // Server still responding, just getting errors from api calls + switch (err.response.status) { + case 401: + if (router.currentRoute._value.meta.requiresAuth) router.push({ name: 'Login'}) + break + case 403: + if (router.currentRoute._value.meta.ignoreAxiosInterceptor) break + if (err.response.statusText === 'Forbidden' || err.response.data.error === 'Forbidden') { + router.push({ name: 'Forbidden'}) + } + break + case 404: + router.push({ name: 'NotFound'}) + break + default: + break + } + } + else router.push({ name: 'ServiceUnavailable'}) // API is down, 503 + return Promise.reject(err) +}) + export default router From 6d8c78508e1da68c430468b31582a473933a5dc6 Mon Sep 17 00:00:00 2001 From: Anthony Kinsey Date: Thu, 27 Oct 2022 10:39:00 -1000 Subject: [PATCH 008/149] feat(channels): implement working channel connection --- src/composables/services/websocket.js | 92 +++++++++++++++------------ 1 file changed, 52 insertions(+), 40 deletions(-) diff --git a/src/composables/services/websocket.js b/src/composables/services/websocket.js index 2741caf1..33f54c89 100644 --- a/src/composables/services/websocket.js +++ b/src/composables/services/websocket.js @@ -34,40 +34,37 @@ export const WebsocketService = Symbol(WEBSOCKET_KEY) // API Functions export const socketLogin = socketUser => { - console.log("HERERERERERERE") - console.log(socketInstance.connectionState() - ) - let connAvaiable = socketInstance && (socketInstance.connectionState() === 'open' || - socketInstance.connectionState() === 'connecting') - console.log(connAvaiable) + let connAvaiable = socketInstance && ( + socketInstance.connectionState() === 'open' || + socketInstance.connectionState() === 'connecting' + ) + socketInstance.params.token = socketUser.token if (socketInstance.connectionState() === 'closed') { - socketInstance.params.token = socketUser.token - console.log(socketInstance.connect()) socketInstance.connect() - console.log(socketInstance) - - console.log('Phoenix Socket connected!') - } else if (!connAvaiable) { - socketInstance.connect() - console.log('PhoenixSocket reconnected!') + if (window.websocket_logs) console.log('Phoenix Socket connected') + } else if (connAvaiable) { + if (window.websocket_logs) console.log('Phoenix Socket already connected') } else { - console.warn('Trying to connect to a connected socket.') + socketInstance.connect() + if (window.websocket_logs) console.log('Phoenix Socket reconnected') } -console.log(socketInstance) Object.assign(session.user, socketUser) - socket.authenticate(socketUser.token) - NotificationStore.refresh() - NotificationStore.refreshMentionsList() + // NotificationStore.refresh() + // NotificationStore.refreshMentionsList() } export const socketLogout = socketUser => { - socket.subscriptions().forEach(channel => { - if (channel !== publicChannelKey) socket.unsubscribe(channel) - }) Object.assign(session.user, socketUser) - socket.deauthenticate() - socket.emit('loggedOut') + socketInstance.disconnect() + if (window.websocket_logs) console.log('Phoenix Socket disconnected') + + // socket.subscriptions().forEach(channel => { + // if (channel !== publicChannelKey) socket.unsubscribe(channel) + // }) + // Object.assign(session.user, socketUser) + // socket.deauthenticate() + // socket.emit('loggedOut') } export const watchPublicChannel = handler => { @@ -143,29 +140,44 @@ export default { socket.on('authenticate', () => { if (window.websocket_logs) console.log('Authenticated WebSocket Connection') - // Emit LoggedIn event to socket server - socket.emit('loggedIn') + // Emit LoggedIn event to socket server + socket.emit('loggedIn') - // subscribe to user channel - let userChannelKey = JSON.stringify({ type: 'user', id: session.user.id }) - userChannel = socket.subscribe(userChannelKey, options) + // subscribe to user channel + let userChannelKey = JSON.stringify({ type: 'user', id: session.user.id }) + userChannel = socket.subscribe(userChannelKey, options) - // subscribe to roles channels - if (session.user.roles) { - session.user.roles.forEach(role => { - let channel = JSON.stringify({ type: 'role', id: role }) - socket.subscribe(channel, options) - }) - } + // subscribe to roles channels + if (session.user.roles) { + session.user.roles.forEach(role => { + let channel = JSON.stringify({ type: 'role', id: role }) + socket.subscribe(channel, options) + }) + } }) - socket.on('connect', status => status.isAuthenticated ? socket.emit('loggedIn') : null) + // socket.on('connect', status => status.isAuthenticated ? socket.emit('loggedIn') : null) socketInstance.onOpen(() => { - socketInstance.channel('user:notifications', {}).join() - .receive("ok", resp => { console.log("Joined successfully", resp) }) - .receive("error", resp => { console.log("Unable to join", resp) }) + userChannel = socketInstance.channel('user:' + session.user.id, {}) + + userChannel.on('reauthenticate', $auth.reauthenticate) + + userChannel.on('logout', payload => { + console.log("Server Token", payload.token) + console.log("Frontend Token", session.user.token) + if (payload.token === session.user.token) { + $auth.logout() + clearUser() // Handles clearing authed user from a out of focus tab + alertStore.warn('You have been logged out from another window.') + } + }) + + userChannel.join() + .receive("ok", resp => { console.log("Joined successfully", resp) }) + .receive("error", resp => { console.log("Unable to join", resp) }) + .receive("timeout", () => console.log("Networking issue...") ) }) From 316b627acded06b64e34a34f51578bc4aaaf2cbb Mon Sep 17 00:00:00 2001 From: Anthony Kinsey Date: Thu, 27 Oct 2022 15:01:25 -1000 Subject: [PATCH 009/149] feat(logout-socket): implement working logout socket --- src/api/index.js | 2 +- src/composables/services/websocket.js | 30 +++++++++------------------ src/composables/stores/auth.js | 5 ++++- 3 files changed, 15 insertions(+), 22 deletions(-) diff --git a/src/api/index.js b/src/api/index.js index dcb8493d..4f976971 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -209,7 +209,7 @@ export const usersApi = { delete: userId => $http(`/api/users/${userId}`, { method: 'DELETE' }), deactivate: userId => $http(`/api/users/${userId}/deactivate`, { method: 'POST' }), reactivate: userId => $http(`/api/users/${userId}/reactivate`, { method: 'POST' }), - preferences: () => $http('/api/users/preferences'), + preferences: () => $http2('/api/users/preferences'), pageIgnored: params => $http('/api/ignoreUsers/ignored', { params }), notes: params => $http('/api/user/notes', { params }), createNote: data => $http('/api/user/notes', { method: 'POST', data }), diff --git a/src/composables/services/websocket.js b/src/composables/services/websocket.js index 33f54c89..2a1821fa 100644 --- a/src/composables/services/websocket.js +++ b/src/composables/services/websocket.js @@ -50,21 +50,16 @@ export const socketLogin = socketUser => { } Object.assign(session.user, socketUser) - // NotificationStore.refresh() - // NotificationStore.refreshMentionsList() + NotificationStore.refresh() + NotificationStore.refreshMentionsList() } export const socketLogout = socketUser => { - Object.assign(session.user, socketUser) - socketInstance.disconnect() - if (window.websocket_logs) console.log('Phoenix Socket disconnected') - - // socket.subscriptions().forEach(channel => { - // if (channel !== publicChannelKey) socket.unsubscribe(channel) - // }) - // Object.assign(session.user, socketUser) - // socket.deauthenticate() - // socket.emit('loggedOut') + if (socketInstance.connectionState() === 'open') { + Object.assign(session.user, socketUser) + socketInstance.disconnect() + if (window.websocket_logs) console.log('Phoenix Socket disconnected') + } } export const watchPublicChannel = handler => { @@ -158,20 +153,15 @@ export default { // socket.on('connect', status => status.isAuthenticated ? socket.emit('loggedIn') : null) - socketInstance.onOpen(() => { + userChannel = socketInstance.channel('user:' + session.user.id, {}) userChannel.on('reauthenticate', $auth.reauthenticate) userChannel.on('logout', payload => { - console.log("Server Token", payload.token) - console.log("Frontend Token", session.user.token) - if (payload.token === session.user.token) { - $auth.logout() - clearUser() // Handles clearing authed user from a out of focus tab - alertStore.warn('You have been logged out from another window.') - } + // Logout all sessions sharing the same token (usually an entire device) + if (payload.token === session.user.token) $auth.websocketLogout() }) userChannel.join() diff --git a/src/composables/stores/auth.js b/src/composables/stores/auth.js index 13ea7af6..0820dea9 100644 --- a/src/composables/stores/auth.js +++ b/src/composables/stores/auth.js @@ -61,12 +61,14 @@ export default { const logout = () => authApi.logout() .then(() => userCleanup(`Goodbye ${user.username}, you have successfully logged out!`)) + const websocketLogout = () => { if (user.token) userCleanup() } + const userCleanup = msg => { delete user.token // clear token to invalidate session immediately $appCache.delete(AUTH_KEY) $prefs.clear() BanStore.clearBanNotice() - $alertStore.warn(msg) + if (msg) $alertStore.warn(msg) // redirect to home on logout if ($route.meta.requiresAuth && $route.path !== '/') $router.push({ path: '/' }) // delay clearing reactive user to give css transitions time to complete @@ -120,6 +122,7 @@ export default { reauthenticate, login, logout, + websocketLogout, register, confirmRegistration, inviteRegistration, From 721a3917d63a4efe269afea84bfbad4b519e79c7 Mon Sep 17 00:00:00 2001 From: Anthony Kinsey Date: Fri, 28 Oct 2022 12:23:50 -1000 Subject: [PATCH 010/149] feat(phoenix-channels): functioning channel connections for reauthenticate/logout --- src/composables/services/websocket.js | 29 +++++++++++++++------------ 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/composables/services/websocket.js b/src/composables/services/websocket.js index 2a1821fa..177dc634 100644 --- a/src/composables/services/websocket.js +++ b/src/composables/services/websocket.js @@ -3,6 +3,7 @@ import NotificationStore from '@/composables/stores/notifications' import { clearUser, AuthStore } from '@/composables/stores/auth' import { provide, inject, reactive } from 'vue' import { Socket as PhoenixSocket } from 'phoenix' +import { $axios2 } from '@/api' const socketUrl = process.env.VUE_APP_BACKEND_URL.replace('http://', 'ws://') + '/socket' @@ -56,6 +57,8 @@ export const socketLogin = socketUser => { export const socketLogout = socketUser => { if (socketInstance.connectionState() === 'open') { + // Remove token from axios + delete $axios2.defaults.headers.common['Authorization'] Object.assign(session.user, socketUser) socketInstance.disconnect() if (window.websocket_logs) console.log('Phoenix Socket disconnected') @@ -154,24 +157,24 @@ export default { // socket.on('connect', status => status.isAuthenticated ? socket.emit('loggedIn') : null) socketInstance.onOpen(() => { + if (!userChannel) { + userChannel = socketInstance.channel('user:' + session.user.id, {}) - userChannel = socketInstance.channel('user:' + session.user.id, {}) + userChannel.on('reauthenticate', $auth.reauthenticate) - userChannel.on('reauthenticate', $auth.reauthenticate) - - userChannel.on('logout', payload => { - // Logout all sessions sharing the same token (usually an entire device) - if (payload.token === session.user.token) $auth.websocketLogout() - }) - - userChannel.join() - .receive("ok", resp => { console.log("Joined successfully", resp) }) - .receive("error", resp => { console.log("Unable to join", resp) }) - .receive("timeout", () => console.log("Networking issue...") ) + userChannel.on('logout', payload => { + // Logout all sessions sharing the same token (usually an entire device) + if (payload.token === session.user.token) $auth.websocketLogout() + }) + userChannel.join() + .receive("ok", resp => { console.log("Joined successfully", resp) }) + .receive("error", resp => { console.log("Unable to join", resp) }) + .receive("timeout", () => console.log("Networking issue...") ) + } }) - // always subscribe to the public channel + // // always subscribe to the public channel publicChannel = socket.subscribe(publicChannelKey, { waitForAuth: false }) /* Provide Store Data */ From 0bff455be2a253b52db833bdaf6fa58da9f6728c Mon Sep 17 00:00:00 2001 From: Anthony Kinsey Date: Mon, 31 Oct 2022 14:44:40 -1000 Subject: [PATCH 011/149] feat(notifications): swap notification api to use new server --- src/api/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/index.js b/src/api/index.js index 4f976971..bb890c29 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -266,8 +266,8 @@ export const mentionsApi = { } export const notificationsApi = { - dismiss: data => $http('/api/notifications/dismiss', { method: 'POST', data }), - counts: params => $http('/api/notifications/counts', { params }) + dismiss: data => $http2('/api/notifications/dismiss', { method: 'POST', data }), + counts: params => $http2('/api/notifications/counts', { params }) } export const breadcrumbsApi = { From 2a0b8e7de97ed46bf064f59a5eb13fc7c93a812a Mon Sep 17 00:00:00 2001 From: Anthony Kinsey Date: Tue, 8 Nov 2022 14:03:18 -1000 Subject: [PATCH 012/149] feat(mentions): page mentions from new server --- src/api/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/index.js b/src/api/index.js index bb890c29..0fa93137 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -256,7 +256,7 @@ export const reportsApi = { } export const mentionsApi = { - page: params => $http('/api/mentions', { params }), + page: params => $http2('/api/mentions', { params }), pageIgnored: params => $http('/api/mentions/ignored', { params }), ignore: data => $http(`/api/mentions/ignore`, { method: 'POST', data }), unignore: data => $http(`/api/mentions/unignore`, { method: 'POST', data }), From 9a9e9e661d90d138456099baaca15fb8a6f1f6b1 Mon Sep 17 00:00:00 2001 From: Anthony Kinsey Date: Tue, 8 Nov 2022 14:03:46 -1000 Subject: [PATCH 013/149] fix(login-issue): fix issue with login caused by socket logout --- src/composables/stores/auth.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/composables/stores/auth.js b/src/composables/stores/auth.js index 0820dea9..8399e16d 100644 --- a/src/composables/stores/auth.js +++ b/src/composables/stores/auth.js @@ -1,6 +1,6 @@ import { provide, computed, inject, reactive, readonly } from 'vue' import { cloneDeep } from 'lodash' -import { authApi } from '@/api' +import { authApi, $axios2 } from '@/api' import { PreferencesStore } from '@/composables/stores/prefs' import { socketLogout, socketLogin } from '@/composables/services/websocket' import PermissionUtils from '@/composables/utils/permissions' @@ -47,7 +47,6 @@ export default { $prefs.fetch() socketLogin(user) }).catch(() => userCleanup(`Goodbye ${user.username}, your session has expired`)) - const login = (username, password, rememberMe) => authApi.login({ username, password, rememberMe }) .then(dbUser => { $appCache.set(AUTH_KEY, dbUser) @@ -65,6 +64,7 @@ export default { const userCleanup = msg => { delete user.token // clear token to invalidate session immediately + delete $axios2.defaults.headers.common['Authorization'] // clear token from axios $appCache.delete(AUTH_KEY) $prefs.clear() BanStore.clearBanNotice() From d44c4139b30ef19dc4c1027dc7d90087088b3706 Mon Sep 17 00:00:00 2001 From: Anthony Kinsey Date: Thu, 10 Nov 2022 11:25:03 -1000 Subject: [PATCH 014/149] refactor(websockets): wip convert websockets to use phoenix websockets --- src/components/layout/Header.vue | 4 +- src/composables/services/websocket.js | 80 ++++++++++++++++----------- 2 files changed, 50 insertions(+), 34 deletions(-) diff --git a/src/components/layout/Header.vue b/src/components/layout/Header.vue index a54872e2..224004a3 100644 --- a/src/components/layout/Header.vue +++ b/src/components/layout/Header.vue @@ -275,7 +275,7 @@ import BanStore from '@/composables/stores/ban' import NotificationsStore from '@/composables/stores/notifications' import humanDate from '@/composables/filters/humanDate' import { motdApi } from '@/api' -import { watchPublicChannel } from '@/composables/services/websocket' +import { setMotdMessageHandler } from '@/composables/services/websocket' export default { components: { AdminNavigation, AdminSubNavigation, Breadcrumbs, LoginModal, InviteModal, RegisterModal, Alert }, @@ -283,7 +283,7 @@ export default { onBeforeMount(() => { let fetchMotd = () => motdApi.get().then(d => v.motdData = d).catch(() => {}) fetchMotd() - watchPublicChannel(d => d.action === 'announcement' ? fetchMotd() : null) + setMotdMessageHandler(fetchMotd) }) /* Internal Methods */ const scrollHeader = () => { diff --git a/src/composables/services/websocket.js b/src/composables/services/websocket.js index 177dc634..29ea15cf 100644 --- a/src/composables/services/websocket.js +++ b/src/composables/services/websocket.js @@ -17,7 +17,7 @@ const socketcluster = require('socketcluster-client') // Public channel idenitfier and general options let options = { waitForAuth: true } -let userChannel, userChannelNew +let userChannel, userChannelNew, roleChannel let publicChannel let session = reactive({ user: {} }) @@ -29,26 +29,14 @@ const socket = socketcluster.connect({ }) const WEBSOCKET_KEY = 'websocket' -const publicChannelKey = JSON.stringify({ type: 'public' }) export const WebsocketService = Symbol(WEBSOCKET_KEY) // API Functions export const socketLogin = socketUser => { - let connAvaiable = socketInstance && ( - socketInstance.connectionState() === 'open' || - socketInstance.connectionState() === 'connecting' - ) socketInstance.params.token = socketUser.token - if (socketInstance.connectionState() === 'closed') { - socketInstance.connect() - if (window.websocket_logs) console.log('Phoenix Socket connected') - } else if (connAvaiable) { - if (window.websocket_logs) console.log('Phoenix Socket already connected') - } else { - socketInstance.connect() - if (window.websocket_logs) console.log('Phoenix Socket reconnected') - } + socketInstance.disconnect() // disconnect + socketInstance.connect() // reconnect to retrigger onOpen event Object.assign(session.user, socketUser) NotificationStore.refresh() @@ -60,20 +48,21 @@ export const socketLogout = socketUser => { // Remove token from axios delete $axios2.defaults.headers.common['Authorization'] Object.assign(session.user, socketUser) - socketInstance.disconnect() - if (window.websocket_logs) console.log('Phoenix Socket disconnected') + delete socketInstance.params.token + socketInstance.disconnect() // disconnect + socketInstance.connect() // reconnect to retrigger onOpen event } } -export const watchPublicChannel = handler => { - if (window.websocket_logs) console.log('Watching public channel.') - if (publicChannel) publicChannel.watch(handler) - else setTimeout(() => watchPublicChannel(handler), 1000) +export const setMotdMessageHandler = handler => { + if (window.websocket_logs) console.log('Watching \'public\' channel for \'announcement\' message.') + if (publicChannel) publicChannel.on('announcement', handler) + else setTimeout(() => setMotdMessageHandler(handler), 1000) } export const watchUserChannel = handler => { if (window.websocket_logs) console.log('Watching user channel.') - if (userChannel && userChannelNew) userChannel.watch(handler) + if (userChannel) userChannel.watch(handler) else setTimeout(() => watchUserChannel(handler), 1000) } @@ -154,28 +143,55 @@ export default { } }) + socketInstance.connect() // socket.on('connect', status => status.isAuthenticated ? socket.emit('loggedIn') : null) socketInstance.onOpen(() => { - if (!userChannel) { - userChannel = socketInstance.channel('user:' + session.user.id, {}) + publicChannel = publicChannel ? publicChannel : socketInstance.channel('user:public', {}) - userChannel.on('reauthenticate', $auth.reauthenticate) + publicChannel.join() + .receive("ok", resp => { console.log("Joined 'user:public' channel successfully", resp) }) + .receive("error", resp => { console.log("Unable to join 'user:public' channel", resp) }) + .receive("timeout", () => console.log("Networking issue...")) + + if (!roleChannel && session.user.token) { + roleChannel = socketInstance.channel('user:role', {}) + + // Reauthenticate if user has role to fetch updated permissions + roleChannel.on('update', payload => { + let roles = session.user ? session.user.roles : [] + if (roles.includes(payload.lookup)) $auth.reauthenticate() + }) + + roleChannel.join() + .receive("ok", resp => { console.log("Joined 'user:role' channel successfully", resp) }) + .receive("error", resp => { console.log("Unable to join 'user:role' channel", resp) }) + .receive("timeout", () => console.log("Networking issue...")) + } + if (!userChannelNew && session.user.token) { + userChannelNew = socketInstance.channel('user:' + session.user.id, {}) + + userChannelNew.on('reauthenticate', $auth.reauthenticate) + + userChannelNew.on('newMessage', NotificationStore.refresh) + + userChannelNew.on('refreshMentions', () => { + NotificationStore.refresh() + NotificationStore.refreshMentionsList() + }) - userChannel.on('logout', payload => { + userChannelNew.on('logout', payload => { // Logout all sessions sharing the same token (usually an entire device) if (payload.token === session.user.token) $auth.websocketLogout() }) - userChannel.join() - .receive("ok", resp => { console.log("Joined successfully", resp) }) - .receive("error", resp => { console.log("Unable to join", resp) }) + userChannelNew.join() + .receive("ok", resp => { console.log("Joined 'user:" + session.user.id + "' channel successfully", resp) }) + .receive("error", resp => { console.log("Unable to join 'user:" + session.user.id + "' channel", resp) }) .receive("timeout", () => console.log("Networking issue...") ) } }) - // // always subscribe to the public channel - publicChannel = socket.subscribe(publicChannelKey, { waitForAuth: false }) /* Provide Store Data */ return provide(WebsocketService, { @@ -183,7 +199,7 @@ export default { socketLogout, watchUserChannel, unwatchUserChannel, - watchPublicChannel, + setMotdMessageHandler, isOnline }) }, From 6dd52c51711badeb95e78684b5b7e5a7c9b339cb Mon Sep 17 00:00:00 2001 From: Anthony Kinsey Date: Thu, 10 Nov 2022 15:36:47 -1000 Subject: [PATCH 015/149] refactor(websockets): wip websocket port, general sockets working, still need to implement for messages and mentions --- src/composables/services/websocket.js | 171 +++++++------------------- src/composables/stores/auth.js | 2 - 2 files changed, 46 insertions(+), 127 deletions(-) diff --git a/src/composables/services/websocket.js b/src/composables/services/websocket.js index 29ea15cf..3287e233 100644 --- a/src/composables/services/websocket.js +++ b/src/composables/services/websocket.js @@ -1,43 +1,40 @@ -import alertStore from '@/composables/stores/alert' import NotificationStore from '@/composables/stores/notifications' -import { clearUser, AuthStore } from '@/composables/stores/auth' +import { AuthStore } from '@/composables/stores/auth' import { provide, inject, reactive } from 'vue' import { Socket as PhoenixSocket } from 'phoenix' import { $axios2 } from '@/api' -const socketUrl = process.env.VUE_APP_BACKEND_URL.replace('http://', 'ws://') + '/socket' +// Variable initializations +let userChannel, roleChannel, publicChannel +let session = reactive({ user: {} }) +// Initiate the connection to the websocket server +const socketUrl = process.env.VUE_APP_BACKEND_URL.replace('http://', 'ws://') + '/socket' const socketInstance = new PhoenixSocket(socketUrl, { logger: (kind, msg, data) => { if (window.websocket_logs) console.log(`${kind}: ${msg}`, data) } }) -const socketcluster = require('socketcluster-client') - -// Public channel idenitfier and general options -let options = { waitForAuth: true } -let userChannel, userChannelNew, roleChannel -let publicChannel -let session = reactive({ user: {} }) - -// Initiate the connection to the websocket server -const socket = socketcluster.connect({ - hostname: 'localhost', - port: 23958, - autoReconnect: true -}) +// Connect to websocket server +socketInstance.connect() +// Vue Provide Symbol const WEBSOCKET_KEY = 'websocket' - export const WebsocketService = Symbol(WEBSOCKET_KEY) // API Functions export const socketLogin = socketUser => { - socketInstance.params.token = socketUser.token - socketInstance.disconnect() // disconnect - socketInstance.connect() // reconnect to retrigger onOpen event - + let reconnectWithToken = () => { + if (socketInstance.connectionState() === 'open') { + socketInstance.disconnect(() => { + socketInstance.params.token = socketUser.token + socketInstance.connect() + }, 1000, 'Disconnected to attempt authorized socket connection.') // disconnect + } + else setTimeout(() => reconnectWithToken(), 1000) + } + reconnectWithToken() Object.assign(session.user, socketUser) NotificationStore.refresh() NotificationStore.refreshMentionsList() @@ -49,7 +46,7 @@ export const socketLogout = socketUser => { delete $axios2.defaults.headers.common['Authorization'] Object.assign(session.user, socketUser) delete socketInstance.params.token - socketInstance.disconnect() // disconnect + if (socketInstance.isConnected()) socketInstance.disconnect() // disconnect socketInstance.connect() // reconnect to retrigger onOpen event } } @@ -60,139 +57,63 @@ export const setMotdMessageHandler = handler => { else setTimeout(() => setMotdMessageHandler(handler), 1000) } -export const watchUserChannel = handler => { +export const watchUserChannel = () => { if (window.websocket_logs) console.log('Watching user channel.') - if (userChannel) userChannel.watch(handler) - else setTimeout(() => watchUserChannel(handler), 1000) + // if (userChannel) userChannel.watch(handler) + // else setTimeout(() => watchUserChannel(handler), 1000) } -export const unwatchUserChannel = handler => { +export const unwatchUserChannel = () => { if (window.websocket_logs) console.log('Unwatching user channel.') - if (userChannel) userChannel.unwatch(handler) + // if (userChannel) userChannel.unwatch(handler) } -export const isOnline = (socketUser, callback) => { - if (socket.state === 'open') socket.emit('isOnline', socketUser, callback) - else setTimeout(() => isOnline(socketUser, callback), 1000) +export const isOnline = () => { // (socketUser, callback) => { + // if (socket.state === 'open') socket.emit('isOnline', socketUser, callback) + // else setTimeout(() => isOnline(socketUser, callback), 1000) } export default { setup() { const $auth = inject(AuthStore) - // Socket Error logging - socket.on('error', err => window.websocket_logs ? console.log('Websocket error:', err) : null) - - // Channel Subscribe - socket.on('subscribe', channelName => { - if (JSON.parse(channelName).type === 'role') { - socket.watch(channelName, d => { - if (window.websocket_logs) console.log('Received role channel message.', d) - $auth.reauthenticate() - }) - } - else if (JSON.parse(channelName).type === 'user') { - socket.watch(channelName, d => { - if (window.websocket_logs) console.log('Received user channel message', d) - if (d.action === 'reauthenticate') $auth.reauthenticate() - else if (d.action === 'logout' && d.sessionId === socket.getAuthToken().sessionId) { - $auth.logout() - clearUser() // Handles clearing authed user from a out of focus tab - alertStore.warn('You have been logged out from another window.') - } - else if (d.action === 'newMessage') { NotificationStore.refresh() } - else if (d.action === 'refreshMentions') { - NotificationStore.refresh() - NotificationStore.refreshMentionsList() - } - }) - } - else if (JSON.parse(channelName).type === 'public') { - // Placeholder for future public notifications if necessary - } - else window.websocket_logs ? console.log('Not watching', channelName) : null - - if (window.websocket_logs) console.log('Websocket subscribed to', channelName, 'with watchers', socket.watchers(channelName)) - }) - - // Channel Unsubscribe - socket.on('unsubscribe', channelName => { - if (window.websocket_logs) console.log('Websocket unsubscribed from', channelName, socket.watchers(channelName)) - - // disconnect all watchers from the channel - socket.unwatch(channelName) - }) - - // Socket Authentication - socket.on('authenticate', () => { - if (window.websocket_logs) console.log('Authenticated WebSocket Connection') - - // Emit LoggedIn event to socket server - socket.emit('loggedIn') - - // subscribe to user channel - let userChannelKey = JSON.stringify({ type: 'user', id: session.user.id }) - userChannel = socket.subscribe(userChannelKey, options) - - // subscribe to roles channels - if (session.user.roles) { - session.user.roles.forEach(role => { - let channel = JSON.stringify({ type: 'role', id: role }) - socket.subscribe(channel, options) - }) - } - }) - - socketInstance.connect() - - // socket.on('connect', status => status.isAuthenticated ? socket.emit('loggedIn') : null) socketInstance.onOpen(() => { - publicChannel = publicChannel ? publicChannel : socketInstance.channel('user:public', {}) - + // Join Public Channel + if (publicChannel) publicChannel.leave() // leave if already connected + publicChannel = socketInstance.channel('user:public') publicChannel.join() - .receive("ok", resp => { console.log("Joined 'user:public' channel successfully", resp) }) - .receive("error", resp => { console.log("Unable to join 'user:public' channel", resp) }) - .receive("timeout", () => console.log("Networking issue...")) - if (!roleChannel && session.user.token) { - roleChannel = socketInstance.channel('user:role', {}) + // Authenticated Channels + if (socketInstance.params.token) { + // Join Role Channel + if (roleChannel) roleChannel.leave() // leave if already connected + roleChannel = socketInstance.channel('user:role') + roleChannel.join() - // Reauthenticate if user has role to fetch updated permissions roleChannel.on('update', payload => { let roles = session.user ? session.user.roles : [] + // Reauthenticate if user has role, so updated permissions are fetched if (roles.includes(payload.lookup)) $auth.reauthenticate() }) - roleChannel.join() - .receive("ok", resp => { console.log("Joined 'user:role' channel successfully", resp) }) - .receive("error", resp => { console.log("Unable to join 'user:role' channel", resp) }) - .receive("timeout", () => console.log("Networking issue...")) - } - if (!userChannelNew && session.user.token) { - userChannelNew = socketInstance.channel('user:' + session.user.id, {}) - - userChannelNew.on('reauthenticate', $auth.reauthenticate) - - userChannelNew.on('newMessage', NotificationStore.refresh) + // Join User Channel + if (userChannel) userChannel.leave() // leave if already connected + userChannel = socketInstance.channel('user:' + session.user.id) + userChannel.join() - userChannelNew.on('refreshMentions', () => { + userChannel.on('reauthenticate', $auth.reauthenticate) + userChannel.on('newMessage', NotificationStore.refresh) + userChannel.on('refreshMentions', () => { NotificationStore.refresh() NotificationStore.refreshMentionsList() }) - - userChannelNew.on('logout', payload => { + userChannel.on('logout', payload => { // Logout all sessions sharing the same token (usually an entire device) if (payload.token === session.user.token) $auth.websocketLogout() }) - - userChannelNew.join() - .receive("ok", resp => { console.log("Joined 'user:" + session.user.id + "' channel successfully", resp) }) - .receive("error", resp => { console.log("Unable to join 'user:" + session.user.id + "' channel", resp) }) - .receive("timeout", () => console.log("Networking issue...") ) } }) - /* Provide Store Data */ return provide(WebsocketService, { socketLogin, diff --git a/src/composables/stores/auth.js b/src/composables/stores/auth.js index 8399e16d..9fe78563 100644 --- a/src/composables/stores/auth.js +++ b/src/composables/stores/auth.js @@ -27,8 +27,6 @@ export const AuthStore = Symbol(AUTH_KEY) export const localStorageAuth = () => appCache.get(AUTH_KEY) || { data: emtpyUser } -export const clearUser = () => Object.assign(user, cloneDeep(emtpyUser)) - export default { setup() { /* Internal Data */ From cc4731ea3e8b57895fb335f186337e4fe7e84e17 Mon Sep 17 00:00:00 2001 From: Anthony Kinsey Date: Mon, 14 Nov 2022 13:09:10 -1000 Subject: [PATCH 016/149] feat(websockets): use phoenix for mentions, motd and messages. --- src/components/layout/Header.vue | 4 +- src/composables/services/websocket.js | 55 ++++++++++++++++++++------- src/views/Mentions.vue | 10 ++--- src/views/Messages.vue | 11 +++--- 4 files changed, 53 insertions(+), 27 deletions(-) diff --git a/src/components/layout/Header.vue b/src/components/layout/Header.vue index 224004a3..66b9b60a 100644 --- a/src/components/layout/Header.vue +++ b/src/components/layout/Header.vue @@ -275,7 +275,7 @@ import BanStore from '@/composables/stores/ban' import NotificationsStore from '@/composables/stores/notifications' import humanDate from '@/composables/filters/humanDate' import { motdApi } from '@/api' -import { setMotdMessageHandler } from '@/composables/services/websocket' +import { addAnnouncementListener } from '@/composables/services/websocket' export default { components: { AdminNavigation, AdminSubNavigation, Breadcrumbs, LoginModal, InviteModal, RegisterModal, Alert }, @@ -283,7 +283,7 @@ export default { onBeforeMount(() => { let fetchMotd = () => motdApi.get().then(d => v.motdData = d).catch(() => {}) fetchMotd() - setMotdMessageHandler(fetchMotd) + addAnnouncementListener(fetchMotd) }) /* Internal Methods */ const scrollHeader = () => { diff --git a/src/composables/services/websocket.js b/src/composables/services/websocket.js index 3287e233..c84bf6cb 100644 --- a/src/composables/services/websocket.js +++ b/src/composables/services/websocket.js @@ -51,21 +51,48 @@ export const socketLogout = socketUser => { } } -export const setMotdMessageHandler = handler => { - if (window.websocket_logs) console.log('Watching \'public\' channel for \'announcement\' message.') +export const addAnnouncementListener = handler => { + if (window.websocket_logs) console.log('Listening on \'public\' channel for \'announcement\' message.') if (publicChannel) publicChannel.on('announcement', handler) - else setTimeout(() => setMotdMessageHandler(handler), 1000) + else setTimeout(() => addAnnouncementListener(handler), 1000) } -export const watchUserChannel = () => { - if (window.websocket_logs) console.log('Watching user channel.') - // if (userChannel) userChannel.watch(handler) - // else setTimeout(() => watchUserChannel(handler), 1000) +export const addMentionListener = handler => { + if (window.websocket_logs) console.log('Listening on \'user:' + session.user.id + '\' channel for \'refreshMentions\' message.') + if (userChannel) userChannel.on('refreshMentions', handler) + else setTimeout(() => addMentionListener(handler), 1000) } -export const unwatchUserChannel = () => { - if (window.websocket_logs) console.log('Unwatching user channel.') - // if (userChannel) userChannel.unwatch(handler) +// NOTE: channel.on(msg, handler) is supposed to return a ref that +// can be used to turn off a specific handler, but it doesn't. +// This is a work around: turn off all handlers, reset default handler +export const removeMentionListener = () => { + if (window.websocket_logs) console.log('No longer listening on \'user:' + session.user.id + '\' channel for \'refreshMentions\' message.') + if (userChannel) { + // turn off all channel handlers for this message + userChannel.off('refreshMentions') + // re-add default message handler for this channel + userChannel.on('refreshMentions', () => { + NotificationStore.refresh() + NotificationStore.refreshMentionsList() + }) + } +} + +export const addMessageListener = handler => { + if (window.websocket_logs) console.log('Listening on \'user:' + session.user.id + '\' channel for \'newMessage\' message.') + if (userChannel) userChannel.on('newMessage', handler) + else setTimeout(() => addMessageListener(handler), 1000) +} + +export const removeMessageListener = () => { + if (window.websocket_logs) console.log('No longer listening on \'user:' + session.user.id + '\' channel for \'newMessage\' message.') + if (userChannel) { + // turn off all channel handlers for this message + userChannel.off('newMessage') + // re-add default message handler for this channel + userChannel.on('newMessage', NotificationStore.refresh) + } } export const isOnline = () => { // (socketUser, callback) => { @@ -118,9 +145,11 @@ export default { return provide(WebsocketService, { socketLogin, socketLogout, - watchUserChannel, - unwatchUserChannel, - setMotdMessageHandler, + addAnnouncementListener, + addMentionListener, + addMessageListener, + removeMentionListener, + removeMessageListener, isOnline }) }, diff --git a/src/views/Mentions.vue b/src/views/Mentions.vue index 9bc39ea4..dcaf3c3e 100644 --- a/src/views/Mentions.vue +++ b/src/views/Mentions.vue @@ -95,7 +95,7 @@ import { localStoragePrefs } from '@/composables/stores/prefs' import humanDate from '@/composables/filters/humanDate' import { useRoute, useRouter } from 'vue-router' import NotificationsStore from '@/composables/stores/notifications' -import { watchUserChannel, unwatchUserChannel } from '@/composables/services/websocket' +import { addMentionListener, removeMentionListener } from '@/composables/services/websocket' export default { name: 'Mentions', @@ -117,7 +117,7 @@ export default { next() }, beforeRouteLeave(to, from, next) { - unwatchUserChannel(this.userChannelHandler) + removeMentionListener() next() }, setup() { @@ -149,11 +149,9 @@ export default { }) // Websocket Handling - const userChannelHandler = data => data.action === 'refreshMentions' ? refreshMentions() : null + addMentionListener(refreshMentions) - watchUserChannel(userChannelHandler) - - return { ...toRefs(v), humanDate, pageResults, dismissNotifications, deleteMention, userChannelHandler } + return { ...toRefs(v), humanDate, pageResults, dismissNotifications, deleteMention } } } diff --git a/src/views/Messages.vue b/src/views/Messages.vue index 2606c33e..f5ee082a 100644 --- a/src/views/Messages.vue +++ b/src/views/Messages.vue @@ -163,7 +163,7 @@ import DeleteMessageModal from '@/components/modals/messages/DeleteMessage.vue' import ReportMessageModal from '@/components/modals/messages/ReportMessage.vue' import Editor from '@/components/layout/Editor.vue' // import { avatarHighlight, usernameHighlight, userRoleHighlight } from '@/composables/utils/userUtils' -import { watchUserChannel, unwatchUserChannel } from '@/composables/services/websocket' +import { addMessageListener, removeMessageListener } from '@/composables/services/websocket' export default { name: 'Messages', @@ -198,7 +198,7 @@ export default { next() }, beforeRouteLeave(to, from, next) { - unwatchUserChannel(this.userChannelHandler) + removeMessageListener() next() }, setup() { @@ -372,11 +372,10 @@ export default { } }) - const userChannelHandler = data => data.action === 'newMessage' ? loadConversation(v.selectedConversationId) : null + const newMessageHandler = () => v.selectedConversationId ? loadConversation(v.selectedConversationId) : null + addMessageListener(newMessageHandler) - watchUserChannel(userChannelHandler) - - return { ...toRefs(v), reload, createMessage, createConversation, loadRecentMessages, preloadConversation, loadConversation, loadMoreMessages, canDeleteConversation, canDeleteMessage, addQuote, canCreateConversation, canCreateMessage, deleteMessageSuccess, listMessageReceivers, humanDate, userChannelHandler } + return { ...toRefs(v), reload, createMessage, createConversation, loadRecentMessages, preloadConversation, loadConversation, loadMoreMessages, canDeleteConversation, canDeleteMessage, addQuote, canCreateConversation, canCreateMessage, deleteMessageSuccess, listMessageReceivers, humanDate } } } From b22d0e9cdcfad79c3e98cd931e587c8e6f335e0e Mon Sep 17 00:00:00 2001 From: Anthony Kinsey Date: Tue, 15 Nov 2022 17:17:07 -1000 Subject: [PATCH 017/149] feat(websockets): port isOnline to phoenix channels --- src/composables/services/websocket.js | 42 +++++++++++++++------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/src/composables/services/websocket.js b/src/composables/services/websocket.js index c84bf6cb..26574c8a 100644 --- a/src/composables/services/websocket.js +++ b/src/composables/services/websocket.js @@ -8,16 +8,17 @@ import { $axios2 } from '@/api' let userChannel, roleChannel, publicChannel let session = reactive({ user: {} }) +window.websocket_logs = true // Initiate the connection to the websocket server const socketUrl = process.env.VUE_APP_BACKEND_URL.replace('http://', 'ws://') + '/socket' -const socketInstance = new PhoenixSocket(socketUrl, { +const socket = new PhoenixSocket(socketUrl, { logger: (kind, msg, data) => { if (window.websocket_logs) console.log(`${kind}: ${msg}`, data) } }) // Connect to websocket server -socketInstance.connect() +socket.connect() // Vue Provide Symbol const WEBSOCKET_KEY = 'websocket' @@ -26,10 +27,10 @@ export const WebsocketService = Symbol(WEBSOCKET_KEY) // API Functions export const socketLogin = socketUser => { let reconnectWithToken = () => { - if (socketInstance.connectionState() === 'open') { - socketInstance.disconnect(() => { - socketInstance.params.token = socketUser.token - socketInstance.connect() + if (socket.connectionState() === 'open') { + socket.disconnect(() => { + socket.params.token = socketUser.token + socket.connect() }, 1000, 'Disconnected to attempt authorized socket connection.') // disconnect } else setTimeout(() => reconnectWithToken(), 1000) @@ -41,13 +42,13 @@ export const socketLogin = socketUser => { } export const socketLogout = socketUser => { - if (socketInstance.connectionState() === 'open') { + if (socket.connectionState() === 'open') { // Remove token from axios delete $axios2.defaults.headers.common['Authorization'] Object.assign(session.user, socketUser) - delete socketInstance.params.token - if (socketInstance.isConnected()) socketInstance.disconnect() // disconnect - socketInstance.connect() // reconnect to retrigger onOpen event + delete socket.params.token + if (socket.isConnected()) socket.disconnect() // disconnect + socket.connect() // reconnect to retrigger onOpen event } } @@ -95,26 +96,31 @@ export const removeMessageListener = () => { } } -export const isOnline = () => { // (socketUser, callback) => { - // if (socket.state === 'open') socket.emit('isOnline', socketUser, callback) - // else setTimeout(() => isOnline(socketUser, callback), 1000) +export const isOnline = (userId, callback) => { + if (socket.connectionState() === 'open') { + publicChannel.push("is_online", { user_id: userId }) + .receive("ok", payload => callback(undefined, payload)) + .receive("error", err => callback(err)) + .receive("timeout", () => callback("Websocket request to check if user(" + userId + ") is online, timed out")) + } + else setTimeout(() => isOnline(userId, callback), 1000) } export default { setup() { const $auth = inject(AuthStore) - socketInstance.onOpen(() => { + socket.onOpen(() => { // Join Public Channel if (publicChannel) publicChannel.leave() // leave if already connected - publicChannel = socketInstance.channel('user:public') + publicChannel = socket.channel('user:public') publicChannel.join() // Authenticated Channels - if (socketInstance.params.token) { + if (socket.params.token) { // Join Role Channel if (roleChannel) roleChannel.leave() // leave if already connected - roleChannel = socketInstance.channel('user:role') + roleChannel = socket.channel('user:role') roleChannel.join() roleChannel.on('update', payload => { @@ -125,7 +131,7 @@ export default { // Join User Channel if (userChannel) userChannel.leave() // leave if already connected - userChannel = socketInstance.channel('user:' + session.user.id) + userChannel = socket.channel('user:' + session.user.id) userChannel.join() userChannel.on('reauthenticate', $auth.reauthenticate) From 2f9c7db7b152735708ab484b3cf71e11f06c6b0b Mon Sep 17 00:00:00 2001 From: Anthony Kinsey Date: Thu, 17 Nov 2022 10:26:40 -1000 Subject: [PATCH 018/149] refactor(websockets): change role channel message to permissionsChanged --- src/composables/services/websocket.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/composables/services/websocket.js b/src/composables/services/websocket.js index 26574c8a..ebe4b0ac 100644 --- a/src/composables/services/websocket.js +++ b/src/composables/services/websocket.js @@ -123,7 +123,7 @@ export default { roleChannel = socket.channel('user:role') roleChannel.join() - roleChannel.on('update', payload => { + roleChannel.on('permissionsChanged', payload => { let roles = session.user ? session.user.roles : [] // Reauthenticate if user has role, so updated permissions are fetched if (roles.includes(payload.lookup)) $auth.reauthenticate() From 4d4b286aaab554809b2c6991535333672c7ae1bd Mon Sep 17 00:00:00 2001 From: Anthony Kinsey Date: Fri, 18 Nov 2022 11:19:19 -1000 Subject: [PATCH 019/149] refactor(websockets): clean up websocket_log variable --- src/composables/services/websocket.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/composables/services/websocket.js b/src/composables/services/websocket.js index ebe4b0ac..c841fe57 100644 --- a/src/composables/services/websocket.js +++ b/src/composables/services/websocket.js @@ -8,7 +8,6 @@ import { $axios2 } from '@/api' let userChannel, roleChannel, publicChannel let session = reactive({ user: {} }) -window.websocket_logs = true // Initiate the connection to the websocket server const socketUrl = process.env.VUE_APP_BACKEND_URL.replace('http://', 'ws://') + '/socket' const socket = new PhoenixSocket(socketUrl, { From 322831798da6b72e668a4d1a66048113999136d1 Mon Sep 17 00:00:00 2001 From: Anthony Kinsey Date: Thu, 9 Feb 2023 11:27:30 -1000 Subject: [PATCH 020/149] feat(get-boards): use elixir api route to fetch boards for boards view, todo: split recent threads into separate call --- src/api/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/index.js b/src/api/index.js index 0fa93137..3c334702 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -104,7 +104,7 @@ export const categoriesApi = { export const boardsApi = { slugToBoardId: slug => $http(`/api/boards/${slug}/id`), - getBoards: stripped => $http(`/api/boards${stripped ? '?stripped=true' : ''}`), + getBoards: stripped => $http2(`/api/boards${stripped ? '?stripped=true' : ''}`), movelist: () => $http('/api/boards/movelist'), unfiltered: () => $http('/api/boards/unfiltered'), uncategorized: () => $http('/api/boards/uncategorized'), From 3e2cbf41f249a9ea4602efce22e8abea128b845d Mon Sep 17 00:00:00 2001 From: Anthony Kinsey Date: Tue, 11 Apr 2023 10:10:01 -1000 Subject: [PATCH 021/149] feat(by-board): port by board and slug routes to use new elixir server --- src/api/index.js | 4 ++-- yarn.lock | 13 ++++--------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/api/index.js b/src/api/index.js index 3c334702..4e103f8f 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -103,7 +103,7 @@ export const categoriesApi = { } export const boardsApi = { - slugToBoardId: slug => $http(`/api/boards/${slug}/id`), + slugToBoardId: slug => $http2(`/api/boards/${slug}/id`), getBoards: stripped => $http2(`/api/boards${stripped ? '?stripped=true' : ''}`), movelist: () => $http('/api/boards/movelist'), unfiltered: () => $http('/api/boards/unfiltered'), @@ -124,7 +124,7 @@ export const threadsApi = { unlock: threadId => $http(`/api/threads/${threadId}/lock`, { data: { status: false }, method: 'POST'}), sticky: threadId => $http(`/api/threads/${threadId}/sticky`, { data: { status: true}, method: 'POST'}), unsticky: threadId => $http(`/api/threads/${threadId}/sticky`, { data: { status: false }, method: 'POST'}), - byBoard: params => $http('/api/threads', { params }), + byBoard: params => $http2('/api/threads', { params }), postedIn: params => $http('/api/threads/posted', { params }), slugToThreadId: slug => $http(`/api/threads/${slug}/id`), notifications: () => $http('api/threadnotifications'), diff --git a/yarn.lock b/yarn.lock index 321a25a7..dcc230f6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2354,15 +2354,10 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001370: - version "1.0.30001373" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001373.tgz#2dc3bc3bfcb5d5a929bec11300883040d7b4b4be" - integrity sha512-pJYArGHrPp3TUqQzFYRmP/lwJlj8RCbVe3Gd3eJQkAV8SAC6b19XS9BjMvRdvaS8RMkaTN8ZhoHP6S1y8zzwEQ== - -caniuse-lite@^1.0.30001400, caniuse-lite@^1.0.30001407: - version "1.0.30001421" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001421.tgz#979993aaacff5ab72a8d0d58c28ddbcb7b4deba6" - integrity sha512-Sw4eLbgUJAEhjLs1Fa+mk45sidp1wRn5y6GtDpHGBaNJ9OCDJaVh2tIaWWUnGfuXfKf1JCBaIarak3FkVAvEeA== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001370, caniuse-lite@^1.0.30001400, caniuse-lite@^1.0.30001407: + version "1.0.30001456" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001456.tgz" + integrity sha512-XFHJY5dUgmpMV25UqaD4kVq2LsiaU5rS8fb0f17pCoXQiQslzmFgnfOxfvo1bTpTqf7dwG/N/05CnLCnOEKmzA== case-sensitive-paths-webpack-plugin@^2.3.0: version "2.4.0" From 31157f25353747d8f878832026f00c25c9cb6dc7 Mon Sep 17 00:00:00 2001 From: Chris Rodrigues Date: Fri, 2 Jun 2023 12:48:59 -0700 Subject: [PATCH 022/149] feat(update-dependencies): Updated all vue dependencies that needed updates; Renamed Header component to HeaderComponent; Removed v-html attribute from router-link into its own span element --- package.json | 44 +- src/App.vue | 6 +- src/components/layout/AdminSubNavigation.vue | 4 +- .../{Header.vue => HeaderComponent.vue} | 0 src/components/users/UserPosts.vue | 8 +- src/views/Boards.vue | 4 +- src/views/MemberSearch.vue | 4 +- src/views/Messages.vue | 4 +- src/views/Patrol.vue | 14 +- src/views/PostSearch.vue | 6 +- src/views/Posts.vue | 4 +- src/views/Threads.vue | 22 +- src/views/ThreadsPostedIn.vue | 8 +- src/views/Watchlist.vue | 4 +- src/views/WatchlistEdit.vue | 8 +- src/views/admin/management/Users.vue | 4 +- src/views/admin/moderation/BoardBans.vue | 8 +- src/views/admin/moderation/Logs.vue | 4 +- src/views/admin/moderation/Messages.vue | 20 +- src/views/admin/moderation/Posts.vue | 28 +- src/views/admin/moderation/Users.vue | 20 +- yarn.lock | 2840 +++++++++-------- 22 files changed, 1612 insertions(+), 1452 deletions(-) rename src/components/layout/{Header.vue => HeaderComponent.vue} (100%) diff --git a/package.json b/package.json index adfe7512..697bd670 100644 --- a/package.json +++ b/package.json @@ -19,34 +19,34 @@ "url": "git://github.com/epochtalk/epochtalk-vue" }, "dependencies": { - "@fortawesome/fontawesome-free": "^5.15.1", - "@vueform/multiselect": "^1.2.5", - "axios": "^0.21.0", - "core-js": "^3.6.5", - "dayjs": "^1.9.6", - "emittery": "^0.10.1", - "jquery": "^3.6.0", + "@fortawesome/fontawesome-free": "^6.4.0", + "@vueform/multiselect": "^2.6.2", + "axios": "^1.4.0", + "core-js": "^3.30.2", + "dayjs": "^1.11.8", + "emittery": "^1.0.1", + "jquery": "^3.7.0", "nestable": "git+https://github.com/slickage/Nestable.git", "normalize.css": "^8.0.1", "nprogress": "^0.2.0", - "phoenix": "^1.3.5", - "sass": "^1.55.0", - "slugify": "^1.6.1", - "socketcluster-client": "^14.3.2", - "swrv": "^1.0.0-beta.5", - "vue": "^3.0.0", - "vue-router": "^4.0.6", + "phoenix": "^1.7.3", + "sass": "^1.62.1", + "slugify": "^1.6.6", + "socketcluster-client": "^17.1.1", + "swrv": "^1.0.3", + "vue": "^3.3.4", + "vue-router": "^4.2.2", "vuedraggable": "^4.1.0" }, "devDependencies": { + "@babel/eslint-parser": "^7.21.8", "@vue/cli-plugin-babel": "~5.0.8", "@vue/cli-plugin-eslint": "~5.0.8", "@vue/cli-service": "~5.0.8", - "@vue/compiler-sfc": "^3.0.0", - "babel-eslint": "^10.1.0", - "eslint": "^7.27.0", - "eslint-plugin-vue": "^7.0.0-0", - "sass-loader": "^10" + "@vue/compiler-sfc": "^3.3.4", + "eslint": "^8.41.0", + "eslint-plugin-vue": "^9.14.1", + "sass-loader": "^13.3.1" }, "eslintConfig": { "root": true, @@ -58,9 +58,11 @@ "eslint:recommended" ], "parserOptions": { - "parser": "babel-eslint" + "parser": "@babel/eslint-parser" }, - "rules": {} + "rules": { + "vue/multi-word-component-names": "off" + } }, "browserslist": [ "> 1%", diff --git a/src/App.vue b/src/App.vue index a5d95d91..6f90975d 100644 --- a/src/App.vue +++ b/src/App.vue @@ -4,7 +4,7 @@
    -
    +
    @@ -18,7 +18,7 @@ From 449ccb0bdd62dc9c989a6acf3b0d5bd9341d95ec Mon Sep 17 00:00:00 2001 From: Anthony Kinsey Date: Thu, 2 May 2024 10:57:40 -1000 Subject: [PATCH 078/149] fix(post-timestamp): correct timezone error, todo pull user preference from database --- src/components/users/UserPosts.vue | 5 +---- src/composables/filters/humanDate.js | 24 ++++++++++++++++-------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/components/users/UserPosts.vue b/src/components/users/UserPosts.vue index 622460b5..ffd8df05 100644 --- a/src/components/users/UserPosts.vue +++ b/src/components/users/UserPosts.vue @@ -245,12 +245,9 @@ export default { .post-body { white-space: pre-wrap; &.open { background-color: darken($base-background-color, 5%); } - ul { + ul, ol { white-space: normal; } - ol { - white-space: initial; - } &.closed { width: 100%; max-height: 18px; diff --git a/src/composables/filters/humanDate.js b/src/composables/filters/humanDate.js index b830574d..b2ab4afd 100644 --- a/src/composables/filters/humanDate.js +++ b/src/composables/filters/humanDate.js @@ -1,4 +1,7 @@ import * as dayjs from 'dayjs' +import timezone from 'dayjs/plugin/timezone' + +dayjs.extend(timezone) export default (dateStr, hideTime, customFormat) => { if (!dateStr) return dateStr @@ -11,22 +14,27 @@ export default (dateStr, hideTime, customFormat) => { let isToday = now.toDateString() === date.toDateString() let isThisYear = now.getYear() === date.getYear() let isMaxDate = maxDate.getTime() === date.getTime() + + // TODO(akinsey): load users timezone setting from saved preferences + // otherwise guess timzone. + let convertedDate = dayjs.utc(dateStr).tz(dayjs.tz.guess()) + if (hideTime) { if (isToday) { result = 'Today' } else if (isMaxDate) { result = 'Permanent' } // bans - else { result = dayjs(dateStr).format('MMM D, YYYY') } + else { result = convertedDate.format('MMM D, YYYY') } } - else if (customFormat) result = dayjs(dateStr).format(customFormat) + else if (customFormat) result = convertedDate.format(customFormat) else { if (timezone) { - if (isToday) { result = 'Today at ' + dayjs(dateStr).format('h:mm A').tz(timezone) } - else if (isThisYear) { result = dayjs(dateStr).format('MMMM D [at] h:mm A').tz(timezone) } - else { result = dayjs(dateStr).format('MMM D, YYYY [at] h:mm A').tz(timezone) } + if (isToday) { result = 'Today at ' + convertedDate.format('h:mm A').tz(timezone) } + else if (isThisYear) { result = convertedDate.format('MMMM D [at] h:mm A').tz(timezone) } + else { result = convertedDate.format('MMM D, YYYY [at] h:mm A').tz(timezone) } } else { - if (isToday) { result = 'Today at ' + dayjs(dateStr).format('h:mm A') } - else if (isThisYear) { result = dayjs(dateStr).format('MMMM D [at] h:mm A') } - else { result = dayjs(dateStr).format('MMM D, YYYY [at] h:mm A') } + if (isToday) { result = 'Today at ' + convertedDate.format('h:mm A') } + else if (isThisYear) { result = convertedDate.format('MMMM D [at] h:mm A') } + else { result = convertedDate.format('MMM D, YYYY [at] h:mm A') } } } return result From e0916feee17f92c540ed979aa21e929d3a4d6590 Mon Sep 17 00:00:00 2001 From: Anthony Kinsey Date: Mon, 6 May 2024 13:48:53 -1000 Subject: [PATCH 079/149] fix(preview): css fix for previewing post --- src/components/layout/Editor.vue | 32 ++++++++++++++------------------ src/views/Posts.vue | 2 +- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/components/layout/Editor.vue b/src/components/layout/Editor.vue index bfaa0e09..4078f910 100644 --- a/src/components/layout/Editor.vue +++ b/src/components/layout/Editor.vue @@ -168,12 +168,12 @@
    -
    +