How to call a function outside of an emitted event call function - javascript

I apologize if this is unclear, it's late and I don't know how best to explain it.
I'm using an event emitter to pass data from a server response to a function inside of a separate class in another file, but when trying to use methods in those classes, the this keyword obviously doesn't work (because in this scenario, this refers to the server event emitter) - how would I reference a function within the class itself? I've provided code to help illustrate my point a bit better
ServiceClass.js
class StreamService {
/**
*
* #param {} database
* #param {Collection<Guild>} guilds
*/
constructor (database, guilds,) {
.....
twitchListener.on('live', this.sendLiveAlert) // fire test method when we get a notification
// if there are streamers to monitor, being monitoring
winston.info('Stream service initialized')
}
..............
async get (url, params = null, headers = this.defaultHeaders) {
// check oauth token
const expirationDate = this.token.expires_in || 0
if (expirationDate <= Date.now() || !this.token) await this.getAccessToken()
// build URL
const index = 0
let paramsString = ''
for (const [key, value] of params.entries()) {
if (index === 0) {
paramsString += `?${key}=${value}`
} else {
paramsString += `&${key}=${value}`
}
}
const res = await fetch(url + paramsString, { method: 'GET', headers: headers })
if (!res.ok) {
winston.error(`Error performing GET request to ${url}`)
return null
}
return await res.json()
}
async sendLiveAlert(streamTitle, streamURL, avatar, userName, gameId, viewerCount, thumbnail, startDateTime) {
// get game name first (no headers needed)
const params = new Map()
params.set('id', gameId)
const gameData = await this.get('https://api.twitch.tv/heliix/games', params, this.defaultHeaders)
if(gameData) {
// get webhook and send message to channel
const webhookClient = new WebhookClient('755641606555697305', 'OWZvI01kUUf4AAIR9uv2z4CxRse3Ik8b0LKOluaOYKmhE33h0ypMLT0JJm3laomlZ05o')
const embed = new MessageEmbed()
.setTitle(`${userName} just went live on Twitch!`)
.setURL(streamURL)
.setThumbnail(avatar)
.addFields(
{ name: 'Now Playing', value: gameData.data[0].name },
{ name: 'Stream Title', value: streamTitle }
)
.setImage(thumbnail)
}
webhookClient.send('Webhook test', embed)
}
}
Server.js
class TwitchWebhookListener extends EventEmitter {
......................
// Routes
server
.post((req, res) => {
console.log('Incoming POST request on /webhooks')
............................
const data = req.body.data[0]
if(!this.streamerLiveStatus.get(data.user_id) && data.type === 'live') {
// pass request body to bot for processing
this.emit(
'live',
data.title, // stream title
`https://twitch.tv/${data.user_name}`, // channel link
`https://avatar.glue-bot.xyz/twitch/${data.user_name}`, // streamer avatar
data.user_name,
data.game_id,
data.viewer_count,
data.thumbnail_url,
data.started_at // do we need this?
)
}
break
default:
res.send(`Unknown webhook for ${req.params.id}`)
break
}
} else {
console.log('The Signature did not match')
res.send('Ok')
}
} else {
console.log('It didn\'t seem to be a Twitch Hook')
res.send('Ok')
}
})
}
}
const listener = new TwitchWebhookListener()
listener.listen()
module.exports = listener
Within the sendLiveAlert method, I'm trying to call the get method of the StreamService class - but because it's called directly via the emitter within server.js, this refers specifically to the Server.js class - is there any way I can use StreamService.get()? I could obviously just rewrite the code inside the method itself, but that seems unnecessary when its right there?

Change this:
twitchListener.on('live', this.sendLiveAlert)
to this:
twitchListener.on('live', this.sendLiveAlert.bind(this))
Or, you could also do this:
twitchListener.on('live', (...args) => {
this.sendLiveAlert(...args);
});
With .bind() it creates a function wrapper that resets the proper value of this for you. In the case of the arrow function, it preserves the lexical value of this for you.

Related

Function Binding Issues on Callbacks

I'm working with WebSocket and having an issue with a function showing incorrect data, All my code used to work with a Class-based component, I'm only trying to convert a class component that actually calls the connect method of Websocket to a functional-based component. The Websocket is class-based for instance.
So I have a WebSocket which on socket New Message sets callbacks.
socketNewMessage(data) {
console.log(data);
const parsedData = JSON.parse(data);
const command = parsedData.command;
if (command === "userChatGroups") {
this.callback[command](parsedData.chatGRoups);
}
if (command === "new_userChatGroups") {
this.callback[command](parsedData.chatGRoup);
}
}
and here are the callbacks defined -
addCallbacks(userChatGroups, newUserChatGroup) {
this.callback["userChatGroups"] = userChatGroups;
this.callback["new_userChatGroups"] = newUserChat;
}
and then websocket finally return -
const WebSocketInstance = Websocketservice.getInstance();
export default WebSocketInstance;
Now the class-based component which I'm trying to convert to functional based -
let's call this component Chats-
this calls the connect method and a Websocket instance is returned.
useEffect(() => {
if (loggedInUserDetail) {
WebSocketInstance.connect(loggedInUserDetail[0].id);
}
}, [loggedInUserDetail]);
I have a helper function which checks the status of websocket -
function waitForSocketConnection(callback) {
setTimeout(() => {
if (WebSocketInstance.state() === 1) {
console.log("connection is secure");
callback();
return;
} else {
console.log("waiting for connection");
waitForSocketConnection(callback);
}
}, 10);
}
and I check the status and map my callbacks - here is the problem-
useEffect(() => {
waitForSocketConnection(() => {
WebSocketInstance.addCallbacks(
setChatGroups,
addNewChatGroup
);
});
}, [loggedInUserDetail]);
I have a state to manage CHatGroups -
const [groups, setGroups] = useState([]);
the setChatGroups (which initially loads all the groups the users are associated with works fine and sets the state of chatsGroups) as -
const setChatGroups = useCallback((userChatGroups) => {
setGroups(userChatGroups); //whatever data it recieved from the websocket.
}, []);
but the function addNewChatGroup always shows groups value as an empty array [] (it was updated earlier with setChatGroups). If I manually check the value of groups it is an array of n length, but in addNewChatGroup function, it always shows an empty array with the initial value [].
const addNewChatGroup = useCallback(
(newCHatGroup) => {
console.log(groups); **error -> this is always empty array**
// here I have to update. add the received value with the previous
},
[groups] // callbacks used hoping this will bind
);
In the class-based component I used to set callbacks on the constructor and used to bind with this, but I'm not able to do it here, can anyone help what I'm missing?
I'm sure it is a binding issue. maybe. May I know the reason for this binding failure?
Well if I understood correctly your problem, it could be link to multiple things.
The problem actually is I don't have a clear view on all your components, maybe you can try to paste a sandbox link or something like that with a "simple structure".
I tried to reproduced a typescript version, I don't know if it could help:
class MyWebSocket {
private static _instance: MyWebSocket;
public callbacks: any = {};
public connected: boolean = false;
public socketNewMessage(data: any): void {
const parsedData = JSON.parse(data);
console.log('new message received:', parsedData);
const command = parsedData.command;
if (command === "new_userChatGroups") {
this.callbacks[command](parsedData.newGroupAdded);
}
}
public addCallbacks(elements: {command: string, func: Function}[]) {
console.log('adding callbacks...', elements);
elements.forEach(element => {
this.callbacks[element.command] = element.func;
});
}
public connect(): void {
setTimeout(() => this.connected = true, 1100);
}
public static getInstance(): MyWebSocket {
return this._instance || (this._instance = new MyWebSocket());
}
}
class SocketUtils {
static waitForSocketConnection(callback: any): void {
const waitingInterval = setInterval(() => {
if (MyWebSocket.getInstance().connected) {
console.log('socket is connected! processing callback...');
clearInterval(waitingInterval);
callback();
return;
} else {
console.log('socket is not connected after 1sec, waiting...');
}
}, 1000);
}
}
class Chat {
groups: string[] = ['group of JStw'];
new_userChatGroups(group: string) {
this.groups.push(group);
}
}
class Main {
constructor() {
const myChat = new Chat();
MyWebSocket.getInstance().connect();
// waiting connections.
SocketUtils.waitForSocketConnection(() => {
console.log('waitForSocketConnection is triggered, adding callbacks...');
// adding callbacks
MyWebSocket.getInstance().addCallbacks([{command: 'new_userChatGroups', func: myChat.new_userChatGroups.bind(myChat)}]);
});
// Waiting 5min to dispatch an message
setTimeout(() => {
// testing eventing after getting connection
MyWebSocket.getInstance().socketNewMessage(JSON.stringify({command: 'new_userChatGroups', newGroupAdded: 'group of Ranu Vijay'}));
}, 5000);
setTimeout(() => {
console.log('program finished, results of chat groups:', myChat.groups);
}, 10000);
}
}
new Main();
Output:
I'm more specialized on functional component by using react so without a link to investigate all your code, it will be complex to help.
I don't know if you are using a library, but there is this one: https://www.npmjs.com/package/react-use-websocket which seems to be really ultra simple to use without managing socket connection/disconnection.
For me if I had to implement it, I would say:
Component ChatGroup which is using websocket hook const { sendMessage, lastMessage, readyState } = useWebSocket(socketUrl); and contains the state groups, setGroups.
Component Chat which can use the sendMessage from props of ChatGroup component and call it from this component if a join a group.
Then your "parent" component is managing the state and is controlling the data.

