Vuejs create a websocket connection in only one vue component - vue.js

I want to instantiate a websocket connection with the server, only in one particular component. I'm using Vuecli, socket.io, socket.io-client and vue-socket.io
Googling around I've been able to instantiate a global connection and then use it like in the following example:
/main.js
[...]
import socketio from 'socket.io-client';
import VueSocketIO from 'vue-socket.io';
[...]
export const SocketInstance = socketio('http://localhost:8080');
Vue.use( new VueSocketIO( {"debug":true, "connection":SocketInstance }));
and in my Comenponent.vue I can use this.$socket. to refer to the websocket instance.
<template>
.....
</template>
<script>
export default {
data(){
return { .... }
},
methods:{
...
// works fine
ping(){ this.$socket.emit('pingServer','hey') }
...
},
// to listen for messages from the server i'm using:
sockets: {
message(data){ console.log(data); },
serverResp(data){ console.log(data); },
}
...
}
</script>
To have the websocket in a single component I've tried the following:
/Component.vue
<template>
.....
</template>
<script>
//import lib
import socketio from 'socket.io-client';
import VueSocketIO from 'vue-socket.io';
export default {
data(){
return {
socket: null,
...
}
},
created(){
this.s = socketio('http://localhost:8080');
this.socket = new VueSocketIO( {"debug":true, "connection":s });
....
},
methods: {
// emit data does work
ping(){ this.socket.emit('pingServer','hey') }
},
// not available anymore
sockets:{
message(data){}
}
}
</script>
Per state of the above code, I can send data to server with this.sock.emit() but I can't figure out how to listen for the message coming from server.
Thanks in advance for any help.
github link of the project: https://github.com/anatolieGhebea/simpleDocBuilder
the component is under /frontend/src/views/Editor.vue

