Signaling OpenTok and React - javascript

Has anyone implemented sending and receiving signals with opentok-react https://github.com/aiham/opentok-react? I can't find even a simple example on how to do it in React using opentok-react.

Thanks for using opentok-react. Unfortunately an easy way to do signaling hasn't yet been added to opentok-react so the following process is a bit convoluted.
To do signaling you will need to get access to the Session object and call the signal method on it as you normally would (See https://tokbox.com/developer/sdks/js/reference/Session.html#signal).
If you used the OTSession component you can access the Session object by getting a reference to the OTSession element (See https://reactjs.org/docs/refs-and-the-dom.html).
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.otSession = React.createRef();
}
render() {
return <OTSession ref={this.otSession} />;
}
}
and using its sessionHelper property to call the signal method:
this.otSession.current.sessionHelper.session.signal(...);
If you want to specify a particular target connection for the recipient then you need to get it from the underlying Publisher or Subscriber object's stream property. First get a reference to the OTPublisher or OTSubscriber element :
<OTPublisher ref={this.otPublisher} />
// or
<OTSubscriber ref={this.otSubscriber} />
Then get access to the Connection object:
this.otPublisher.current.getPublisher().stream.connection
// or
this.otSubscriber.current.getSubscriber().stream.connection
I have not tested this but once you have access to the Session and Connection objects then you can use the full signaling capabilities of the OpenTok JS SDK.

I Created a npm package 'opentok-rvc' with reference to opentok-react.
Here i created a listener to watch signaling and if any signal gets i send the signal to another event
// signal message listener inside npm package
session.on('signal:msg', function signalCallback(event) {
console.log(event);
onSignalRecieve(event);
});
Inside your component, please do the following
// to send the opentok signal
// here param data can be object for eg:
// data = { type: 'START_VIDEO_CALL', data: 'By Alex' }
onSignalSend = (data) => {
if (this.otSession.current !== null) {
this.otSession.current.sessionHelper.session.signal({
type: 'msg',
data: data
}, function signalCallback(error) {
if (error) {
console.log('onSignalSend Error', error)
} else {
console.log('onSignalSend Success', data)
}
})
}
}
// to receive the opentok signal
onSignalReceive = (signal) => {
console.log('onSignalReceive => ', JSON.parse(signal.data));
// based on signal data type you can do use switch or conditional statements
}
<OTSession
ref={this.otSession}
apiKey={apiKey}
sessionId={sessionId}
token={token}
onError={this.onSessionError}
eventHandlers={this.sessionEventHandlers}
onSignalRecieve={this.onSignalReceive}
getDevices={this.setDevices}
onMediaDevices={this.onMediaDevices}
checkScreenSharing={this.checkScreenSharing}>
<OTPublisher properties/>
<OTStreams>
<OTSubscriber properties/>
</OTStreams>

Here's a way to do this using functional component syntax.
import React, { useRef } from 'react';
import { OTSession, preloadScript } from 'opentok-react';
function MyComponent() {
const sessionRef = useRef();
const sendSignal = () => {
sessionRef.current.sessionHelper.session.signal(
{
type: 'TheSignalType',
data: 'TheData',
},
function (error) {
if (error) {
console.log('signal error: ' + error.message);
} else {
console.log('signal sent');
}
}
);
};
return (
<OTSession ref={sessionRef} apiKey={apiKey} sessionId={sessionId} token={token} eventHandlers={eventHandlers}>
// rest of your tokbox code here
<Button onClick={sendSignal}>Send Signal</Button>
</OTSession>
);
}
export default preloadScript(MyComponent);

In addition to #aiham answer, You can access the Opentok session Object getting the ref from the OTSession element and then send signals like below
otSession.current.sessionHelper.session.signal(
{
type: "msg",
data: text,
},
function(err, data) {
if (err) {
console.log(err.message);
} else {
console.log(data)
}
}
);
And signals can be received by adding a listener as follows;
otSession.current.sessionHelper.session.on("signal", (event) => {
console.log("i got", event);
});

Related

Return asynchronous call through Web Components (MVC)

