Skip to content

Instantly share code, notes, and snippets.

@unr
Created October 16, 2018 21:12
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save unr/345fe5c9daf7e5daffa1f23842d2c9ab to your computer and use it in GitHub Desktop.
Save unr/345fe5c9daf7e5daffa1f23842d2c9ab to your computer and use it in GitHub Desktop.
Bare Minimum Laravel Echo in Nuxt.JS example.
// 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;
@rameezcubet
Copy link

Food for thought... Thanks...

@unr
Copy link
Author

unr commented May 24, 2019

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.

@xtrasmal
Copy link

xtrasmal commented Jun 2, 2019

@unr How do you work around the "do not mutate vuex store state outside mutation handlers." errors?

@gabfr
Copy link

gabfr commented Jul 4, 2019

@unr How do you work around the "do not mutate vuex store state outside mutation handlers." errors?

you should try to Object.assign({}, ...) it

@unr
Copy link
Author

unr commented Jul 4, 2019

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?

@dimer22zhc
Copy link

dimer22zhc commented Sep 1, 2019

not working

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment