Does GRPC Middleware library support grpc-node? I'm interested in logging grpc proto requests, and it seems like I might have to learn golang in order to have a logging feature?
Definitely you don't need to learn Golang for that. You just need to check how to use gRPC interceptors with node. In the interceptor code you will implement any of those features available in the gRPC middleware for Golang.
It would be something like that
const interceptors = require('grpc-interceptors');
const server = interceptors.serverProxy(new grpc.Server());
server.addService(proto.MyPackage.MyService.service, { Method1, Method2 });
const myMiddlewareFunc = function (ctx, next) {
// do stuff before call
console.log('Making gRPC call...');
await next()
// do stuff after call
console.log(ctx.status.code);
}
server.use(myMiddlewareFunc);
Related
I am seeking the best manner in which this should be done.
I have a https based GCF Function such as:
// google function
exports.someFunction = async (req, res) => {
try {
...
// some logic and access
res.status(200).send(data)
}
catch(error) {
res.status(400).send(error.message)
}
}
The API serverless function in Next.js is using axios. Is that the recommended method?
// next.js pages/api/call-google-func.js
async function handler(req, res) {
try {
const url = '....' //https://gcp-zone-project-xx834.cloudfunctions.net/someFunc
const res = await axios.get(url)
const resdata = res.data
res.status(200).send(resdata)
}
catch(error) {
res.status(400).send(error)
}
}
The problem with this method is that the GCF must have public access. How can we set up to access the GCF from Next.js by passing credentials as environment variables. Thanks
I think for this situation where a Vercel Serverless Function must communicate with the outside world, a Google Cloud Function, you'd want to create a JWT token on Vercel's side to pass to Google's side which you would then need to verify. I think Exchanging a self-signed JWT for a Google-signed ID token would be what you need.
Since either side doesn't know about the other, Google's IAM normal cloud privileges for allowing GCG<>GCF communication wouldn't apply here.
I am writing an API using Firebase Functions for a Flutter Web application. I cannot see any way to use the FlutterFire cloud_functions.dart package to call my functions using any http method other than 'POST.' Am I missing something?
With this Firebase function:
export const hello = functions.https.onRequest(async (req, res) => {
cors(req, res);
functions.logger.info(req.method);
switch (req.method.toLowerCase()) {
case 'get':
res.status(200).send({'data': 'get', 'error': false });
break;
case 'post':
res.status(201).send();
break;
case 'options':
res.status(200).send();
break;
default:
res.status(405).send({error: true, data: {}, message: 'method not allowed'});
}
});
Is there any way to have code like
HttpsCallable callable = FirebaseFunctions.instance.httpsCallable('hello');
HttpsCallableResult result = await callable();
print(result.data);
issue a GET request?
Requests made to callable functions with the Firebase Functions SDK are always POST and can't be changed. You can see exactly what it's doing if you read over the protocol specification for callable functions.
If you want to use the Firebase Functions SDK to invoke a function, you must also define your function using onCall instead of onRequest, as described in the documentation for callable functions.
If you define your function with onRequest, then you should use a standard HTTP client library to invoke it. The Functions client SDK will not work.
You can't really mix and match onRequest and onCall functions - they have different use cases and implementation details.
I am trying to develop a server-side validation of my users' in-app purchases and subscriptions as recommended, and I want to use Firebase Functions for that. Basically it has to be an HTTP trigger function that receives a purchase token, calls the Play Developer API to verify the purchase, and then does something with the result.
However, calling many of the Google APIs (including Play Developer API) requires non-trivial authorization. Here's how I understand the required setup:
There has to be a GCP project with Google Play Developer API v2 enabled.
It should be a separate project, since there can be only one linked to Play Store in the Google Play Console.
My Firebase Functions project must somehow authenticate to that other project. I figured that using a Service Account is most suitable in this server-to-server scenario.
Finally, my Firebase Functions code must somehow obtain authentication token (hopefully JWT?) and finally make an API call to get a subscription status.
The problem is that absolutely no human-readable documentation or guidance on that is existent. Given that ingress traffic in Firebase is included in the free plan (so I assume they encourage using Google APIs from Firebase Functions), that fact is pretty disappointing. I've managed to find some bits of info here and there, but having too little experience with Google APIs (most of which required simply using an api key), I need help with putting it together.
Here's what I figured out so far:
I got a GCP project linked to the Play Store and with the API enabled. For some reason though, trying to test it in APIs Explorer results in an error "The project id used to call the Google Play Developer API has not been linked in the Google Play Developer Console".
I made a Service Account and exported a JSON key, which contains the key to produce a JWT.
I also set up read permissions for that Service Account in Play Console.
I found a Node.JS client library for Google APIs, which is in alpha and has very sparse documentation (e.g. there's no obvious documentation on how to authenticate with JWT, and no samples on how to call the android publisher API). At the moment I'm struggling with that. Unfortunately I'm not super-comfortable with reading JS library code, especially when the editor doesn't provide the possibility to jump to highlighted functions' sources.
I'm pretty surprised this hasn't been asked or documented, because verifying in-app purchases from Firebase Functions seems like a common task. Has anyone successfully done it before, or maybe the Firebase team will step in to answer?
I figured it out myself. I also ditched the heavyweight client library and just coded those few requests manually.
Notes:
The same applies to any Node.js server environment. You still need the key file of a separate service account to mint a JWT and the two steps to call the API, and Firebase is no different.
The same applies to other APIs that require authentication as well — differing only in scope field of the JWT.
There are a few APIs that don't need you to exchange the JWT for an access token — you can mint a JWT and provide it directly in Authentication: Bearer, without a round trip to OAuth backend.
After you've got the JSON file with the private key for a Service Account that's linked to Play Store, the code to call the API is like this (adjust to your needs). Note: I used request-promise as a nicer way to do http.request.
const functions = require('firebase-functions');
const jwt = require('jsonwebtoken');
const keyData = require('./key.json'); // Path to your JSON key file
const request = require('request-promise');
/**
* Exchanges the private key file for a temporary access token,
* which is valid for 1 hour and can be reused for multiple requests
*/
function getAccessToken(keyData) {
// Create a JSON Web Token for the Service Account linked to Play Store
const token = jwt.sign(
{ scope: 'https://www.googleapis.com/auth/androidpublisher' },
keyData.private_key,
{
algorithm: 'RS256',
expiresIn: '1h',
issuer: keyData.client_email,
subject: keyData.client_email,
audience: 'https://www.googleapis.com/oauth2/v4/token'
}
);
// Make a request to Google APIs OAuth backend to exchange it for an access token
// Returns a promise
return request.post({
uri: 'https://www.googleapis.com/oauth2/v4/token',
form: {
'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
'assertion': token
},
transform: body => JSON.parse(body).access_token
});
}
/**
* Makes a GET request to given URL with the access token
*/
function makeApiRequest(url, accessToken) {
return request.get({
url: url,
auth: {
bearer: accessToken
},
transform: body => JSON.parse(body)
});
}
// Our test function
exports.testApi = functions.https.onRequest((req, res) => {
// TODO: process the request, extract parameters, authenticate the user etc
// The API url to call - edit this
const url = `https://www.googleapis.com/androidpublisher/v2/applications/${packageName}/purchases/subscriptions/${subscriptionId}/tokens/${token}`;
getAccessToken(keyData)
.then(token => {
return makeApiRequest(url, token);
})
.then(response => {
// TODO: process the response, e.g. validate the purchase, set access claims to the user etc.
res.send(response);
return;
})
.catch(err => {
res.status(500).send(err);
});
});
These are the docs I followed.
I think I found a slightly quicker way to do this... or at least... more simply.
To support scaling and keep index.ts from growing out of control... I have all the functions and globals in the index file but all the actual events are handled by handlers. Easier to maintain.
So here's my index.ts (I heart type safety):
//my imports so you know
import * as functions from 'firebase-functions';
import * as admin from "firebase-admin";
import { SubscriptionEventHandler } from "./subscription/subscription-event-handler";
// honestly not 100% sure this is necessary
admin.initializeApp({
credential: admin.credential.applicationDefault(),
databaseURL: 'dburl'
});
const db = admin.database();
//reference to the class that actually does the logic things
const subscriptionEventHandler = new SubscriptionEventHandler(db);
//yay events!!!
export const onSubscriptionChange = functions.pubsub.topic('subscription_status_channel').onPublish((message, context) => {
return subscriptionEventHandler.handle(message, context);
});
//aren't you happy this is succinct??? I am!
Now... for the show!
// importing like World Market
import * as admin from "firebase-admin";
import {SubscriptionMessageEvent} from "./model/subscription-message-event";
import {androidpublisher_v3, google, oauth2_v2} from "googleapis";
import {UrlParser} from "../utils/url-parser";
import {AxiosResponse} from "axios";
import Schema$SubscriptionPurchase = androidpublisher_v3.Schema$SubscriptionPurchase;
import Androidpublisher = androidpublisher_v3.Androidpublisher;
// you have to get this from your service account... or you could guess
const key = {
"type": "service_account",
"project_id": "not going to tell you",
"private_key_id": "really not going to tell you",
"private_key": "okay... I'll tell you",
"client_email": "doesn't matter",
"client_id": "some number",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "another url"
};
//don't guess this... this is right
const androidPublisherScope = "https://www.googleapis.com/auth/androidpublisher";
// the handler
export class SubscriptionEventHandler {
private ref: admin.database.Reference;
// so you don't need to do this... I just did to log the events in the db
constructor(db: admin.database.Database) {
this.ref = db.ref('/subscriptionEvents');
}
// where the magic happens
public handle(message, context): any {
const data = JSON.parse(Buffer.from(message.data, 'base64').toString()) as SubscriptionMessageEvent;
// if subscriptionNotification is truthy then we're solid here
if (message.json.subscriptionNotification) {
// go get the the auth client but it's async... so wait
return google.auth.getClient({
scopes: androidPublisherScope,
credentials: key
}).then(auth => {
//yay! success! Build android publisher!
const androidPublisher = new Androidpublisher({
auth: auth
});
// get the subscription details
androidPublisher.purchases.subscriptions.get({
packageName: data.packageName,
subscriptionId: data.subscriptionNotification.subscriptionId,
token: data.subscriptionNotification.purchaseToken
}).then((response: AxiosResponse<Schema$SubscriptionPurchase>) => {
//promise fulfilled... grandma would be so happy
console.log("Successfully retrieved details: " + response.data.orderId);
}).catch(err => console.error('Error during retrieval', err));
});
} else {
console.log('Test event... logging test');
return this.ref.child('/testSubscriptionEvents').push(data);
}
}
}
There are few model classes that help:
export class SubscriptionMessageEvent {
version: string;
packageName: string;
eventTimeMillis: number;
subscriptionNotification: SubscriptionNotification;
testNotification: TestNotification;
}
export class SubscriptionNotification {
version: string;
notificationType: number;
purchaseToken: string;
subscriptionId: string;
}
So that's how we do that thing.
I want add a response interceptor to my $http service for error handling purposes. The interceptor logic include send errors messages to server using $http in case necessary, BUT I don't want send errors messages to the server about errors messages, I mean, I want disable my interceptor while sending error message to the server.
My idea was create a service named 'remote_log' and put inside it all the code needed to send error to server. That service of course will use the $http service and have it in its dependency list.
Then add as dependency of the interceptor to the 'remote_log' service, and use the 'remote_log' inside the interceptor when need send errors to the server. The problems is that:
Interceptors must be defined using the $httpProvider when the $http service still is not instantiated/accessible, so, inside the interceptor code can't be a dependency to that the $http service because a "Circular dependency" error happen.
I think my only option is create a separate instance of the $http service inside my 'remote_log', an instance that don't uses the $httpProvider configuration I set while creating the interceptor. My question is: How can I do that? Any other ideas?
1. Circular dependency problem.
So, why does the error appear? Here is a quick overview of the process:
$http service is requested.
$httpProvider is asked to construct it.
During construction you register interceptor, that requests $http service not existing yet.
You get "Circular dependency" error.
First solution.
Create your dependency using angular.injector(). Notice, that you will create another $http service, independent from your app.
$httpProvider.interceptors.push(function($q) {
$injector = angular.injector();
return {
response: function(response) {
$injector.invoke(function($http) {
// This is the exterior $http service!
// This interceptor will not affect it.
});
}
};
});
Second solution (better).
Inject $injector in your interceptor and use it to retrieve dependencies after $http initialization, right at the time you need them. These dependencies are registered services of your app and will not be created anew!
$httpProvider.interceptors.push(function($q, $injector) {
return {
response: function(response) {
$injector.invoke(function($http, someService) {
// $http is already constructed at the time and you may
// use it, just as any other service registered in your
// app module and modules on which app depends on.
});
}
};
});
2. Interception prevention problem.
If you use the second solution, there are actually two problems:
If you utilize $http service inside your
interceptor, you may end up with infinite interceptions: you send
request, interceptor catches it, sends another, catches another,
send again, and so on.
Sometimes you want just prevent request from being intercepted.
The 'config' parameter of $http service is just an object. You may create a convention, providing custom parameters and recognizing them in your interceptors.
For example, let's add "nointercept" property to config and try duplicate every user request. This is a silly application, but useful example to understand the behavior:
$httpProvider.interceptors.push(function($q, $injector) {
return {
response: function(response) {
if (response.config.nointercept) {
return $q.when(response); // let it pass
} else {
var defer = $q.defer();
$injector.invoke(function($http) {
// This modification prevents interception:
response.config.nointercept = true;
// Reuse modified config and send the same request again:
$http(response.config)
.then(function(resp) { defer.resolve(resp); },
function(resp) { defer.reject(resp); });
});
return defer.promise;
}
}
};
});
Having the testing for property in interceptor, you may prevent the interception in controllers and services:
app.controller('myController', function($http) {
// The second parameter is actually 'config', see API docs.
// This query will not be duplicated by the interceptor.
$http.get('/foo/bar', {nointercept: true})
.success(function(data) {
// ...
});
});
I used what is described in the answer but I used the syntax with a factory because with the anonymous function it didn't work, I don't really know why:
(function(angular){
angular.module('app', [])
.config([
'$httpProvider',
function($httpProvider) {
$httpProvider.interceptors.push('Interceptor');
}
])
.factory('Interceptor', [
'$injector',
InterceptorFactory
]);
function InterceptorFactory($injector){
return {
request: function(config) {
var ServiceWithHttp = $injector.get('ServiceWithHttp');
// Use ServiceWithHttp
return config;
}
};
}
}(window.angular));
So I would like to do something like:
app.On_All_Incoming_Request(function(req, res){
console.log('request received from a client.');
});
the current app.all() requires a path, and if I give for example this / then it only works when I'm on the homepage, so it's not really all..
In plain node.js it is as simple as writing anything after we create the http server, and before we do the page routing.
So how to do this with express, and what is the best way to do it?
Express is based on the Connect middleware.
The routing capabilities of Express are provided by the router of your app and you are free to add your own middlewares to your application.
var app = express.createServer();
// Your own super cool function
var logger = function(req, res, next) {
console.log("GOT REQUEST !");
next(); // Passing the request to the next handler in the stack.
}
app.configure(function(){
app.use(logger); // Here you add your logger to the stack.
app.use(app.router); // The Express routes handler.
});
app.get('/', function(req, res){
res.send('Hello World');
});
app.listen(3000);
It's that simple.
(PS : If you just want some logging you might consider using the logger provided by Connect)
You should do this:
app.all("*", (req, res, next) => {
console.log(req); // do anything you want here
next();
});
You can achieve it by introducing a middleware function.
app.use(your_function) can be of help. app.use with accept a function that will get executed on every request logged to your server.
Example:
app.use((req, res, next) => {
console.log("req received from client");
next(); // this will invoke next middleware function
});
Express supports wildcards in route paths. So app.all('*', function(req, res) {}) is one way to go.
But that's just for route handlers. The difference is that an Express route handler is expected to send a response, and, if it doesn't, Express will never send a response. If you want to do something without explicitly sending a response, like check for a header, you should use Express middleware. app.use(function(req, res, next) { doStuff(); next(); }