Replace django-channels with `useWebSocket` from `@vueuse/core` (!1759)
This commit is contained in:
parent
688685a9cf
commit
f21c860985
|
@ -0,0 +1 @@
|
|||
Fix CSP issue caused by django-channels package (#1752)
|
|
@ -0,0 +1 @@
|
|||
Replace django-channels package with web socket implementation from @vueuse/core (#1715)
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue