NodeJS gRPC: "Method handler expected but not provided" - javascript

I plowed through the docs and haven't found a solution yet. The app is loosely based on the "sayHello"-example from their docs but every time the code runs the warning Method handler for /eventComm.DatabaseRPC/InsertSingleDocument expected but not provided is returned.
My proto file:
service DatabaseRPC {
rpc InsertSingleDocument (Doc) returns (Doc) {}
}
message Doc {
required string name = 1;
required int32 id = 2;
}
My gRPC Server:
function InsertSingleDocument (call, callback) {
callback(null, {
name: 'Hello ',
id: 1
})
}
let server = new grpc.Server()
server.addProtoService(protoDef.DatabaseRPC.service, {
InsertSingleDocument: InsertSingleDocument
})
server.bind('0.0.0.0:50051', grpc.ServerCredentials.createInsecure())
server.start()
What is the problem with this code? Of course I already tried to google the error but found no solution

To conform with JavaScript naming conventions, methods should be provided with the first letter lowercased:
server.addProtoService(protoDef.DatabaseRPC.service, {
insertSingleDocument: InsertSingleDocument
})
You can see this in the Hello World example you linked. The method is declared as SayHello in the proto file, but is passed to the server as sayHello.
Note: I agree that this is confusing, and I will try to improve the situation.

Related

Get fully-qualified path of Protobuf message in JavaScript

I am trying to get the fully-qualified path of my Protobuf message type in JavaScript. For example, given the following file: status.proto
package my.messages.proto;
message Status {
string code = 1;
}
Once compiled with protoc, I can then do something like:
import { Status } from 'gen/my/messages/proto/status_pb.js';
const status = new Status();
But then how can I get the fully-qualified path of this message? I want a string of my.messages.proto.Status, but I can't seem to find any API where this is possible. I basically want the equivalent of the C++ function message.GetDescriptor()->full_name().
If I do console.log(status);, I see something printed out like:
my.messages.proto.Status {wrappers_: null, messageId_: undefined, arrayIndexOffset_: -1, array: Array(1), pivot_: 1.7976931348623157e+308, …}
So, the information is there, but just not sure how I can access it. Is this possible in JavaScript with Protobuf?
All the proto message types generated using protoc_node are available on the global proto namespace in node as a key:class object. So you can check whether your message is an instance of one of the key's values and return the key:
import { MessageType } from './MessageType_pb'
const message = new MessageType()
export function getSubjectFromMessage(message: Message){
for (const key in proto){
if(message instanceof proto[key]){
return key
}
}
}
getSubjectFromMessage(message) === "MessageType" // true
Nasty that they don't provide a class.name or something.

how to use Twit's Typescript definitions; Callback function not assignable