How to validate GitHub webhook with Deno?

I'm trying to make a GitHub webhook server with Deno, but I cannot find any possible way to do the validation.
This is my current attempt using webhooks-methods.js:
import { Application } from "https://deno.land/x/oak/mod.ts";
import { verify } from "https://cdn.skypack.dev/#octokit/webhooks-methods?dts";
const app = new Application();
app.use(async (ctx, next) => {
try {
await next();
} catch (_err) {
ctx.response.status = 500;
}
});
const secret = "...";
app.use(async (ctx) => {
const signature = ctx.request.headers.get("X-Hub-Signature-256");
if (signature) {
const payload = await ctx.request.body({ type: "text" }).value;
const result = await verify(secret, payload, signature);
console.log(result);
}
ctx.response.status = 200;
});
The verify function is returning false every time.
Your example is very close. The GitHub webhook documentation details the signature header schema. The value is a digest algorithm prefix followed by the signature, in the format of ${ALGO}=${SIGNATURE}:
X-Hub-Signature-256: sha256=d57c68ca6f92289e6987922ff26938930f6e66a2d161ef06abdf1859230aa23c
So, you need to extract the signature from the value (omitting the prefix):
const signatureHeader = request.headers.get("X-Hub-Signature-256");
const signature = signatureHeader.slice("sha256=".length);
Update: Starting in release version 3.0.1 of octokit/webhooks-methods.js, it is no longer necessary to manually extract the signature from the header — that task is handled by the verify function. The code in the answer has been updated to reflect this change.
Here's a complete, working example that you can simply copy + paste into a project or playground on Deno Deploy:
gh-webhook-logger.ts:
import { assert } from "https://deno.land/std#0.177.0/testing/asserts.ts";
import {
Application,
NativeRequest,
Router,
} from "https://deno.land/x/oak#v11.1.0/mod.ts";
import type { ServerRequest } from "https://deno.land/x/oak#v11.1.0/types.d.ts";
import { verify } from "https://esm.sh/#octokit/webhooks-methods#3.0.2?pin=v106";
// In actual usage, use a private secret:
// const SECRET = Deno.env.get("SIGNING_SECRET");
// But for the purposes of this demo, the exposed secret is:
const SECRET = "Let me know if you found this to be helpful!";
type GitHubWebhookVerificationStatus = {
id: string;
verified: boolean;
};
// Because this uses a native Request,
// it can be used in other contexts besides Oak (e.g. `std/http/serve`):
async function verifyGitHubWebhook(
request: Request,
): Promise<GitHubWebhookVerificationStatus> {
const id = request.headers.get("X-GitHub-Delivery");
// This should be more strict in reality
assert(id, "Not a GH webhhok");
const signatureHeader = request.headers.get("X-Hub-Signature-256");
let verified = false;
if (signatureHeader) {
const payload = await request.clone().text();
verified = await verify(SECRET, payload, signatureHeader);
}
return { id, verified };
}
// Type predicate used to access native Request instance
// Ref: https://github.com/oakserver/oak/issues/501#issuecomment-1084046581
function isNativeRequest(r: ServerRequest): r is NativeRequest {
// deno-lint-ignore no-explicit-any
return (r as any).request instanceof Request;
}
const webhookLogger = new Router().post("/webhook", async (ctx) => {
assert(isNativeRequest(ctx.request.originalRequest));
const status = await verifyGitHubWebhook(ctx.request.originalRequest.request);
console.log(status);
ctx.response.status = 200;
});
const app = new Application()
.use(webhookLogger.routes())
.use(webhookLogger.allowedMethods());
// The port is not important in Deno Deploy
await app.listen({ port: 8080 });

