How can I access the RethinkDB context set up by the Hapi plugin? - rethinkdb

According to this Hapi JS plugin's documentation, The RethinkDB library and the connection are bound to the server context inside handlers on this.rethinkdb and this.rethinkdbConn.
However, I'm not being able to access those, only using request.server.plugins etc. Does anyone know why? Is it because the context set inside a plugin with server.bind() is only available inside the plugin itself ? Thanks!
EDIT:
Here's is some of the code on index.js
const plugins = [{register: require('hapi-rethinkdb'), options: {url: //url here}]
server.register(plugins, () => {
server.route(require('./routes'))
server.start()
})
The on routes.js, let's say a have a route like this:
{
method: 'GET',
path: '/list-of-things',
handler: function(request, reply) {
// I have to use it like this:
const r = request.server.plugins['hapi-rethinkdb'].rethinkdb;
const connection = request.server.plugins['hapi-rethinkdb'].connection;
// Because this throws undefined:
const r = this.rethinkdb;
const connection = this.rethinkdbConn
}
}

Related

Using Express Router to proxy non-CORS content doubles request path

There must be something obvious I am missing. I have an external record like https/udspace.udel.edu/rest/handle/19716/28599 that is not set up for CORS, but I would like to get access to it in my application. In the past, I have set up a server to simply GET the resource and redeliver it for my application. However, I read about Node http-proxy-middleware and thought I'd try out a more direct proxy.
First, I could not use the target in the createProxyMiddleware() constructor because I wanted to send in the hostname and path of the desired resource like, perhaps, http://example.com/https/udspace.udel.edu/rest/handle/19716/28599 to get the resource above.
Relevant Code (index.js)
const express = require('express')
const { createProxyMiddleware } = require('http-proxy-middleware')
const app = express()
app.get('/info', (req, res, next) => {
res.send('Proxy is up.');
})
// Proxy endpoint
app.use('/https', createProxyMiddleware({
target: "incoming",
router: req => {
const protocol = 'https'
const hostname = req.path.split("/https/")[1].split("/")[0]
const path = req.path.split(hostname)[1]
console.log(`returning: ${protocol}, ${hostname}, ${path}`)
return `${protocol}://${hostname}/${path}`
},
changeOrigin: true
}))
app.listen(PORT, HOST, () => {
console.log(`Starting Proxy at ${HOST}:${PORT}`);
})
I was getting a 404 from the DSpace server without other information, so I knew that the request was going through, but transformed incorrectly. I tried again with an item in an S3 bucket, since AWS gives better errors and saw that my path was being duplicated:
404 Not Found
Code: NoSuchKey
Message: The specified key does not exist.
Key: api/cookbook/recipe/0001-mvm-image/manifest.json/https/iiif.io/api/cookbook/recipe/0001-mvm-image/manifest.json
What dumb thing am I doing wrong? Is this not what this proxy is for and I need to do something else?

How can I get socket.io to work with deno?

My aim is to get socket.io to work with deno. Deno does have a standard library for web sockets, but that does not have a fail-over.
I think it is possible to use the UNPKG service to use an NPM package in you deno program, but I seem to get the syntax wrong for importing socket.io:
import { serve } from "https://deno.land/std/http/server.ts";
import {Socket} from "https://unpkg.com/browse/socket.io#3.0.1/dist/index.d.ts";
new Worker(new URL("worker.js", import.meta.url).href, { type: "module" });
const server = serve({ port: 3001 });
const app = new Application();
const io = Socket(3001);
// serve index page
if (req.url === "/") {
req.respond({
status: 200,
body: await Deno.open("./public/index.html"),
});
}
io.on("connection", (socket) => {
// either with send()
socket.send("Hello!");
// or with emit() and custom event names
socket.emit("greetings", "Hey!", { "ms": "jane" }, Buffer.from([4, 3, 3, 1]));
// handle the event sent with socket.send()
socket.on("message", (data) => {
console.log(data);
});
// handle the event sent with socket.emit()
socket.on("salutations", (elem1, elem2, elem3) => {
console.log(elem1, elem2, elem3);
});
});
I get the following error:
error: An unsupported media type was attempted to be imported as a module.
Specifier: https://unpkg.com/browse/socket.io#3.0.1/dist/index.d.ts
MediaType: Unknown
It fails because you are importing a .d.ts file, which is a declaration file and essentially you can't run any code from - it's purely to support types.
Instead you should replace index.d.ts with index.js
Try this:
import Socket from 'https://cdn.esm.sh/v9/socket.io#3.0.3/esnext/socket.io.js';
As #Indecisive said, you are importing a .d.ts file.
You can use #Marcos Casagrande: https://stackoverflow.com/a/61821141/6250402 (remember install socket.io module with npm)
Or use https://deno.land/std#0.79.0/ws/mod.ts

How to set up a socket connection on a strapi server

I am trying to integrate socket.io with strapi. But unfortunately I have been unable to do so without any proper tutorial or documentation covering this aspect.
I followed along with the only resource I found online which is:
https://medium.com/strapi/strapi-socket-io-a9c856e915a6
But I think the article is outdated. I can't seem to run the code mentioned in it without running into tonnes of errors.
Below is my attempt to implement it and I have been trying to connect it through a chrome websocket plugin smart websocket client But I am not getting any response when I try to run the server.
I'm totally in the dark. Any help will be appreciated
module.exports = ()=> {
// import socket io
var io = require('socket.io')(strapi.server)
console.log(strapi.server) //undefined
// listen for user connection
io.on('connect', socket => {
socket.send('Hello!');
console.log("idit")
// or with emit() and custom event names
socket.emit('greetings', 'Hey!', { 'ms': 'jane' }, Buffer.from([4, 3, 3, 1]));
// handle the event sent with socket.send()
socket.on('message', (data) => {
console.log(data);
});
// handle the event sent with socket.emit()
socket.on('salutations', (elem1, elem2, elem3) => {
console.log(elem1, elem2, elem3);
});
});
};
So I found the solution. Yay. I'll put it here just in case anybody needs it.
boostrap.js
module.exports = async () => {
process.nextTick(() =>{
var io = require('socket.io')(strapi.server);
io.on('connection', async function(socket) {
console.log(`a user connected`)
// send message on user connection
socket.emit('hello', JSON.stringify({message: await strapi.services.profile.update({"posted_by"})}));
// listen for user diconnect
socket.on('disconnect', () =>{
console.log('a user disconnected')
});
});
strapi.io = io; // register socket io inside strapi main object to use it globally anywhere
})
};
Found this at: https://github.com/strapi/strapi/issues/5869#issuecomment-619508153_
Apparently, socket.server is not available when the server starts. So you have to make use of process.nextTick that waits for the socket.server to initialize.
I'll also add a few questions that I faced when setting this up.
How do i connect from an external client like nuxt,vue or react?
You just have to connect through "http://localhost:1337" that is my usual address for strapi.
I am using nuxt as my client side and this is how set up my socketio on the client side
I first installed nuxt-socket-io through npm
Edited the nuxt.config file as per it's documention
modules:[
...
'nuxt-socket-io',
...
],
io: {
// module options
sockets: [
{
name: 'main',
url: 'http://localhost:1337',
},
],
},
And then i finally added a listener in one of my pages.
created() {
this.socket = this.$nuxtSocket({})
this.socket.on('hello', (msg, cb) => {
console.log('SOCKET HI')
console.log(msg)
})
},
And it works.
A clean way to integrate third-party services into Strapi is to use hooks. They are loaded once during the server boot. In this case, we will create a local hook.
The following example has worked with strapi#3.6.
Create a hook for socket.io at ./hooks/socket.io/index.js
module.exports = strapi => {
return {
async initialize() {
const ioServer = require('socket.io')(strapi.server, {
cors: {
origin: process.env['FRONT_APP_URL'],
methods: ['GET', 'POST'],
/* ...other cors options */
}
})
ioServer.on('connection', function(socket) {
socket.emit('hello', `Welcome ${socket.id}`)
})
/* HANDLE CLIENT SOCKET LOGIC HERE */
// store the server.io instance to global var to use elsewhere
strapi.services.ioServer = ioServer
},
}
}
Enable the new hook in order for Strapi to load it - ./config/hook.js
module.exports = {
settings: {
'socket.io': {
enabled: true,
},
},
};
That's done. You can access the websocket server inside ./config/functions/bootstrap.js or models' lifecycle hooks.
// ./api/employee/models/employee.js
module.exports = {
lifecycles: {
async afterUpdate(result, params, data) {
strapi.services.ioServer.emit('update:employee', result)
},
},
};
For those who are looking the answer using Strapi version 4
var io = require("socket.io")(strapi.server.httpServer)

AWS CDK passing API Gateway URL to static site in same Stack

I'm trying to deploy an S3 static website and API gateway/lambda in a single stack.
The javascript in the S3 static site calls the lambda to populate an HTML list but it needs to know the API Gateway URL for the lambda integration.
Currently, I generate a RestApi like so...
const handler = new lambda.Function(this, "TestHandler", {
runtime: lambda.Runtime.NODEJS_10_X,
code: lambda.Code.asset("build/test-service"),
handler: "index.handler",
environment: {
}
});
this.api = new apigateway.RestApi(this, "test-api", {
restApiName: "Test Service"
});
const getIntegration = new apigateway.LambdaIntegration(handler, {
requestTemplates: { "application/json": '{ "statusCode": "200" }' }
});
const apiUrl = this.api.url;
But on cdk deploy, apiUrl =
"https://${Token[TOKEN.39]}.execute-api.${Token[AWS::Region.4]}.${Token[AWS::URLSuffix.1]}/${Token[TOKEN.45]}/"
So the url is not parsed/generated until after the static site requires the value.
How can I calculate/find/fetch the API Gateway URL and update the javascript on cdk deploy?
Or is there a better way to do this? i.e. is there a graceful way for the static javascript to retrieve a lambda api gateway url?
Thanks.
You are creating a LambdaIntegration but it isn't connected to your API.
To add it to the root of the API do: this.api.root.addMethod(...) and use this to connect your LambdaIntegration and API.
This should give you an endpoint with a URL
If you are using the s3-deployment module to deploy your website as well, I was able to hack together a solution using what is available currently (pending a better solution at https://github.com/aws/aws-cdk/issues/12903). The following together allow for you to deploy a config.js to your bucket (containing attributes from your stack that will only be populated at deploy time) that you can then depend on elsewhere in your code at runtime.
In inline-source.ts:
// imports removed for brevity
export function inlineSource(path: string, content: string, options?: AssetOptions): ISource {
return {
bind: (scope: Construct, context?: DeploymentSourceContext): SourceConfig => {
if (!context) {
throw new Error('To use a inlineSource, context must be provided');
}
// Find available ID
let id = 1;
while (scope.node.tryFindChild(`InlineSource${id}`)) {
id++;
}
const bucket = new Bucket(scope, `InlineSource${id}StagingBucket`, {
removalPolicy: RemovalPolicy.DESTROY
});
const fn = new Function(scope, `InlineSource${id}Lambda`, {
runtime: Runtime.NODEJS_12_X,
handler: 'index.handler',
code: Code.fromAsset('./inline-lambda')
});
bucket.grantReadWrite(fn);
const myProvider = new Provider(scope, `InlineSource${id}Provider`, {
onEventHandler: fn,
logRetention: RetentionDays.ONE_DAY // default is INFINITE
});
const resource = new CustomResource(scope, `InlineSource${id}CustomResource`, { serviceToken: myProvider.serviceToken, properties: { bucket: bucket.bucketName, path, content } });
context.handlerRole.node.addDependency(resource); // Sets the s3 deployment to depend on the deployed file
bucket.grantRead(context.handlerRole);
return {
bucket: bucket,
zipObjectKey: 'index.zip'
};
},
};
}
In inline-lambda/index.js (also requires archiver installed into inline-lambda/node_modules):
const aws = require('aws-sdk');
const s3 = new aws.S3({ apiVersion: '2006-03-01' });
const fs = require('fs');
var archive = require('archiver')('zip');
exports.handler = async function(event, ctx) {
await new Promise(resolve => fs.unlink('/tmp/index.zip', resolve));
const output = fs.createWriteStream('/tmp/index.zip');
const closed = new Promise((resolve, reject) => {
output.on('close', resolve);
output.on('error', reject);
});
archive.pipe(output);
archive.append(event.ResourceProperties.content, { name: event.ResourceProperties.path });
archive.finalize();
await closed;
await s3.upload({Bucket: event.ResourceProperties.bucket, Key: 'index.zip', Body: fs.createReadStream('/tmp/index.zip')}).promise();
return;
}
In your construct, use inlineSource:
export class TestConstruct extends Construct {
constructor(scope: Construct, id: string, props: any) {
// set up other resources
const source = inlineSource('config.js', `exports.config = { apiEndpoint: '${ api.attrApiEndpoint }' }`);
// use in BucketDeployment
}
}
You can move inline-lambda elsewhere but it needs to be able to be bundled as an asset for the lambda.
This works by creating a custom resource that depends on your other resources in the stack (thereby allowing for the attributes to be resolved) that writes your file into a zip that is then stored into a bucket, which is then picked up and unzipped into your deployment/destination bucket. Pretty complicated but gets the job done with what is currently available.
The pattern I've used successfully is to put a CloudFront distribution or an API Gateway in front of the S3 bucket.
So requests to https://[api-gw]/**/* are proxied to https://[s3-bucket]/**/*.
Then I will create a new Proxy path in the same API gateway, for the route called /config which is a standard Lambda-backed API endpoint, where I can return all sorts of things like branding information or API keys to the frontend, whenever the frontend calls GET /config.
Also, this avoids issues like CORS, because both origins are the same (the API Gateway domain).
With CloudFront distribution instead of an API Gateway, it's pretty much the same, except you use the CloudFront distribution's "origin" configuration instead of paths and methods.

Log apollo-server GraphQL query and variables per request

When using apollo-server 2.2.1 or later, how can one log, for each request, the query and the variables?
This seems like a simple requirement and common use case, but the documentation is very vague, and the query object passed to formatResponse no longer has the queryString and variables properties.
Amit's answer works (today), but IMHO it is a bit hacky and it may not work as expected in the future, or it may not work correctly in some scenarios.
For instance, the first thing that I thought when I saw it was: "that may not work if the query is invalid", it turns out that today it does work when the query is invalid. Because with the current implementation the context is evaluated before the the query is validated. However, that's an implementation detail that can change in the future. For instance, what if one day the apollo team decides that it would be a performance win to evaluate the context only after the query has been parsed and validated? That's actually what I was expecting :-)
What I'm trying to say is that if you just want to log something quick in order to debug something in your dev environment, then Amit's solution is definitely the way to go.
However, if what you want is to register logs for a production environment, then using the context function is probably not the best idea. In that case, I would install the graphql-extensions and I would use them for logging, something like:
const { print } = require('graphql');
class BasicLogging {
requestDidStart({queryString, parsedQuery, variables}) {
const query = queryString || print(parsedQuery);
console.log(query);
console.log(variables);
}
willSendResponse({graphqlResponse}) {
console.log(JSON.stringify(graphqlResponse, null, 2));
}
}
const server = new ApolloServer({
typeDefs,
resolvers,
extensions: [() => new BasicLogging()]
});
Edit:
As Dan pointed out, there is no need to install the graphql-extensions package because it has been integrated inside the apollo-server-core package.
With the new plugins API, you can use a very similar approach to Josep's answer, except that you structure the code a bit differently.
const BASIC_LOGGING = {
requestDidStart(requestContext) {
console.log("request started");
console.log(requestContext.request.query);
console.log(requestContext.request.variables);
return {
didEncounterErrors(requestContext) {
console.log("an error happened in response to query " + requestContext.request.query);
console.log(requestContext.errors);
}
};
},
willSendResponse(requestContext) {
console.log("response sent", requestContext.response);
}
};
const server = new ApolloServer(
{
schema,
plugins: [BASIC_LOGGING]
}
)
server.listen(3003, '0.0.0.0').then(({ url }) => {
console.log(`GraphQL API ready at ${url}`);
});
If I had to log the query and variables, I would probably use apollo-server-express, instead of apollo-server, so that I could add a separate express middleware before the graphql one that logged that for me:
const express = require('express')
const { ApolloServer } = require('apollo-server-express')
const { typeDefs, resolvers } = require('./graphql')
const server = new ApolloServer({ typeDefs, resolvers })
const app = express()
app.use(bodyParser.json())
app.use('/graphql', (req, res, next) => {
console.log(req.body.query)
console.log(req.body.variables)
return next()
})
server.applyMiddleware({ app })
app.listen({ port: 4000}, () => {
console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`)
})
Dan's solution mostly resolves the problem but if you want to log it without using express,
you can capture it in context shown in below sample.
const server = new ApolloServer({
schema,
context: params => () => {
console.log(params.req.body.query);
console.log(params.req.body.variables);
}
});
I found myself needing something like this but in a more compact form - just the query or mutation name and the ID of the user making the request. This is for logging queries in production to trace what the user was doing.
I call logGraphQlQueries(req) at the end of my context.js code:
export const logGraphQlQueries = ( req ) => {
// the operation name is the first token in the first line
const operationName = req.body.query.split(' ')[0];
// the query name is first token in the 2nd line
const queryName = req.body.query
.split('\n')[1]
.trim()
.split(' ')[0]
.split('(')[0];
// in my case the user object is attached to the request (after decoding the jwt)
const userString = req.user?.id
? `for user ${req.user.id}`
: '(unauthenticated)';
console.log(`${operationName} ${queryName} ${userString}`);
};
This outputs lines such as:
query foo for user e0ab63d9-2513-4140-aad9-d9f2f43f7744
Apollo Server exposes a request lifecycle event called didResolveOperation at which point the requestContext has populated properties called operation and operationName
plugins: [
{
requestDidStart(requestContext) {
return {
didResolveOperation({ operation, operationName }) {
const operationType = operation.operation;
console.log(`${operationType} recieved: ${operationName}`)
}
};
}
}
]
// query recieved: ExampleQuery
// mutation recieved: ExampleMutation

Resources