I'm just getting started into Node and typescript. I'm an experienced Java engineer and have done front end work, but it's been mostly copy-paste-modify without fully understanding all the javascript. Now I'm trying to get deeper into Node, JS and Typescript, and think I have jumped too far ahead without understanding what all is going on. This is just a small personal project. I would like to use classes and liked what TS offered but it seems to introduce more complexity and problems than it solved, so maybe it's not worth it.
I'm trying to use the Twit package to just read tweets. Had it working fine in plain js before trying to convert it to TS. This was my working js:
var _ = require('lodash');
var Twit = require('twit');
var T = new Twit({
consumer_key: '..'
, consumer_secret: '..'
, access_token: '..'
, access_token_secret: '..'
})
var options = { screen_name: 'TwitterUser',
count: 200 };
T.get('statuses/user_timeline', options , function(err, data) {
// it comes latest first; I want earliest first
data.reverse();
console.log(data[0]);
// try to filter out alerts only
var alerts = _.filter(data, function(tweet){
return (tweet.text.indexOf('#alert') > -1) ||
(tweet.text.indexOf('#ALERT') > -1);
});
for (var i = 0; i < alerts.length ; i++) {
console.log(alerts[i].created_at + ' ' + alerts[i].text);
}
})
here is the Typescript version:
import { IncomingMessage } from "http";
import _ from "lodash";
import Twit from "twit";
export class TwitterListener {
getlatestTweets() {
const T = new Twit({
consumer_key: '..'
, consumer_secret: '..'
, access_token: '..'
, access_token_secret: '..'
})
const options = { screen_name: 'TwitterUser',
count: 200 };
T.get('statuses/user_timeline', options, function(err, result: Response, data: IncomingMessage) {
// it comes latest first; I want earliest first
data.reverse();
console.log(data[0]);
// try to filter out alerts only
const alerts = _.filter(data, function(tweet: Twit.Twitter.Status) {
return (tweet.text.indexOf('#alert') > -1) ||
(tweet.text.indexOf('#ALERT') > -1);
});
for (let i = 0; i < alerts.length ; i++) {
console.log(alerts[i].created_at + ' ' + alerts[i].text);
}
})
}
}
The biggest problem is on the callback for the get user timeline. The VS Code linter says 'Argument of type (whatever I've tried) is not assignable to parameter of type Callback. The Type Docs at DefinitelyTyped say this should be a Callback like so:
export interface Callback {
(err: Error, result: Response, response: IncomingMessage): void;
}
Which is why I tried to change it to that. I thought from the plain js version where the callback is function(err, data) and data is an array of tweets/Statuses that it might be something like function(err:Error, data: any[]) or function(err, data: Array<Twit.Twitter.Status>), which I also tried, but got the same error. I have a feeling it's the syntax of how I'm defining this callback function, but can't figure out what.
I don't understand how the callback definition goes from function(err, data) to the Callback type with the result and response. What am I missing?
The problem here is - like mentioned in the comment by Aluan Haddad - that the type of the second parameter of your function is not compatible to the specified type of the callback function in the Twit package.
The type of the callback function is this:
export interface Callback {
(err: Error, result: Response, response: IncomingMessage): void;
}
I think the source of your confusion is, that the Response type in this definition does not refer to the built-in Response type, but the Twit package overwrites (shadows) this definition with this one:
export type Response = object;
This type definition is a bit confusing and at least the naming should reflect that its a Twitter response and not the built-in definition that is describing the Fetch API response built into the browser.
To fix your problem, you have to import this Response definition from the Twit package or refer to it via "Twit.Response". Importing it into your own file, will shadow the built-in definition there too and you can refer to the "Response" type of the Twit package.
import Twit, {Response} from "twit"
I want to remark that the strongly typed parameters of your function are NOT the problem, instead they have lead to you questioning your code. Without the types in your own function, you wouldn't have seen a problem and - maybe - never would have seen that there is a misunderstanding.
After looking at twit's documentation I realized the callback accept three (3) parameters err - an error, data - arrays of tweets and response - response object from twitter. The reason the js version worked is because of the loosely typed nature of javascript.

Using Meteor.wrapAsync correctly