Cloud Function Cannot Read Property of Undefined

New to Cloud Functions and trying to understand my error here from the log. It says cannot read property 'uid' of undefined. I am trying to match users together. onCreate will call matching function to check if a user exists under live-Channels and if so will set channel value under both users in live-Users to uid+uid2. Does the log also say which line the error is from? Confused where it shows that.
const functions = require('firebase-functions');
//every time user added to liveLooking node
exports.command = functions.database
.ref('/liveLooking/{uid}')
.onCreate(event => {
const uid = event.params.uid
console.log(`${uid} this is the uid`)
const root = event.data.adminRef.root
//match with another user
let pr_cmd = match(root, uid)
const pr_remove = event.data.adminRef.remove()
return Promise.all([pr_cmd, pr_remove])
})
function match(root, uid) {
let m1uid, m2uid
return root.child('liveChannels').transaction((data) => {
//if no existing channels then add user to liveChannels
if (data === null) {
console.log(`${uid} waiting for match`)
return { uid: uid }
}
else {
m1uid = data.uid
m2uid = uid
if (m1uid === m2uid) {
console.log(`$m1uid} tried to match with self!`)
return
}
//match user with liveChannel user
else {
console.log(`matched ${m1uid} with ${m2uid}`)
return {}
}
}
},
(error, committed, snapshot) => {
if (error) {
throw error
}
else {
return {
committed: committed,
snapshot: snapshot
}
}
},
false)
.then(result => {
// Add channels for each user matched
const channel_id = m1uid+m2uid
console.log(`starting channel ${channel_id} with m1uid: ${m1uid}, m2uid: ${m2uid}`)
const m_state1 = root.child(`liveUsers/${m1uid}`).set({
channel: channel_id
})
const m_state2 = root.child(`liveUsers/${m2uid}`).set({
channel: channel_id
})
return Promise.all([m_state1, m_state2])
})
}
You are referring to a very old version of the Cloud Functions API. Whatever site or tutorial you might be looking it, it's showing examples that are no longer relevant.
In modern Cloud Functions for Firebase, Realtime Database onCreate triggers receive two parameters, a DataSnapshot, and a Context. It no longer receives an "event" as the only parameter. You're going to have to port the code you're using now to the new way of doing things. I strongly suggest reviewing the product documentation for modern examples.
If you want to get the wildcard parameters as you are trying with the code const uid = event.params.uid, you will have to use the second context parameter as illustrated in the docs. To access the data from snapshot, use the first parameter.

Firebase cloud functions onCreate painfully slow to update database