In your created() method (I'm not sure which is using the socket.io client), but you can do
//example msg event
this.s.on("msg", (data) => { console.log("joined", data); }
I implemented something similar, though I used a mixin, but you can readily transfer this to a single component. An excerpt of my code (on the client-side, I'm just using the npm library 'socket.io-client') from here )
const io = require("socket.io-client")
export default{
data() {
return {
socket: undefined
}
},//end data
created() {
let chatUrl = "http://localhost:3000";
this.socket = io(chatUrl, {
//force websockets only - it's optional
transports: ["websocket"]
});
//socket io events
this.socket.on("join", data => {
console.log("joined ", data);
});
},//end created
methods: {
//e.g. sending a chat message
send_chat: function(message) {
this.socket.emit("chat", message);
},
},//end methods
}

Related

Socket.io with Vue3

I have a Vue 3 app and an express server. The server does not serve any pages just acts as an API so no socket.io/socket.io.js file is sent to client.
I am trying to set up socket.io in one of my vue components but whatever I try does not work. Using vue-3-socket.io keeps giving 't.prototype is undefined' errors.
I have tried vue-socket.io-extended as well with no luck.
Any advice would be appreciated as to the reason and solution for the error above, I have tried various SO solutions without success, and the best way forward.
You can use socket.io-client. I have used socket.io-client of 4.4.1 version.
step: 1
Write class inside src/services/SocketioService.js which returns an instance of socketio.
import {io} from 'socket.io-client';
class SocketioService {
socket;
constructor() { }
setupSocketConnection() {
this.socket = io(URL, {
transports: ["websocket"]
})
return this.socket;
}
}
export default new SocketioService();
Step 2:
Import SocketioService in App.vue. You can instantiate in any lifecycle hook of vue. I have instantiated on mounted as below. After instantiation, I am listening to welcome and notifications events and used quasar notify.
<script>
import { ref } from "vue";
import SocketioService from "./services/socketio.service.js";
export default {
name: "LayoutDefault",
data() {
return {
socket: null,
};
},
components: {},
mounted() {
const socket = SocketioService.setupSocketConnection();
socket.on("welcome", (data) => {
const res = JSON.parse(data);
if (res?.data == "Connected") {
this.$q.notify({
type: "positive",
message: `Welcome`,
classes: "glossy",
});
}
});
socket.on("notifications", (data) => {
const res = JSON.parse(data);
let type = res?.variant == "error" ? "negative" : "positive";
this.$q.notify({
type: type,
message: res?.message,
position: "bottom-right",
});
});
},
};
</script>

How to get the this instance in vue 3?

In vue 2+ I can easily get the instance of this as a result I can write something like this,
// main.js
app.use(ElMessage)
// home.vue
this.$message({
showClose: true,
message: 'Success Message',
type: 'success',
})
What should I do for vue 3 as,
Inside setup(), this won't be a reference to the current active
instance Since setup() is called before other component options are
resolved, this inside setup() will behave quite differently from this
in other options. This might cause confusions when using setup() along
other Options API. - vue 3 doc.
Using ElMessage directly
ElementPlus supports using ElMessage the same way as $message(), as seen in this example:
import { ElMessage } from 'element-plus'
export default {
setup() {
const open1 = () => {
ElMessage('this is a message.')
}
const open2 = () => {
ElMessage({
message: 'Congrats, this is a success message.',
type: 'success',
})
}
return {
open1,
open2,
}
}
}
Using $message()
Vue 3 provides getCurrentInstance() (an internal API) inside the setup() hook. That instance allows access to global properties (installed from plugins) via appContext.config.globalProperties:
import { getCurrentInstance } from "vue";
export default {
setup() {
const globals = getCurrentInstance().appContext.config.globalProperties;
return {
sayHi() {
globals.$message({ message: "hello world" });
},
};
},
};
demo
Note: Being an internal API, getCurrentInstance() could potentially be removed/renamed in a future release. Use with caution.
Providing a different method where the idea is to set a globally scoped variable to the _component property of the viewmodel/app or component:
pageVM = Vue.createApp({
data: function () {
return {
renderComponent: true,
envInfo: [],
dependencies: [],
userGroups: []
}
},
mounted: function () {
//Vue version 3 made it harder to access the viewmodel's properties.
pageVM_props = pageVM._component;
this.init();
},

VueJS - function in import js file not getting triggered

We are building a web application using Vue JS and PHP, we are new to Vue JS. The server-side execution is fine, the API is able to fetch data as JSON. While trying out a static array display before making the API call, we find that the function in imported "app.js" is not getting called and the table displayed is empty. Please let us know what we might be doing wrong. Appreciate your help.
import Vue from 'vue';
export const MY_CONST = 'Vue.js';
export let memberList = new Vue({
el: '#members',
data: {
members: []
},
mounted: function () {
this.getAllMembers();
},
methods: {
getAllMembers: function () {
/*
axios.get("https://xxxxxx.com/services/api.php")
.then(function (response) {
memberList.members = response.data.members;
});
*/
memberList.members = [{ "empname": "Dinesh Dassss" },
{ "empname": "Kapil Koranne" }];
}
}
});
This is the Vue component. The members object is empty.
<script>
import * as mykey from './app.js'
export default {
name: 'Home',
props: {
msg: String
},
data() {
return {
message: `Hello ${mykey.MY_CONST}!`,
members: mykey.memberList.members
}
}
};
</script>
You can also use this reference for current instance reference:
getAllMembers: function () {
var me = this;
/*
axios.get("https://xxxxxx.com/services/api.php")
.then(function (response) {
// direct this not works here but we have
//saved this in another variable and scope of a var is there
me.members = response.data.members;
});
*/
// this reference works fine here.
this.members = [{ "empname": "Dinesh Dassss" },
{ "empname": "Kapil Koranne" }];
}

vue-apollo awsappsync - refresh credentials

I am using vue-apollo with AWSAppSyncClient. I have followed this documentation for Vue - https://github.com/awslabs/aws-mobile-appsync-sdk-js . My requirement is user should be able to subscribe to appsync. Here is the main.js code.
import './bootstrap';
import router from './routes';
import store from './store';
import App from './components/templates/App';
import AWSAppSyncClient from 'aws-appsync';
import VueApollo from "vue-apollo";
const config = {
url: process.env.MIX_APPSYNC_URL,
region: process.env.MIX_APPSYNC_REGION,
auth: {
type: process.env.MIX_APPSYNC_TYPE,
credentials: {
accessKeyId: "temporary access key goes here",
secretAccessKey: "temporary secret access key goes here",
sessionToken: "session token goes here"
}
},
};
I get the 'credentials' part after user logged in successfully with aws cognito validation.
const options = {
defaultOptions: {
watchQuery: {
fetchPolicy: 'cache-and-network',
}
}
}
// Create the apollo client
const apolloClient = new AWSAppSyncClient(config, options);
//The provider holds the Apollo client instances that can then be used by all the child components.
const apolloProvider = new VueApollo({
defaultClient: apolloClient,
});
var vm = new Vue({
el:"#dashboardapp",
router:router,
apolloProvider:apolloProvider,
store:store,
components: { App },
template: '<App/>',
data() {
return {
}
},
});
The above set up works fine. User can login. After cognito verifies user, it sends temporary credentials (accesskey, secret key, session token). With temporary credentials I am able to subscribe to aws appsync through vue-apollo. However, the credentials are valid for 1 hour only. So I need to update the credentials and keep the subscription part to get live data. But I dont know how to do it. I have gone through the docs, but not able to find anything specific to my case.
I need to update the credentials from either a child component of 'vm' or from vuex store. I already have updated credentials. I just dont know how to pass it to the 'AWSAppSyncClient' and how to re-subscribe with updated credentials. Also I don't want to reload the page. It should update credentials without reloading the page. Hope anyone would have done this before ?
I have done few changes to my code and now I am able to achieve what I wanted. Here are the changes I have done, in case anyone doing same thing.
First loading the apollo client as blank - means without awsappsyncclient in main.js file.
import './bootstrap';
import router from './routes';
import store from './store';
import App from './components/templates/App';
import VueApollo from "vue-apollo";
// Create the apollo client
const apolloClient = '';
//The provider holds the Apollo client instances that can then be used by all the child components.
const apolloProvider = new VueApollo({
defaultClient: apolloClient,
});
var vm = new Vue({
el:"#dashboardapp",
router:router,
apolloProvider:apolloProvider,
store:store,
components: { App },
template: '<App/>',
data() {
return {
}
},
});
Then from child component I am creating smart subscription. Once temporary credentials are expired, I am generating new credentials and updating in vuex store. Based on the change, I am stooping the old smart subscription and creating a new smart subscription.
Here is the child component code.
<template>
<div class="status-frame">
<!-- relevant code goes here -->
</div>
</template>
<script>
import gql from 'graphql-tag';
import AWSAppSyncClient from 'aws-appsync';
import VueApollo from "vue-apollo";
export default {
data () {
return {
}
},
methods: {
timelineSubscribe() {
if(this.$parent.$apolloProvider.clients[1]) {
delete this.$parent.$apolloProvider.clients[1];
this.$apollo.subscriptions.subscribeToNewNotification.stop();
}
const config = {
url: process.env.MIX_APPSYNC_URL,
region: process.env.MIX_APPSYNC_REGION,
auth: {
type: process.env.MIX_APPSYNC_TYPE,
credentials: {
accessKeyId: this.appsyncObj.accessKeyId,
secretAccessKey: this.appsyncObj.secretAccessKey,
sessionToken: this.appsyncObj.sessionToken
}
},
};
const options = {
defaultOptions: {
watchQuery: {
fetchPolicy: 'cache-and-network',
}
}
}
// Create the apollo client
const apolloClient = new AWSAppSyncClient(config, options);
// add apolloClient to a new index in apolloProvider.
this.$parent.$apolloProvider.clients[1] = apolloClient;
console.log(this.$apollo.provider.clients);
this.$apollo.addSmartSubscription('subscribeToAnything', {
client: '1',
query: gql`subscription subscribeToAnything ($accountId: String!) {
subscribeToAnything(accountId: $accountId) {
// required fields goes here
}
}`,
// Reactive variables
variables() {
// This works just like regular queries
// and will re-subscribe with the right variables
// each time the values change
return {
accountId: 'account_id goes here',
}
},
// Result hook
result(data) {
console.log(data);
},
skip () {
return false;
}
});
}
},
computed: {
appsyncObj() {
return this.$store.getters['profile/appsyncObj']; // get from vuex store
}
},
watch: {
'appsyncObj' () {
this.timelineSubscribe(); // each time appsyncObj changes, it will call this method and resubscribe with new credentials.
}
},
}
I update the vuex store for appsyncObj after login and after getting new credentials. However, I have not added that code here.

Vue-apollo doesn't populate data object

I am experimenting using vue-apollo with nuxt by implementing the #nuxtjs/apollo module. I have a working GraphQL server running on localhost:4000. I wrote the following code :
<template>
<div>
<p v-for = "item in stuff" :key="item.id">item.name</p>
</div>
</template>
<script>
import stuff from '~/apollo/queries/stuff'
export default {
apollo: {
stuff: {
query: stuff,
variables: {
limit: 10
}
}
},
data () {
return {
stuff: []
}
}
}
</script>
stuff.gql :
{
stuff {
id
name
}
}
client-config :
import { ApolloLink } from 'apollo-link'
import { HttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
export default (ctx) => {
const httpLink = new HttpLink({ uri: 'http://localhost:4000' })
// middleware
const middlewareLink = new ApolloLink((operation, forward) => {
const token = process.server ? ctx.req.session : window.__NUXT__.state.session
operation.setContext({
headers: { authorization: `Bearer ${token}` }
})
return forward(operation)
})
const link = middlewareLink.concat(httpLink)
return {
link,
cache: new InMemoryCache()
}
}
The observant reader will see that I basically copied the example code from the docs. What I expected to happen was that the data object of my vue component would get updated with the first 10 results of stuff from my backend. However, I see everything in an $apolloData object which is not accessible from the component. Also, the data is not limited to the first 10 entries. Could someone point out what I am doing wrong? Because I don't see it.
I also tried :
apollo: {
products: {
query: stuff,
variables () {
return {
limit: 10
}
}
}
}
And with all variations on the prefetch option.
OK, so I installed a fresh version of the nuxt starter template today and migrated only the essentials to get apollo working. It worked immediately. I have no clue what was causing the error and due to the fact that I already had a dozen packages installed we probably will never know.