Replace django-channels with `useWebSocket` from `@vueuse/core` (!1759)

environments/review-docs-devel-1399dq/deployments/11160
Kasper Seweryn 2022-04-16 08:56:26 +00:00 zatwierdzone przez JuniorJPDJ
rodzic 688685a9cf
commit f21c860985
9 zmienionych plików z 64 dodań i 125 usunięć

Wyświetl plik

@ -0,0 +1 @@
Fix CSP issue caused by django-channels package (#1752)

Wyświetl plik

@ -0,0 +1 @@
Replace django-channels package with web socket implementation from @vueuse/core (#1715)

Wyświetl plik

@ -17,10 +17,11 @@
"postinstall": "yarn run fix-fomantic-css"
},
"dependencies": {
"@vue/composition-api": "1.4.9",
"@vueuse/core": "8.2.5",
"axios": "0.26.1",
"axios-auth-refresh": "3.2.2",
"diff": "5.0.0",
"django-channels": "2.1.3",
"focus-trap": "6.7.3",
"fomantic-ui-css": "2.8.8",
"howler": "2.2.3",

Wyświetl plik

@ -50,7 +50,7 @@
import axios from 'axios'
import _ from 'lodash'
import { mapState, mapGetters } from 'vuex'
import { WebSocketBridge } from 'django-channels'
import { useWebSocket, whenever } from '@vueuse/core'
import GlobalEvents from '@/components/utils/global-events.vue'
import locales from './locales'
import { getClientOnlyRadio } from '@/radios'
@ -65,6 +65,7 @@ import SetInstanceModal from '@/components/SetInstanceModal.vue'
import ShortcutsModal from '@/components/ShortcutsModal.vue'
import FilterModal from '@/components/moderation/FilterModal.vue'
import ReportModal from '@/components/moderation/ReportModal.vue'
import { watch, watchEffect } from '@vue/composition-api'
export default {
name: 'App',
@ -81,9 +82,32 @@ export default {
ReportModal,
GlobalEvents
},
setup (props, { root }) {
const store = root.$store
const url = store.getters['instance/absoluteUrl']('api/v1/activity')
.replace(/^http/, 'ws')
const { data, status, open, close } = useWebSocket(url, {
autoReconnect: true,
immediate: false
})
watch(() => store.state.auth.authenticated, (authenticated) => {
if (authenticated) return open()
close()
})
whenever(data, () => {
store.dispatch('ui/websocketEvent', JSON.parse(data.value))
})
watchEffect(() => {
console.log('Websocket status:', status.value)
})
},
data () {
return {
bridge: null,
instanceUrl: null,
showShortcutsModal: false,
showSetInstanceModal: false,
@ -172,13 +196,6 @@ export default {
this.setTheme(newValue)
}
},
'$store.state.auth.authenticated' (newValue) {
if (!newValue) {
this.disconnect()
} else {
this.openWebsocket()
}
},
'$store.state.ui.currentLanguage': {
immediate: true,
handler (newValue) {
@ -248,7 +265,6 @@ export default {
}
window.addEventListener('resize', this.handleResize)
this.handleResize()
this.openWebsocket()
const self = this
if (!this.$store.state.ui.selectedLanguage) {
this.autodetectLanguage()
@ -264,7 +280,7 @@ export default {
}
const url = urlParams.get('_url')
if (url) {
this.$router.replace(url)
await this.$router.replace(url)
} else if (!this.$store.state.instance.instanceUrl) {
// we have several way to guess the API server url. By order of precedence:
// 1. use the url provided in settings.json, if any
@ -279,11 +295,6 @@ export default {
this.$store.commit('instance/instanceUrl', this.$store.state.instance.instanceUrl)
}
await this.fetchNodeInfo()
this.$store.dispatch('auth/check')
setInterval(() => {
// used to refresh profile every now and then (important for refreshing scoped tokens)
self.$store.dispatch('auth/check')
}, 1000 * 60 * 60 * 8)
this.$store.dispatch('instance/fetchSettings')
this.$store.commit('ui/addWebsocketEventHandler', {
eventName: 'inbox.item_added',
@ -354,7 +365,6 @@ export default {
eventName: 'Listen',
id: 'handleListen'
})
this.disconnect()
},
methods: {
incrementNotificationCountInSidebar (event) {
@ -400,36 +410,6 @@ export default {
}
this.$store.commit('ui/currentLanguage', candidate)
},
disconnect () {
if (!this.bridge) {
return
}
this.bridge.socket.close(1000, 'goodbye', { keepClosed: true })
},
openWebsocket () {
if (!this.$store.state.auth.authenticated) {
return
}
this.disconnect()
const self = this
const token = this.$store.state.auth.token
const bridge = new WebSocketBridge()
this.bridge = bridge
let url =
this.$store.getters['instance/absoluteUrl'](`api/v1/activity?token=${token}`)
url = url.replace('http://', 'ws://')
url = url.replace('https://', 'wss://')
bridge.connect(
url,
[],
{ reconnectInterval: 1000 * 60 })
bridge.addEventListener('message', function (event) {
self.$store.dispatch('ui/websocketEvent', event.data)
})
bridge.socket.addEventListener('open', function () {
console.log('Connected to WebSocket')
})
},
getTrackInformationText (track) {
const trackTitle = track.title
const albumArtist = (track.album) ? track.album.artist.name : null

Wyświetl plik

@ -14,6 +14,7 @@ import GetTextPlugin from 'vue-gettext'
import { sync } from 'vuex-router-sync'
import locales from '@/locales'
import createAuthRefreshInterceptor from 'axios-auth-refresh'
import VueCompositionAPI from '@vue/composition-api'
import filters from '@/filters' // eslint-disable-line
import { parseAPIErrors } from '@/utils'
@ -57,6 +58,7 @@ Vue.use(GetTextPlugin, {
silent: true
})
Vue.use(VueCompositionAPI)
Vue.use(VueLazyload)
Vue.directive('title', function (el, binding) {
store.commit('ui/pageTitle', binding.value)

Wyświetl plik

@ -54,7 +54,6 @@ export default {
moderation: false
},
profile: null,
token: '',
oauth: getDefaultOauth(),
scopedTokens: getDefaultScopedTokens()
},
@ -71,7 +70,6 @@ export default {
state.profile = null
state.username = ''
state.fullUsername = ''
state.token = ''
state.scopedTokens = getDefaultScopedTokens()
state.oauth = getDefaultOauth()
state.availablePermissions = {
@ -89,7 +87,6 @@ export default {
if (value === false) {
state.username = null
state.fullUsername = null
state.token = null
state.profile = null
state.scopedTokens = getDefaultScopedTokens()
state.availablePermissions = {}
@ -106,9 +103,6 @@ export default {
state.profile.avatar = value
}
},
token: (state, value) => {
state.token = value
},
scopedTokens: (state, value) => {
state.scopedTokens = { ...value }
},
@ -138,7 +132,6 @@ export default {
})
return axios.post('users/login', form).then(response => {
logger.default.info('Successfully logged in as', credentials.username)
// commit('token', response.data.token)
dispatch('fetchProfile').then(() => {
// Redirect to a specified route
import('@/router').then((router) => {
@ -169,16 +162,6 @@ export default {
})
logger.default.info('Log out, goodbye!')
},
async check ({ commit, dispatch, state }) {
logger.default.info('Checking authentication…')
commit('authenticated', false)
const profile = await dispatch('fetchProfile')
if (profile) {
commit('authenticated', true)
} else {
logger.default.info('Anonymous user')
}
},
fetchProfile ({ commit, dispatch, state }) {
return new Promise((resolve, reject) => {
axios.get('users/me/').then((response) => {

Wyświetl plik

@ -37,28 +37,15 @@ describe('store/auth', () => {
it('authenticated false', () => {
const state = {
username: 'dummy',
token: 'dummy',
profile: 'dummy',
availablePermissions: 'dummy'
}
store.mutations.authenticated(state, false)
expect(state.authenticated).to.equal(false)
expect(state.username).to.equal(null)
expect(state.token).to.equal(null)
expect(state.profile).to.equal(null)
expect(state.availablePermissions).to.deep.equal({})
})
it('token null', () => {
const state = {}
store.mutations.token(state, null)
expect(state.token).to.equal(null)
})
it('token real', () => {
const state = {}
let token = 'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJodHRwczovL2p3dC1pZHAuZXhhbXBsZS5jb20iLCJzdWIiOiJtYWlsdG86bWlrZUBleGFtcGxlLmNvbSIsIm5iZiI6MTUxNTUzMzQyOSwiZXhwIjoxNTE1NTM3MDI5LCJpYXQiOjE1MTU1MzM0MjksImp0aSI6ImlkMTIzNDU2IiwidHlwIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9yZWdpc3RlciJ9.'
store.mutations.token(state, token)
expect(state.token).to.equal(token)
})
it('permissions', () => {
const state = { availablePermissions: {} }
store.mutations.permission(state, {key: 'admin', status: true})
@ -86,19 +73,6 @@ describe('store/auth', () => {
]
})
})
it('check jwt null', () => {
testAction({
action: store.actions.check,
params: {state: {}},
expectedMutations: [
{ type: 'authenticated', payload: false },
{ type: 'authenticated', payload: true },
],
expectedActions: [
{ type: 'fetchProfile' },
]
})
})
it('login success', () => {
moxios.stubRequest('token/', {
status: 200,

Wyświetl plik

@ -17,14 +17,6 @@ export default defineConfig({
}
}
},
{
name: 'fix-django-channels',
transform (src, id) {
if (id.includes('django-channels')) {
return `var parcelRequire;${src}`
}
}
}
],
server: {
port: process.env.VUE_PORT || '8080',

Wyświetl plik

@ -1009,13 +1009,6 @@
"@babel/types" "^7.4.4"
esutils "^2.0.2"
"@babel/runtime@^7.5.5":
version "7.17.7"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.7.tgz#a5f3328dc41ff39d803f311cfe17703418cf9825"
integrity sha512-L6rvG9GDxaLgFjg41K+5Yv9OMrU98sWe+Ykmc6FDJW/+vYZMhdOMKkISgzptMaERHvS2Y2lw9MDRm2gHhlQQoA==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.8.4":
version "7.17.8"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.8.tgz#3e56e4aff81befa55ac3ac6a0967349fd1c5bca2"
@ -1603,6 +1596,11 @@
optionalDependencies:
prettier "^1.18.2 || ^2.0.0"
"@vue/composition-api@^1.4.9":
version "1.4.9"
resolved "https://registry.yarnpkg.com/@vue/composition-api/-/composition-api-1.4.9.tgz#6fa65284f545887b52d421f23b4fa1c41bc0ad4b"
integrity sha512-l6YOeg5LEXmfPqyxAnBaCv1FMRw0OGKJ4m6nOWRm6ngt5TuHcj5ZoBRN+LXh3J0u6Ur3C4VA+RiKT+M0eItr/g==
"@vue/reactivity-transform@3.2.31":
version "3.2.31"
resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.31.tgz#0f5b25c24e70edab2b613d5305c465b50fc00911"
@ -1628,6 +1626,27 @@
lodash "^4.17.15"
pretty "^2.0.0"
"@vueuse/core@^8.2.5":
version "8.2.5"
resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-8.2.5.tgz#ca6a59091ecf16e6739c53f3d857b11967a5eb06"
integrity sha512-5prZAA1Ji2ltwNUnzreu6WIXYqHYP/9U2BiY5mD/650VYLpVcwVlYznJDFcLCmEWI3o3Vd34oS1FUf+6Mh68GQ==
dependencies:
"@vueuse/metadata" "8.2.5"
"@vueuse/shared" "8.2.5"
vue-demi "*"
"@vueuse/metadata@8.2.5":
version "8.2.5"
resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-8.2.5.tgz#51c7d95e04284ea378a5242a2e88b77494e2c117"
integrity sha512-Lk9plJjh9cIdiRdcj16dau+2LANxIdFCiTgdfzwYXbflxq0QnMBeOD2qHgKDE7fuVrtPcVWj8VSuZEx1HRfNQA==
"@vueuse/shared@8.2.5":
version "8.2.5"
resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-8.2.5.tgz#1ae200a240c4b8d42d41723b64d8f917aa57ff16"
integrity sha512-lNWo+7sk6JCuOj4AiYM+6HZ6fq4xAuVq1sVckMQKgfCJZpZRe4i8es+ZULO5bYTKP+VrOCtqrLR2GzEfrbr3YQ==
dependencies:
vue-demi "*"
abab@^2.0.3, abab@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a"
@ -2572,15 +2591,6 @@ diff@5.0.0, diff@^5.0.0:
resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b"
integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==
django-channels@2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/django-channels/-/django-channels-2.1.3.tgz#4d175b9d8553f3e2b1263b75de0b4f23fc9acac3"
integrity sha512-H0jzdw3XvBR3HC4FqMqhoM0M4iUlOJ1TCRpyL51r//8LX2KGAK7lA1+4JeTxvnlaq8xBvZ3mMTf55eaRoDcgKA==
dependencies:
"@babel/runtime" "^7.5.5"
event-target-shim "^5.0.1"
reconnecting-websocket "^4.1.10"
doctrine@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d"
@ -3121,11 +3131,6 @@ esutils@^2.0.2:
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
event-target-shim@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
execa@^5.0.0:
version "5.1.1"
resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
@ -5136,11 +5141,6 @@ readdirp@~3.6.0:
dependencies:
picomatch "^2.2.1"
reconnecting-websocket@^4.1.10:
version "4.4.0"
resolved "https://registry.yarnpkg.com/reconnecting-websocket/-/reconnecting-websocket-4.4.0.tgz#3b0e5b96ef119e78a03135865b8bb0af1b948783"
integrity sha512-D2E33ceRPga0NvTDhJmphEgJ7FUYF0v4lr1ki0csq06OdlxKfugGzN0dSkxM/NfqCxYELK4KcaTOUOjTV6Dcng==
regenerate-unicode-properties@^10.0.1:
version "10.0.1"
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz#7f442732aa7934a3740c779bb9b3340dccc1fb56"
@ -5849,6 +5849,11 @@ void-elements@^3.1.0:
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
integrity sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=
vue-demi@*:
version "0.12.5"
resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.12.5.tgz#8eeed566a7d86eb090209a11723f887d28aeb2d1"
integrity sha512-BREuTgTYlUr0zw0EZn3hnhC3I6gPWv+Kwh4MCih6QcAeaTlaIX0DwOVN0wHej7hSvDPecz4jygy/idsgKfW58Q==
vue-eslint-parser@^7.10.0:
version "7.11.0"
resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-7.11.0.tgz#214b5dea961007fcffb2ee65b8912307628d0daf"