I am having a slightly odd issue, and due to the lack of errors, I am not exactly sure what I am doing wrong. What I am trying to do is on an onCreate event, make an API call, and then update a field on the database if the field is not set to null. Based on my console logs for cloud functions, I can see the API call getting a ok, and everything is working properly, but after about 2-5 minutes, it will update. A few times, it didnt update after 15 mins. What is causing such a slow update?
I have eliminated the gaxios call as the bottleneck simply from the functions logs, and local testing.
Some context: I am on the firebase blaze plan to allow for egress and my dataset isnt really big. I am using gaxios because it is already part of firebase-funcstions npm install.
The code is:
const functions = require('firebase-functions');
const { request } = require('gaxios');
const { parse } = require('url');
exports.getGithubReadme = functions.firestore.document('readmes/{name}').onCreate((snapshot, context) => {
const toolName = context.params.name;
console.log(toolName);
const { name, description, site } = snapshot.data();
console.log(name, description, site);
const parsedUrl = parse(site);
console.log(parsedUrl);
if (description) return;
if (parsedUrl.hostname === 'github.com') {
let githubUrl = `https://api.github.com/repos${parsedUrl.path}/readme`;
request({
method : 'GET',
url : githubUrl
})
.then((res) => {
let { content } = res.data;
return snapshot.ref.update({ description: content });
})
.catch((error) => {
console.log(error);
return null;
});
}
return null;
});
When you execute an asynchronous operation (i.e. request() in your case) in a background triggered Cloud Function, you must return a promise, in such a way the Cloud Function waits that this promise resolves in order to terminate.
This is very well explained in the official Firebase video series here (Learning Cloud Functions for Firebase (video series)). In particular watch the three videos titled "Learn JavaScript Promises" (Parts 2 & 3 especially focus on background triggered Cloud Functions, but it really worth watching Part 1 before).
So you should adapt your code as follows, returning the promise returned by request():
const functions = require('firebase-functions');
const { request } = require('gaxios');
const { parse } = require('url');
exports.getGithubReadme = functions.firestore.document('readmes/{name}').onCreate((snapshot, context) => {
const toolName = context.params.name;
console.log(toolName);
const { name, description, site } = snapshot.data();
console.log(name, description, site);
const parsedUrl = parse(site);
console.log(parsedUrl);
if (description) return null;
if (parsedUrl.hostname === 'github.com') {
let githubUrl = `https://api.github.com/repos${parsedUrl.path}/readme`;
return request({
method: 'GET',
url: githubUrl
})
.then((res) => {
let { content } = res.data;
return snapshot.ref.update({ description: content });
})
.catch((error) => {
console.log(error);
return null;
});
} else {
return null;
}
});

CycleJs - Unit Testing a Component with Mocha