I am building an application with pure javascript and Web Components. I also want to use the MVC Pattern, but now I have a problem with asynchronous calls from the model.
I am developing a meal-list component. The data is coming from an API as JSON in the following format:
[
{
id: 1,
name: "Burger",
},
]
I want the controller to get the data from the model and send it to the view.
meals.js (Model)
export default {
get all() {
const url = 'http://localhost:8080/meals';
let speisekarte = [];
fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
}).then(res => {
return res.json()
}).then(data => {
// This prints the result I want to use, but I can't return
console.log(data);
// This does not work
speisekarte = data;
// This also does not work
return data;
});
// is undefined.
return speisekarte;
},
}
This is how I tried to get the data from an API.
meal-list.component.js (Controller)
import Template from './meal-list.template.js'
import Meal from '../../../../data/meal.js'
export default class MealListComponent extends HTMLElement {
connectedCallback() {
this.attachShadow({mode: 'open'});
// Should send the Data from the model to the View
this.shadowRoot.innerHTML = Template.render(Meal.all);
}
}
if (!customElements.get('mp-meal-list')) {
customElements.define('mp-meal-list', MealListComponent);
}
meal-list.template.js (View)
export default {
render(meals) {
return `${this.html(meals)}`;
},
html(meals) {
let content = `<h1>Speisekarte</h1>
<div class="container">`;
content += /* display the data from api with meals.forEach */
return content + '</div>';
},
}
As I mentioned in the comments, I have a problem in returning the async data from the model to the view. Either it is undefined when I try to return data; or if I try to save the data into an array. I could also return the whole fetch() method, but this returns a promise and I dont think the controller should handle the promise.
I already read the long thread in How do I return the response from an asynchronous call? but I could not relate it to my case.
Since you declared speisekarte as an array, I'd expect it to always return as an empty array. When the fetch executes and fulfills the promise, its always too late in the above implementation.
You have to wait for the fetch result and there are multiple options you might consider:
Either providing a callback to the fetch result
Or notifying your application via event dispatch and listeners that your data has been loaded, so it can start rendering
Your link already has a very good answer on the topic callbacks and async/await, I could not put it better than what is explained there.
Thanks to lotype and Danny '365CSI' Engelman I've found the perfect solution for my projct. I solved it with custom events and an EventBus:
meal.js (model)
get meals() {
const url = 'http://localhost:8080/meals';
return fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
}).then(res => {
return res.json()
}).then(data => {
let ce = new CustomEvent(this.ESSEN_CHANGE_EVENT, {
detail: {
action: this.ESSEN_LOAD_ACTION,
meals: data,
}
});
EventBus.dispatchEvent(ce);
});
},
EventBus.js (from book: Web Components in Action)
export default {
/**
* add event listener
* #param type
* #param cb
* #returns {{type: *, callback: *}}
*/
addEventListener(type, cb) {
if (!this._listeners) {
this._listeners = [];
}
let listener = {type: type, callback: cb};
this._listeners.push(listener);
return listener;
},
/**
* trigger event
* #param ce
*/
dispatchEvent(ce) {
this._listeners.forEach(function (l) {
if (ce.type === l.type) {
l.callback.apply(this, [ce]);
}
});
}
}
Now, when the data is ready, a signal to the event bus is sent. The meal-list-component is waiting for the events and then gets the data:
export default class MealListComponent extends HTMLElement {
connectedCallback() {
this.attachShadow({mode: 'open'});
this.shadowRoot.innerHTML = Template.render();
this.dom = Template.mapDOM(this.shadowRoot);
// Load Speisekarte on init
this.dom.meals.innerHTML = Template.renderMeals(MealData.all);
// Custom Eventlistener - always triggers when essen gets added, deleted, updated etc.
EventBus.addEventListener(EssenData.ESSEN_CHANGE_EVENT, e => {
this.onMealChange(e);
});
}
onMealChange(e) {
switch (e.detail.action) {
case EssenData.ESSEN_LOAD_ACTION:
this.dom.meals.innerHTML = Template.renderMEals(e.detail.meals);
break;
}
}
}

How to call a composition function from another composition in VueJS

