I have the following function in a ReactJS app that is supposed to initialize the Twilio services that I am using. However, it seems like the Twilio channels are not being accessed correctly. Here is my code:
componentDidMount() {
let chatClient = this;
$.ajax({
method: "GET",
url: 'get_twilio_token',
data: {device: chatClient.device},
success: (data) => {
let accessManager = new Twilio.AccessManager(data.token);
//let messagingClient = new Twilio.Chat.Client(data.token);
let messagingClient = new Twilio.Chat.Client.create(data.token).then(client => {
client.getUserChannelDescriptors().then(channels => {
let channelsHash = {};
console.log('inside callback of messagingClient2')
channels.items.map(channel => {
channel.on('messageAdded', () => {})
channelsHash[channel.uniqueName] = channel;
});
});
});
}
});
}
This function throws an error message saying TypeError: channel.on is not a function in the line channel.on('messageAdded', () => {}).
Any help is appreciated. Thanks.
getUserChannelDescriptors() returns ChannelDescriptors not Channels. To get Channel you have to call getChannel for the descriptor: https://media.twiliocdn.com/sdk/js/chat/releases/1.0.0/docs/ChannelDescriptor.html#getChannel__anchor
This is how I've done it
async function getChannels() {
// Initialize the chat client
this.chatClient = new Chat(this.state.twilioToken);
await this.chatClient.initialize();
// Get channel descriptors
this.chatClient.getUserChannelDescriptors().then(paginator => {
let channels = [];
let channelsBulkFetch = [];
if (paginator.items.length) {
channels = paginator.items;
// Loop through all channels and call getChannel() for each cahnnel
for (let i = 0; i < paginator.items.length; i++) {
channelsBulkFetch.push(channels[i].getChannel());
}
// Loop through each channel detailed object and perform various operations
channels.map(channel => {
// Do whatever you want with channel object
channel.on('messageAdded', this.messageAdded);
});
}
})
}
And for sorted channels with respective to last message timestamp
async function getSortedChannels() {
// Initialize the chat client
this.chatClient = new Chat(this.state.twilioToken);
await this.chatClient.initialize();
// Get channel descriptors
this.chatClient.getUserChannelDescriptors().then(paginator => {
let channels = [];
let sortedChannels = [];
let channelsBulkFetch = [];
if (paginator.items.length) {
channels = paginator.items;
// Loop through all channels and call getChannel() for each cahnnel
for (let i = 0; i < paginator.items.length; i++) {
channelsBulkFetch.push(channels[i].getChannel());
}
/**
* Additional part for sorting
*/
sortedChannels = channels.sort(function (a, b) {
// Turn strings into dates, and then subtract them
// If channel doesn't have any message consider the dateDreated for sorting
return new Date(b.lastMessage ? b.lastMessage.timestamp : b.dateCreated) - new Date(a.lastMessage ? a.lastMessage.timestamp : a.dateCreated);
});
// Loop through each channel detailed object and perform various operations
sortedChannels.map(channel => {
// Do whatever you want with channel object
channel.on('messageAdded', this.messageAdded);
});
}
})
}
Related
I am using Laravel, and trying add browser to browser audio calling. I am using Vonage (Tookbox) API for this, but I am getting some error.
here is my code:
async function audioCall() {
var publisher;
var targetElement = 'publisher';
var pubOptions = {publishAudio:true, publishVideo:false};
publisher = OT.initPublisher(targetElement, pubOptions, function(error) {
if (error) {
alert("The client cannot publish.");
} else {
console.log('Publisher initialized.');
}
});
// Setting an audio source to a new MediaStreamTrack
const stream = await OT.getUserMedia({
videoSource: null
});
const [audioSource] = stream.getAudioTracks();
publisher.setAudioSource(audioSource).then(() => console.log('Audio source updated'));
// Cycling through microphone inputs
let audioInputs;
let currentIndex = 0;
OT.getDevices((err, devices) => {
audioInputs = devices.filter((device) => device.kind === 'audioInput');
// Find the right starting index for cycleMicrophone
audioInputs.forEach((device, idx) => {
if (device.label === publisher.getAudioSource().label) {
currentIndex = idx;
}
});
});
const cycleMicrophone = () => {
currentIndex += 1;
let deviceId = audioInputs[currentIndex % audioInputs.length].deviceId;
publisher.setAudioSource(deviceId);
};
}
This code return an error on console:
Uncaught SyntaxError: await is only valid in async functions and the top level bodies of modules
I believe the issue is that you have
device.kind === 'audioInput'
and I'm pretty sure device.kind comes out like 'audioinput' (all lowercase).
examples:
https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo/kind
https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices#examples
That would make audioInputs empty (try to console.log it to verify) and gives you the error because there is no device.
Try:
device.kind.toLowerCase() === 'audioinput'
Hope it works out.
I'm not sure what's going on here. I have set up an API route in NextJS that returns before the data has been loaded. Can anyone point out any error here please?
I have this function that calls the data from makeRequest():
export async function getVendors() {
const vendors = await makeRequest(`Vendor.json`);
console.log({ vendors });
return vendors;
}
Then the route: /api/vendors.js
export default async (req, res) => {
const response = await getVendors();
return res.json(response);
};
And this is the makeRequest function:
const makeRequest = async (url) => {
// Get Auth Header
const axiosConfig = await getHeader();
// Intercept Rate Limited API Errors & Retry
api.interceptors.response.use(
function (response) {
return response;
},
async function (error) {
await new Promise(function (res) {
setTimeout(function () {
res();
}, 2000);
});
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
token[n] = null;
originalRequest._retry = true;
const refreshedHeader = await getHeader();
api.defaults.headers = refreshedHeader;
originalRequest.headers = refreshedHeader;
return Promise.resolve(api(originalRequest));
}
return Promise.reject(error);
}
);
// Call paginated API and return number of requests needed.
const getQueryCount = await api.get(url, axiosConfig).catch((error) => {
throw error;
});
const totalItems = parseInt(getQueryCount.data['#attributes'].count);
const queriesNeeded = Math.ceil(totalItems / 100);
// Loop through paginated API and push data to dataToReturn
const dataToReturn = [];
for (let i = 0; i < queriesNeeded; i++) {
setTimeout(async () => {
try {
const res = await api.get(`${url}?offset=${i * 100}`, axiosConfig);
console.log(`adding items ${i * 100} through ${(i + 1) * 100}`);
const { data } = res;
const arrayName = Object.keys(data)[1];
const selectedData = await data[arrayName];
selectedData.map((item) => {
dataToReturn.push(item);
});
if (i + 1 === queriesNeeded) {
console.log(dataToReturn);
return dataToReturn;
}
} catch (error) {
console.error(error);
}
}, 3000 * i);
}
};
The issue that I'm having is that getVendors() is returned before makeRequest() has finished getting the data.
Looks like your issue stems from your use of setTimeout. You're trying to return the data from inside the setTimeout call, and this won't work for a few reasons. So in this answer, I'll go over why I think it's not working as well as a potential solution for you.
setTimeout and the event loop
Take a look at this code snippet, what do you think will happen?
console.log('start')
setTimeout(() => console.log('timeout'), 1000)
console.log('end')
When you use setTimeout, the inner code is pulled out of the current event loop to run later. That's why end is logged before the timeout.
So when you use setTimeout to return the data, the function has already ended before the code inside the timeout even starts.
If you're new to the event loop, here's a really great talk: https://youtu.be/cCOL7MC4Pl0
returning inside setTimeout
However, there's another fundamental problem here. And it's that data returned inside of the setTimeout is the return value of the setTimeout function, not your parent function. Try running this, what do you think will happen?
const foo = () => {
setTimeout(() => {
return 'foo timeout'
}, 1000)
}
const bar = () => {
setTimeout(() => {
return 'bar timeout'
}, 1000)
return 'bar'
}
console.log(foo())
console.log(bar())
This is a result of a) the event loop mentioned above, and b) inside of the setTimeout, you're creating a new function with a new scope.
The solution
If you really need the setTimeout at the end, use a Promise. With a Promise, you can use the resolve parameter to resolve the outer promise from within the setTimeout.
const foo = () => {
return new Promise((resolve) => {
setTimeout(() => resolve('foo'), 1000)
})
}
const wrapper = async () => {
const returnedValue = await foo()
console.log(returnedValue)
}
wrapper()
Quick note
Since you're calling the setTimeout inside of an async function, you will likely want to move the setTimeout into it's own function. Otherwise, you are returning a nested promise.
// don't do this
const foo = async () => {
return new Promise((resolve) => resolve(true))
}
// because then the result is a promise
const result = await foo()
const trueResult = await result()
I have code like this:
loadImageFile(url: string, progressCallback: (progress: number) => void): Observable<string> {
return new Observable<string>(observer => {
const xhr = new XMLHttpRequest();
const nativeWindow = this.windowRef.nativeWindow;
let notifiedNotComputable = false;
xhr.open("GET", url, true);
xhr.responseType = "arraybuffer";
xhr.onprogress = event => {
if (event.lengthComputable) {
const progress: number = (event.loaded / event.total) * 100;
progressCallback(progress);
} else {
if (!notifiedNotComputable) {
notifiedNotComputable = true;
progressCallback(-1);
}
}
};
xhr.onloadend = function() {
if (!xhr.status.toString().match(/^2/)) {
// Here I want that the user of the Observable created at the top with
// "return new Observable" can use "pipe(catchError(...))".
}
if (!notifiedNotComputable) {
progressCallback(100);
}
const options: any = {};
const headers = xhr.getAllResponseHeaders();
const m = headers.match(/^Content-Type:\s*(.*?)$/im);
if (m && m[1]) {
options.type = m[1];
}
const blob = new Blob([this.response], options);
observer.next((nativeWindow as any).URL.createObjectURL(blob));
observer.complete();
};
xhr.send();
});
}
How can I make the xhr.onloadend act so the Observable returned by this loadImageFile method will throwError?
I believe my issue is that I am already inside new Observable, while it's the main function loadImageFile that should return throwError.
How can I overcome this?
PS: Please ignore this text: StackOverflow won't let me post this because it's mostly code, but in this case, I believe it makes sense, so I'm just writing this paragraph here to make the post validation pass :)
Thanks!
Here's the solution:
observer.error(xhr)
I need to create and consume an array from Dexie ordeBy Promise
var list = [];
const ms = wmsLocalDb.table1.orderBy("index").toArray();
ms.each(m => list.push(m)).then(
//When list is complete I want to consume
for (var i = 0; i < list.length; i++) {
//something
}
);
But i cannot read list array.
Best regards
Ingd
You want something along the following lines :
wmsLocalDb.table1.orderBy("index").toArray()
.then(list => {
list.forEach(item => {
//something
});
})
.catch(error => {
// handle error
});
Hopefully the code below communicates the problem clearly. The issue is that in the module which uses the get method of fetchData, the value being returned is the actual Promise, rather than the JSON as desired. Any thoughts on this?
// fetchData.js module
var _ = require('lodash');
function get() {
var endpoint1 = `/endpoint1`;
var endpoint2 = `/endpoint2`;
return fetch(endpoint1)
.then((endpoint1Response) => {
return endpoint1Response.json()
.then((endpoint1JSON) => {
return fetch(endpoint2)
.then((endpoint2Response) => {
return endpoint2Response.json()
.then((endpoint2JSON) => {
var data = _.merge({}, {json1: endpoint1JSON}, {json2: endpoint2JSON});
console.log('data in fetch', data); // this logs the json
return data;
});
});
});
});
}
exports.get = get;
// module which uses get method of fetchData get
var fetchData = require('fetchData');
var data = fetchData.get();
console.log('returned from fetchData', data); // this logs a Promise
Yes, that's exactly what's supposed to happen. The whole point of promises is that their result value is not immediately available and that doesn't change just because you're obtaining one from a separate module.
You can access the value like this:
var fetchData = require('fetchData');
fetchData.get().then(data =>
console.log('returned from fetchData', data);
);
Also note that you are using promises in a non-idiomatic way and creating a "tower of doom." This is much easier on the eyes and accomplishes the same thing:
function fetchJson(endpoint) {
return fetch(endpoint)
.then(endpointResponse => endpointResponse.json());
}
function get() {
var endpoint1 = `/endpoint1`;
var endpoint2 = `/endpoint2`;
return Promise.all([fetchJson(endpoint1), fetchJson(endpoint2)])
.then(responses => {
var data = { json1: responses[0], json2: responses[1] };
console.log('data in fetch', data); // this logs the json
return data;
});
}
Edit I haven't used async/await in JavaScript, but to answer your question, I presume this would work:
async function fetchJson(endpoint) {
var res = await fetch(endpoint);
return res.json();
}
async function get() {
var endpoint1 = `/endpoint1`;
var endpoint2 = `/endpoint2`;
var data = {
json1: await fetchJson(endpoint1),
json2: await fetchJson(endpoint2)
};
console.log('data in fetch', data); // this logs the json
return data;
}
// module which uses get method of fetchData get
async function main() {
var fetchData = require('fetchData');
var data = await fetchData.get();
console.log('returned from fetchData', data);
}
return main();