-
-
Save unr/345fe5c9daf7e5daffa1f23842d2c9ab to your computer and use it in GitHub Desktop.
| // In my global app layout, once my app is mounted and ready to listen... | |
| import { mapActions, mapGetters } from 'vuex'; | |
| const Layout = { | |
| mounted() { | |
| // set up pusher listeners | |
| this.connectToPublicChannel(); | |
| this.$watch('authenticated', (auth) => { | |
| if (auth) this.connectToUserChannel(); | |
| else this.leaveUserChannel(); | |
| }, { immediate: true }); | |
| // maintains window width, for use app-wide | |
| this.updateWindowWidth(window.innerWidth); | |
| window.addEventListener('resize', throttle(() => { | |
| this.updateWindowWidth(window.innerWidth); | |
| }), 400); | |
| }, | |
| beforeDestroy() { | |
| // leaves channels, so next layout will mount fresh channels. | |
| this.leaveAllChannels(); | |
| }, | |
| computed: { | |
| ...mapGetters({ | |
| authenticated: 'user/authenticated', | |
| }), | |
| }, | |
| methods: { | |
| ...mapActions({ | |
| connectToPublicChannel: 'connectToPublicChannel', | |
| connectToUserChannel: 'connectToUserChannel', | |
| leaveAllChannels: 'leaveAllChannels', | |
| leaveUserChannel: 'leaveUserChannel', | |
| }), | |
| }, | |
| }; | |
| export default Layout; |
| /** | |
| * Echo Module | |
| * Sets up listeners for pusher channels, and delegates their responses. | |
| */ | |
| import Vue from 'vue'; | |
| // eslint-disable-next-line | |
| import Pusher from 'pusher-js'; | |
| import Echo from 'laravel-echo'; | |
| const EchoModule = { | |
| state() { | |
| return { | |
| public: false, | |
| private: false, | |
| }; | |
| }, | |
| mutations: { | |
| setPublic(state, value) { | |
| Vue.set(state, 'public', value); | |
| }, | |
| setPrivate(state, value) { | |
| Vue.set(state, 'private', value); | |
| }, | |
| }, | |
| actions: { | |
| // Creates a public echo instance, for casual updates | |
| createPublic({ commit, state }) { | |
| return new Promise((resolve, reject) => { | |
| if (state.public) return resolve(true); | |
| this.$axios.$get('refresh-csrf').then(() => { | |
| const instance = new Echo({ | |
| broadcaster: 'pusher', | |
| key: process.env.pusherAppKey, | |
| cluster: process.env.pusherCluster, | |
| encrypted: true, | |
| }); | |
| commit('setPublic', instance); | |
| return resolve(true); | |
| }) | |
| .catch(err => reject(err)); | |
| }); | |
| }, | |
| // Creates a private cookie-based instance, for user-updates | |
| createPrivate({ commit, state }) { | |
| return new Promise((resolve, reject) => { | |
| if (state.private) return resolve(true); | |
| const cookie = Cookie.parse(document.cookie); | |
| this.$axios.$get('refresh-csrf').then(() => { | |
| const instance = new Echo({ | |
| broadcaster: 'pusher', | |
| key: process.env.pusherAppKey, | |
| cluster: process.env.pusherCluster, | |
| encrypted: true, | |
| auth: { | |
| // Echo is NOT using app.$axios -- so it needs to read the XSRF TOKEN itself | |
| headers: { 'X-XSRF-TOKEN': cookie['XSRF-TOKEN'] }, | |
| }, | |
| }); | |
| commit('setPrivate', instance); | |
| return resolve(true); | |
| }) | |
| .catch(err => reject(err)); | |
| }); | |
| }, | |
| // Connects the current user to the public echo channel | |
| connectToPublicChannel({ dispatch, state }) { | |
| dispatch('createPublic').then(() => { | |
| // handle public match updates | |
| state.public.channel('matches') | |
| .listen('BettingStatusChanged', notification => dispatch('handleBettingStatusChanged', notification)) | |
| .listen('MatchOutcomeOddsChanged', notification => dispatch('handleMatchOutcomeOddsChanged', notification)) | |
| .listen('MatchScheduledAtChanged', notification => dispatch('handleMatchScheduledAtChanged', notification)) | |
| .listen('MatchMarketsStatusChanged', notification => dispatch('handleMatchMarketsStatusChanged', notification)) | |
| .listen('MatchStatusChanged', notification => dispatch('handleMatchStatusChanged', notification)); | |
| }) | |
| .catch((err) => { | |
| if (process.client) { | |
| this.$raven.captureMessage('failed to connect to matches channel', { | |
| extra: { state, error: err }, | |
| }); | |
| } | |
| }); | |
| }, | |
| // Connects the current user to the Rivalry Live echo channel | |
| connectToLiveChannel({ dispatch, state }) { | |
| dispatch('createPublic').then(() => { | |
| // handle live match score updates | |
| state.public.channel('live') | |
| .listen('MatchScoreChanged', notification => dispatch('handleMatchScoreChanged', notification)); | |
| }) | |
| .catch((err) => { | |
| if (process.client) { | |
| this.$raven.captureMessage('failed to connect to Live channel', { | |
| extra: { state, error: err }, | |
| }); | |
| } | |
| }); | |
| }, | |
| // Connects the current user to the private echo channel | |
| connectToUserChannel({ dispatch, getters, state }) { | |
| const uuid = getters['user/uuid']; | |
| dispatch('createPrivate').then(() => { | |
| state.private.private(`User.${uuid}`) | |
| .notification((notification) => { | |
| // User's balance was updated | |
| if (notification.type.includes('BalanceUpdateNotification')) dispatch('handleBalanceUpdateNotification', notification); | |
| }); | |
| }) | |
| .catch((err) => { | |
| if (process.client) { | |
| this.$raven.captureMessage('failed to connect to user channel', { | |
| extra: { state, uuid, error: err }, | |
| }); | |
| } | |
| }); | |
| }, | |
| // Leave all channels currently active. | |
| // When a Layout is being unmounted, user will leave all channels. | |
| // When layout is rendered again, they will join appropriate channels. | |
| leaveAllChannels({ state }) { | |
| if (state.public) { | |
| const channels = Object.keys(state.public.connector.channels); | |
| each(channels, channel => state.public.leave(channel)); | |
| } | |
| if (state.private) { | |
| const channels = Object.keys(state.private.connector.channels); | |
| each(channels, channel => state.private.leave(channel)); | |
| } | |
| }, | |
| // Leave User Channel | |
| // If our user logs out, we need to leave the currently active User channel. | |
| leaveUserChannel({ state }) { | |
| if (!state.private) return; | |
| const channels = Object.keys(state.private.connector.channels); | |
| // look for the private.User channel, and leave it if it exists | |
| const userChannel = find(channels, channel => channel.includes('private-User')); | |
| state.private.leave(userChannel); | |
| }, | |
| // Leave Live Channel when user leaves the Rivalry Live page | |
| leaveLiveChannel({ state }) { | |
| if (!state.public) return; | |
| const channels = Object.keys(state.public.connector.channels); | |
| // look for the live channel, and leave it if it exists | |
| const liveChannel = find(channels, ['live']); | |
| state.public.leave(liveChannel); | |
| }, | |
| // User balance updated | |
| handleBalanceUpdateNotification({ dispatch }, notification) { | |
| dispatch('user/setBalance', notification.balance); | |
| }, | |
| handleMatchScoreChanged({ dispatch }, notification) { | |
| dispatch('matches/updateMatchScores', notification); | |
| }, | |
| }, | |
| }; | |
| export default EchoModule; |
Also worth noting - now that I've split my Nuxt.js app from my Laravel API (separate domains), all the Echo.js magic seems lost.
Going to basically write this exact same thing, but mimicing https://github.com/jaggy/vue-pusher/blob/master/index.js instead.
No real benefit of echo from laravel, when it's not hosted by laravel anymore.
@unr How do you work around the "do not mutate vuex store state outside mutation handlers." errors?
@unr How do you work around the "do not mutate vuex store state outside mutation handlers." errors?
you should try to Object.assign({}, ...) it
I haven't had those errors while working with something like this. This shouldn't be changing state directly.
The only state manipulation here should be done in mutations fine. Maybe something I just didn't have in my setup?
not working
Food for thought... Thanks...