Hopefully this is a newbie question.
I have the following code that I am trying to convert to using meteor.wrapAsync. I am getting a "Exception while invoking method 'emailSend' ReferenceError: syncfunc is not defined" exception. What am i missing?
Stack Trace:
I20191031-06:21:16.246(-5)? Exception while invoking method 'emailSend' ReferenceError: syncfunc is not defined
I20191031-06:21:16.248(-5)? at MethodInvocation.emailSend (src/imports/api/email.js:13:27)
I20191031-06:21:16.249(-5)? at maybeAuditArgumentChecks (packages/ddp-server/livedata_server.js:1771:12)
I20191031-06:21:16.273(-5)? at DDP._CurrentMethodInvocation.withValue (packages/ddp-server/livedata_server.js:719:19)
I20191031-06:21:16.275(-5)? at Meteor.EnvironmentVariable.EVp.withValue (packages\meteor.js:1234:12)
I20191031-06:21:16.276(-5)? at DDPServer._CurrentWriteFence.withValue (packages/ddp-server/livedata_server.js:717:46)
I20191031-06:21:16.277(-5)? at Meteor.EnvironmentVariable.EVp.withValue (packages\meteor.js:1234:12)
I20191031-06:21:16.277(-5)? at Promise (packages/ddp-server/livedata_server.js:715:46)
I20191031-06:21:16.278(-5)? at new Promise (<anonymous>)
I20191031-06:21:16.279(-5)? at Session.method (packages/ddp-server/livedata_server.js:689:23)
I20191031-06:21:16.280(-5)? at packages/ddp-server/livedata_server.js:559:43
email.js:
Meteor.methods(
{
emailSend(fromAddress, subject, emailText)
{
if (Meteor.isServer)
{
const { Email } = require('../server/email.js');
var syncFunc = Meteor.wrapAsync(Email.send);
var sendEmailReturn=syncfunc(fromAddress, subject, emailText);
return sendEmailReturn;
**//if I comment out the above three lines and uncomment the line below then the application works fine.**
//return Email.send(fromAddress, subject, emailText);
}
},
})
You don't need to use external callback to sync methods as Meteor supports "async" and "awaits" by default. Below is an example of using 'await' method.
Meteor.methods({
async emailSend(fromAddress, subject, emailText) {
const { Email } = require('../server/email.js');
var sendEmailReturn = await Email.send(fromAddress, subject, emailText);
}
});
I believe Meteor.defer is more suited to what you're trying to achieve here.
Example:
Meteor.methods({
'action_plus_email': function () {
// do something
Meteor.defer(() => {
Email.send(...)
})
return 'hello there, user';
}
})
https://www.meteor-tuts.com/chapters/1/meteorsnacks#Meteor-defer
https://apiko.com/blog/organization-of-email-sending-in-meteorjs/
And if you're are going to be sending many emails, please take a look at mail-time. It can be of great help.
https://github.com/VeliovGroup/Mail-Time

How to report console.error with Sentry?

I have application where some critical issues are reported with console.error but are not thrown so application might continue to run - possibly in crippled state.
It's necessary to report also console.error issues, but Sentry (Raven) library send to server only thrown exceptions.
Does someone knows how to solve this nicely ?
(ideally without need to rewrite all console.error calls, cause also some vendor libraries might still write output just into console)
As user #kumar303 mentioned in his comment to the question ... you can use the JS console integration Sentry.Integrations.CaptureConsole.
See https://docs.sentry.io/platforms/javascript/configuration/integrations/plugin/#captureconsole for documentation.
At the end you JS code to setup Sentry looks as follows:
import * as Sentry from '#sentry/browser';
import { CaptureConsole } from '#sentry/integrations';
Sentry.init({
dsn: 'https://your-sentry-server-dsn',
integrations: [
new CaptureConsole({
levels: ['error']
})
],
release: '1.0.0',
environment: 'prod',
maxBreadcrumbs: 50
})
If then someone calls console.error a new event will sent to sentry.
Here's a more robust override solution
// creating function declarations for better stacktraces (otherwise they'd be anonymous function expressions)
var oldConsoleError = console.error;
console.error = reportingConsoleError; // defined via function hoisting
function reportingConsoleError() {
var args = Array.prototype.slice.call(arguments);
Sentry.captureException(reduceConsoleArgs(args), { level: 'error' });
return oldConsoleError.apply(console, args);
};
var oldConsoleWarn = console.warn;
console.warn = reportingConsoleWarn; // defined via function hoisting
function reportingConsoleWarn() {
var args = Array.prototype.slice.call(arguments);
Sentry.captureMessage(reduceConsoleArgs(args), { level: 'warning' });
return oldConsoleWarn.apply(console, args);
}
function reduceConsoleArgs(args) {
let errorMsg = args[0];
// Make sure errorMsg is either an error or string.
// It's therefore best to pass in new Error('msg') instead of just 'msg' since
// that'll give you a stack trace leading up to the creation of that new Error
// whereas if you just pass in a plain string 'msg', the stack trace will include
// reportingConsoleError and reportingConsoleCall
if (!(errorMsg instanceof Error)) {
// stringify all args as a new Error (which creates a stack trace)
errorMsg = new Error(
args.reduce(function(accumulator, currentValue) {
return accumulator.toString() + ' ' + currentValue.toString();
}, '')
);
}
return errorMsg;
}
Based on #Marc Schmid's solution I came up with the following working example, if you link to the Sentry CDN files.
<script src="https://browser.sentry-cdn.com/5.11.1/bundle.min.js" integrity="sha384-r7/ZcDRYpWjCNXLUKk3iuyyyEcDJ+o+3M5CqXP5GUGODYbolXewNHAZLYSJ3ZHcV" crossorigin="anonymous"></script>
<!-- https://github.com/getsentry/sentry-javascript/issues/1976#issuecomment-492260648 -->
<script src="https://browser.sentry-cdn.com/5.11.1/captureconsole.min.js"></script>
<script>
Sentry.init({
dsn: 'https://abcdef1234567890#sentry.io/012345',
debug: false,
integrations: [
new Sentry.Integrations.CaptureConsole({
levels: ['error']
})
],
});
</script>
Found a little hacky solution:
const consoleError = console.error;
console.error = function(firstParam) {
const response = consoleError.apply(console, arguments);
Raven.captureException(firstParam, { level: 'error' });
return response;
};
It just wraps console.error and report each of error logs in console to Raven (Sentry).
If someone have nicer approach (maybe some hidden feature of Sentry) please feel free to share!
I wrote a library that is going this using your Sentry instance.
https://github.com/aneldev/dyna-sentry

How do I get a hold of a Strongloop loopback model?

This is maddening, how do I get a hold of a loopback model so I can programmatically work with it ? I have a Persisted model named "Notification". I can interact with it using the REST explorer. I want to be able to work with it within the server, i.e. Notification.find(...). I execute app.models() and can see it listed. I have done this:
var Notification = app.models.Notification;
and get a big fat "undefined". I have done this:
var Notification = loopback.Notification;
app.model(Notification);
var Notification = app.models.Notification;
and another big fat "undefined".
Please explain all I have to do to get a hold of a model I have defined using:
slc loopback:model
Thanks in advance
You can use ModelCtor.app.models.OtherModelName to access other models from you custom methods.
/** common/models/product.js **/
module.exports = function(Product) {
Product.createRandomName = function(cb) {
var Randomizer = Product.app.models.Randomizer;
Randomizer.createName(cb);
}
// this will not work as `Product.app` is not set yet
var Randomizer = Product.app.models.Randomizer;
}
/** common/models/randomizer.js **/
module.exports = function(Randomizer) {
Randomizer.createName = function(cb) {
process.nextTick(function() {
cb(null, 'random name');
});
};
}
/** server/model-config.js **/
{
"Product": {
"dataSource": "db"
},
"Randomizer": {
"dataSource": null
}
}
I know this post was here a long time ago. But since I got the same question recent days, here's what I figured out with the latest loopback api:
Loopback 2.19.0(the latest for 12th, July)
API, Get the Application object to which the Model is attached.: http://apidocs.strongloop.com/loopback/#model-getapp
You can get the application which your model was attached as following:
ModelX.js
module.exports = function(ModelX) {
//Example of disable the parent 'find' REST api, and creat a remote method called 'findA'
var isStatic = true;
ModelX.disableRemoteMethod('find', isStatic);
ModelX.findA = function (filter, cb) {
//Get the Application object which the model attached to, and we do what ever we want
ModelX.getApp(function(err, app){
if(err) throw err;
//App object returned in the callback
app.models.OtherModel.OtherMethod({}, function(){
if(err) throw err;
//Do whatever you what with the OtherModel.OtherMethod
//This give you the ability to access OtherModel within ModelX.
//...
});
});
}
//Expose the remote method with settings.
ModelX.remoteMethod(
'findA',
{
description: ["Remote method instaed of parent method from the PersistedModel",
"Can help you to impliment your own business logic"],
http:{path: '/finda', verb: 'get'},
accepts: {arg:'filter',
type:'object',
description: 'Filter defining fields, where, include, order, offset, and limit',
http:{source:'query'}},
returns: {type:'array', root:true}
}
);
};
Looks like I'm not doing well with the code block format here...
Also you should be careful about the timing when this 'getApp' get called, it matters because if you call this method very early when initializing the model, something like 'undefined' error will occur.

Categories

Resources