I'm learning in cyclejs and rxjs and I'm struggling to write a unit test for one of the components I'm working on. I'm sorry for the long question. I have tried to simplify the question and this is the best I can do.
Component does the following things:
1) When it's loaded, it sends a request to an end point to retrieve user's tasks. From these tasks, it finds the task user currently working on and creates an url stream which contains the endpoint which will be called when user clicks complete task button.
2) When a new value come to test stream, it creates a new stream from tests defined so far.
3) When user clicks complete task, component first sends a request to an endpoint to add tests user defined to the task. Then, if the call is successful, component sends another request to the endpoint which is retrieved at step 1 to complete task.
4) After complete task request is completed, component shows a notification to the user.
Here is the component:
import Rx from 'rx';
import {Observable} from 'rx';
const Model = ({test$, completeTask$, sources}) => {
const processId = Number(sources.routeParams.processId);
const SET_TEST_RESULTS = `http://dummyhost/tosettests/${processId}`;
const GET_USER_TASKS = 'http://dummyhost/togetusertasks';
const taskRequest$ = Observable.just({
url: GET_USER_TASKS,
method: 'GET'
});
const url$ = sources.HTTP
.filter(response => response.request.url === GET_USER_TASKS)
.flatMap(response => response.body)
.filter(task => task.processId === processId)
.take(1)
.map(task=> {
const activitiId = task.processInstanceId;
return {
completeTask: `http://dummyhost/tocompletetask/${activitiId}`
};
});
const definedTest$ = test$
.scan((acc, curr) => {
acc.push(curr);
return acc;
}, [])
.startWith([])
.share();
const setTestResultsRequest$ = completeTask$
.withLatestFrom(definedTest$, (event, tests) => {
return {
url: SET_TEST_RESULTS,
method: 'POST',
send: {
tests
}
};
});
const setTestResultsResponse$ = sources.HTTP
.filter(response => response.request.url === SET_TEST_RESULTS);
const completeTaskRequest$ = setTestResultsResponse$
.withLatestFrom(url$, (response, url) => {
return {
url: url.completeTask,
method: 'POST'
};
});
const completeTaskResponse$ = sources.HTTP
.withLatestFrom(url$, (response, url) => {
return {
response,
url
};
})
.filter(data => data.response.request.url === data.url.completeTask)
.map((data)=> data.response);
const successNotification$ = completeTaskResponse$
.map(() => {
return {
action: 'success',
message: 'Tests have been defined!'
};
});
const request$ = Observable.merge(taskRequest$, setTestResultsRequest$, completeTaskRequest$);
return {
request$: request$,
notification$: successNotification$,
definedTest$: definedTest$
};
};
What I'm trying to unit test are the followings:
1) Component must send a request to define tests to the task
2) Component must send a request to complete task
3) Component must show a notification
Here's the unit test I wrote so far with Mocha:
describe('When complete task is clicked', ()=> {
const tests = [
{
name: 'test 1 name'
},
{
name: 'test 2 name'
},
{
name: 'test 3 name'
}
];
const test$ = Observable.fromArray(tests);
const completeTask$ = Observable.just({}).delay(300);
const processId = 3;
const activitiId = 3;
var actions;
before(done => {
const HTTPSource = new Rx.ReplaySubject();
const sources = {
DOM: mockDOMSource(),
routeParams: {processId},
HTTP: HTTPSource
};
actions = Model({test$, completeTask$, sources});
const request$ = actions.request$.share();
request$
.filter(request=> request.url === 'http://dummyhost/togetusertasks')
.subscribe((request) => {
const response = {
request,
body: [{
processId,
processInstanceId: activitiId
}]
};
HTTPSource.onNext(response);
});
request$
.filter(request=> request.url === `http://dummyhost/tosettests/${processId}`)
.subscribe((request) => {
const response = {
request
};
HTTPSource.onNext(response);
});
request$
.filter(request=> request.url === `http://dummyhost/tocompletetask/${activitiId}`)
.subscribe((request) => {
const response = {
request
};
HTTPSource.onNext(response);
done();
});
});
it('should send a request to add tests', (done)=> {
actions.request$
.filter(request=> request.url === `http://dummyhost/tosettests/${processId}`)
.subscribe(() => {
done();
});
});
it('should send a request to complete task', (done)=> {
actions.request$
.filter(request=> request.url === `http://dummyhost/tocompletetask/${activitiId}`)
.subscribe(() => {
done();
});
});
it('should show a notification to inform user that tests have been defined', (done) => {
actions.notification$.take(1)
.subscribe(notification => {
expect(notification.action).to.equal('success');
done();
});
});
});
In the test, I have tried to mock HTTP source. By subscribing request stream sink returning from component, I'm able to create mock responses for each request. But I have to use share operator to run mocha's before function successfully. Otherwise, I'm only able to mock first request which is retrieving user's tasks. So, my first question is:
Why do I have to use share operator to run mocha's before function successfully?
Currently, tests that verify complete task request is sent and notification is shown passes. But, test that verifies define test to the task request is failing. This is strange because to verify complete task request is sent, component must send define tests to the task request first. It seems define tests to the task request somehow lost in the request stream returned from component's sink. So my second question is:
Why second and third tests are passing but first test is failing?
Here's a gist that contains all code in one file:
gist link
As I said earlier, I'm new to cyclejs and rxjs. I think I'm not fully understanding what I'm testing. So any comments or answers about cyclejs and rxjs that directs me the right path are welcomed.
If you think question is not clear enough, please let me know. I'll do my best improve the question.

Categories