I've been experimenting with the new composition-api in VueJS and am not sure how to solve a problem. I'm looking for some advice on how to properly implement a solution. This wasn't a problem when everything was vuex-based since you can dispatch an action to another module without a problem. However, I'm struggling to find a solution for the composition implementation.
Problem:
Component calls a CompositionA's function.
CompositionA triggers a login function.
On CompositionA's login success/failure response I would like to call a CompositionB function. (CompositionB contains data and logic for showing a snackbar that's used across the site)
The problem is that it is necessary to inject the snackbar dependency in every component rather than have it be instantiated/mounted from CompositionA. Current solution is to this effect:
Component.vue:
// template calls login(credentials) method
import { useCompositionA } from '#/compositions/compositionA'
import { useCompositionB } from '#/compositions/compositionB'
export default {
name: 'Component',
setup(props, context) {
const { login } = useCompositionA(props, context, useCompositionB(props, context))
return {
login
}
},
}
compositionA.js:
export const useAuth = (props, context, snack) => {
const login = async (credentials) => {
try {
return await loginWithEmailPassword(credentials)
snack.show({text: 'Welcome back!'})
} catch (err) {
snack.show({text: 'Failed to login'})
}
}
return { login }
}
compositionB.js:
export const useSnack = (props, context) => {
const snack = reactive({
color: 'success',
text: null,
timeout: 6000,
visible: true,
})
const snackRefs = toRefs(snack)
const show = ({ text, timeout, color }) => {
snackRefs.text.value = text
snackRefs.timeout.value = timeout || 6000
snackRefs.color.value = color || 'success'
snackRefs.visible.value = true
}
return {
...snackRefs,
show
}
}
Would be nice if something like below existed, but I'm finding that the properties aren't reactive in CompositionB if it's used from CompositionA (method gets called but snackbar doesn't show up). My understanding is that Vue isn't injecting CompositionB into the Component, so I'm just running another instance of CompositionB inside CompositionA. What am I doing something wrong? What's the proper solution here?
compositionA.js (not working):
import { useCompositionB } from '#/compositions/compositionB'
export const useAuth = (props, context) => {
const login = async (credentials) => {
const { show } = useCompositionB()
try {
return await loginWithEmailPassword(credentials)
show({text: 'Welcome back!'})
} catch (err) {
show({text: 'Failed to login'})
}
}
return { login }
}
Thanks in advance,
As expected it was due to the Component referencing its own local copy of CompositionB*. Solution is actually to bring the state of your compositions into the global scope according to:
https://vueschool.io/articles/vuejs-tutorials/state-management-with-composition-api/
Something like this:
compositionB.js:
const snack = reactive({
color: 'success',
text: null,
timeout: 6000,
visible: true,
})
export const useSnack = (props, context) => {
const snackRefs = toRefs(snack)
const show = ({ text, timeout, color }) => {
snackRefs.text.value = text
snackRefs.timeout.value = timeout || 6000
snackRefs.color.value = color || 'success'
snackRefs.visible.value = true
}
return {
...snackRefs,
show
}
}
Works like a charm.
Only caveat I found initially was a composition-api error:
Uncaught Error: [vue-composition-api] must call Vue.use(plugin) before using any function.
This was easily solved by mounting the composition-api first thing in main.js as per solution here:
Uncaught Error: [vue-composition-api] must call Vue.use(plugin) before using any function
I think this won't be a problem with vue3 comes out. Hope this helps someone.

Vanilla JS vs React Class Binding for Listener Functions

I am following some api docs where the only code examples are in vanilla JS but I am trying to use them in React Native. They give fully functional React Native apps for reference but I can't figure out how to repurpose the methods for my needs.
In the api docs it gives the example:
ConnectyCube.videochat.onCallListener = function(session, extension) {
// here show some UI with 2 buttons - accept & reject, and by accept -> run the following code:
var extension = {};
session.accept(extension);
};
ConnectyCube is an module import and I need to use this particular method in React Native. In the app they provide as an example, it looks like this in a class component:
class AppRoot extends React.Component {
componentDidMount() {
ConnectyCube.init(...config)
this.setupListeners();
}
setupListeners() {
ConnectyCube.videochat.onCallListener = this.onCallListener.bind(this);
ConnectyCube.videochat.onUserNotAnswerListener = this.onUserNotAnswerListener.bind(this);
ConnectyCube.videochat.onAcceptCallListener = this.onAcceptCallListener.bind(this);
ConnectyCube.videochat.onRemoteStreamListener = this.onRemoteStreamListener.bind(this);
ConnectyCube.videochat.onRejectCallListener = this.onRejectCallListener.bind(this);
ConnectyCube.videochat.onStopCallListener = this.onStopCallListener.bind(this);
ConnectyCube.videochat.onSessionConnectionStateChangedListener = this.onSessionConnectionStateChangedListener.bind(this);
}
onCallListener(session, extension) {
console.log('onCallListener, extension: ', extension);
const {
videoSessionObtained,
setMediaDevices,
localVideoStreamObtained,
callInProgress
} = this.props
videoSessionObtained(session);
Alert.alert(
'Incoming call',
'from user',
[
{text: 'Accept', onPress: () => {
console.log('Accepted call request');
CallingService.getVideoDevices()
.then(setMediaDevices);
CallingService.getUserMedia(session).then(stream => {
console.log(stream)
localVideoStreamObtained(stream);
CallingService.acceptCall(session);
callInProgress(true);
});
}},
{
text: 'Reject',
onPress: () => {
console.log('Rejected call request');
CallingService.rejectCall(session);
},
style: 'cancel',
},
],
{cancelable: false},
);
}
onUserNotAnswerListener(session, userId) {
CallingService.processOnUserNotAnswer(session, userId);
this.props.userIsCalling(false);
}
onAcceptCallListener(session, userId, extension) {
CallingService.processOnAcceptCallListener(session, extension);
this.props.callInProgress(true);
}
onRemoteStreamListener(session, userID, remoteStream){
this.props.remoteVideoStreamObtained(remoteStream, userID);
this.props.userIsCalling(false);
}
onRejectCallListener(session, userId, extension){
CallingService.processOnRejectCallListener(session, extension);
this.props.userIsCalling(false);
this.props.clearVideoSession();
this.props.clearVideoStreams();
}
onStopCallListener(session, userId, extension){
this.props.userIsCalling(false);
this.props.callInProgress(false);
this.props.clearVideoSession();
this.props.clearVideoStreams();
CallingService.processOnStopCallListener(session, extension);
}
onSessionConnectionStateChangedListener(session, userID, connectionState){
console.log('onSessionConnectionStateChangedListener', userID, connectionState);
}
render() {
console.log('hey');
return <AppRouter />
}
}
function mapDispatchToProps(dispatch) {
return {
videoSessionObtained: videoSession => dispatch(videoSessionObtained(videoSession)),
userIsCalling: isCalling => dispatch(userIsCalling(isCalling)),
callInProgress: inProgress => dispatch(callInProgress(inProgress)),
remoteVideoStreamObtained: remoteStream => dispatch(remoteVideoStreamObtained(remoteStream)),
localVideoStreamObtained: localStream => dispatch(localVideoStreamObtained(localStream)),
clearVideoSession: () => dispatch(clearVideoSession()),
clearVideoStreams: () => dispatch(clearVideoStreams()),
setMediaDevices: mediaDevices => dispatch(setMediaDevices(mediaDevices)),
setActiveVideoDevice: videoDevice => dispatch(setActiveVideoDevice(videoDevice))
}
}
export default connect(null, mapDispatchToProps)(AppRoot)
I want to set up the listeners but I am not using classes like the one in the component above called CallingService or using the same redux actions - I'm taking a functional approach. When I paste the code from the docs in to a service which is just a normal function, I get the error:
Cannot set property 'onCallListener' of undefined.
Any ideas welcome!
componentDidMount() {
document.addEventListener("keyup",this.login,false);
}
login = (event) => {
console.log('i have been activated on keyup event from the componentDidMount()');
};

How do I use axios response in different components without using export?

As the tittle says, I would like to be able to use the same axios response for differents components.
I have some restrictions like, I'm onlyl able to use react by adding scripts tags to my html so things like exports or jsx are impossible for me.
This is my react code:
class User extends React.Component {
state = {
user: {}
}
componentWillMount() {
console.log(localStorage.getItem("user"))
axios.get('http://localhost:8080/dashboard?user=' + localStorage.getItem("user"))
.then(res => {
const userResponse = res.data
setTimeout(() =>
this.setState({user: userResponse.user}), 1000);
})
}
render () {
const {user} = this.state
if (user.fullName === undefined)
return React.createElement("div", null, 'loading..');
return React.createElement("span", {className: "mr-2 d-none d-lg-inline text-gray-600 small" }, user.fullName);
}
}
ReactDOM.render( React.createElement(User, {}, null), document.getElementById('userDropdown') );
class Roles extends React.Component{
state = {
user: {}
}
componentWillMount() {
console.log(localStorage.getItem("user"))
axios.get('http://localhost:8080/dashboard?user=' + localStorage.getItem("user"))
.then(res => {
const userResponse = res.data
setTimeout(() =>
this.setState({user: userResponse.user}), 1000);
})
}
render () {
const {user} = this.state
const roles = user.user.roles.map((rol) => rol.roleName)
if (user.fullName === undefined)
return React.createElement("div", null, 'loading..');
return React.createElement("a", {className: "dropdown-item" }, user.fullName);
}
}
ReactDOM.render( React.createElement(Roles, {}, null), document.getElementById('dropdownRol') );
I would like to be able to manage different components(rendering each one) with data of the same axios response.
Is this possible considering my limitations?
Thanks in advance
Here's a working example of how you might do it. I've tried to annotate everything with comments, but I'm happy to try to clarify if you have questions.
// Fake response object for the store's "load" request
const fakeResponse = {
user: {
fullName: "Carolina Ponce",
roles: [
{ roleName: "administrator" },
{ roleName: "editor" },
{ roleName: "moderator" },
{ roleName: "generally awesome person" }
]
}
};
// this class is responsible for loading the data
// and making it available to other components.
// we'll create a singleton for this example, but
// it might make sense to have more than one instance
// for other use cases.
class UserStore {
constructor() {
// kick off the data load upon instantiation
this.load();
}
// statically available singleton instance.
// not accessed outside the UserStore class itself
static instance = new this();
// UserStore.connect creates a higher-order component
// that provides a 'store' prop and automatically updates
// the connected component when the store changes. in this
// example the only change occurs when the data loads, but
// it could be extended for other uses.
static connect = function(Component) {
// get the UserStore instance to pass as a prop
const store = this.instance;
// return a new higher-order component that wraps the connected one.
return class Connected extends React.Component {
// when the store changes just force a re-render of the component
onStoreChange = () => this.forceUpdate();
// listen for store changes on mount
componentWillMount = () => store.listen(this.onStoreChange);
// stop listening for store changes when we unmount
componentWillUnmount = () => store.unlisten(this.onStoreChange);
render() {
// render the connected component with an additional 'store' prop
return React.createElement(Component, { store });
}
};
};
// The following listen, unlisten, and onChange methods would
// normally be achieved by having UserStore extend EventEmitter
// instead of re-inventing it, but I wasn't sure whether EventEmitter
// would be available to you given your build restrictions.
// Adds a listener function to be invoked when the store changes.
// Called by componentWillMount for connected components so they
// get updated when data loads, etc.
// The store just keeps a simple array of listener functions. This
// method creates the array if it doesn't already exist, and
// adds the new function (fn) to the array.
listen = fn => (this.listeners = [...(this.listeners || []), fn]);
// Remove a listener; the inverse of listen.
// Invoked by componentWillUnmount to disconnect from the store and
// stop receiving change notifications. We don't want to attempt to
// update unmounted components.
unlisten = fn => {
// get this.listeners
const { listeners = [] } = this;
// delete the specified function from the array.
// array.splice modifies the original array so we don't
// need to reassign it to this.listeners or anything.
listeners.splice(listeners.indexOf(fn), 1);
};
// Invoke all the listener functions when the store changes.
// (onChange is invoked by the load method below)
onChange = () => (this.listeners || []).forEach(fn => fn());
// do whatever data loading you need to do here, then
// invoke this.onChange to update connected components.
async load() {
// the loading and loaded fields aren't used by the connected
// components in this example. just including them as food
// for thought. components could rely on these explicit fields
// for store status instead of pivoting on the presence of the
// data.user object, which is what the User and Role components
// are doing (below) in this example.
this.loaded = false;
this.loading = true;
try {
// faking the data request. wait two seconds and return our
// hard-coded data from above.
// (Replace this with your network fetch.)
this.data = await new Promise(fulfill =>
setTimeout(() => fulfill(fakeResponse), 2000)
);
// update the loading/loaded status fields
this.loaded = true;
this.loading = false;
// call onChange to trigger component updates.
this.onChange();
} catch (e) {
// If something blows up during the network request,
// make the error available to connected components
// as store.error so they can display an error message
// or a retry button or whatever.
this.error = e;
}
}
}
// With all the loading logic in the store, we can
// use a much simpler function component to render
// the user's name.
// (This component gets connected to the store in the
// React.createElement call below.)
function User({ store }) {
const { data: { user } = {} } = store || {};
return React.createElement(
"span",
{ className: "mr-2 d-none d-lg-inline text-gray-600 small" },
user ? user.fullName : "loading (User)…"
);
}
ReactDOM.render(
// Connect the User component to the store via UserStore.connect(User)
React.createElement(UserStore.connect(User), {}, null),
document.getElementById("userDropdown")
);
// Again, with all the data loading in the store, we can
// use a much simpler functional component to render the
// roles. (You may still need a class if you need it to do
// other stuff, but this is all we need for this example.)
function Roles({ store }) {
// get the info from the store prop
const { data: { user } = {}, loaded, loading, error } = store || {};
// handle store errors
if (error) {
return React.createElement("div", null, "oh noes!");
}
// store not loaded yet?
if (!loaded || loading) {
return React.createElement("div", null, "loading (Roles)…");
}
// if we made it this far, we have user data. do your thing.
const roles = user.roles.map(rol => rol.roleName);
return React.createElement(
"a",
{ className: "dropdown-item" },
roles.join(", ")
);
}
ReactDOM.render(
// connect the Roles component to the store like before
React.createElement(UserStore.connect(Roles), {}, null),
document.getElementById("dropdownRol")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="userDropdown"></div>
<div id="dropdownRol"></div>

VueJS how to use _.debounce on data changes

I'm building a little vue.js-application where I do some post requests. I use the watch-method to whach for api changes which then updates the component if the post request is successfull. Since the watcher constantly checks the API I want to add the ._debounce method but for some reason it doesn't work.
here is the code:
<script>
import _ from 'lodash'
export default {
data () {
return {
cds: [],
cdCount: ''
}
},
watch: {
cds() {
this.fetchAll()
}
},
methods: {
fetchAll: _.debounce(() => {
this.$http.get('/api/cds')
.then(response => {
this.cds = response.body
this.cdCount = response.body.length
})
})
},
created() {
this.fetchAll();
}
}
</script>
this gives me the error: Cannot read property 'get' of undefined
Can someone maybe tell me what I'm doing wrong?
EDIT
I removed the watch-method and tried to add
updated(): {
this.fetchAll()
}
with the result that the request runs in a loop :-/ When I remove the updated-lifecycle, the component does (of course) not react to api/array changes... I'm pretty clueless
Mind the this: () => { in methods make the this reference window and not the Vue instance.
Declare using a regular function:
methods: {
fetchAll: _.debounce(function () {
this.$http.get('/api/cds/add').then(response => {
this.cds = response.body
this.cdCount = response.body.length
})
})
},
Other problems
You have a cyclic dependency.
The fetchAll method is mutating the cds property (line this.cds = response.body) and the cds() watch is calling this.fetchAll(). As you can see, this leads to an infinite loop.
Solution: Stop the cycle by removing the fetchAll call from the watcher:
watch: {
cds() {
// this.fetchAll() // remove this
}
},

Categories