Request handler unable to render view with Vision plugin - javascript

I'm working on a HapiJS api and I've defined a plugin that registers the Vision plugin and configures a rendering engine (ejs). But when I try and respond to the request by rendering a view I get the error
AssertionError [ERR_ASSERTION]: Missing views manager
If I register the Vision plugin and its config somewhere outside the plugin the tests pass but my expectation is that I should be able to encapsulate this logic in a plugin.
// plugin
const ejs = require('ejs');
const Vision = require('vision');
module.exports = {
name: 'views',
version: '0.0.1',
register: async (server, { path }) => {
await server.register(Vision);
server.views({
engines: { ejs },
path,
});
},
};
The handler code is
// api
server.route({
path: '/korin/songs',
method: 'GET',
handler: async (request, h) => {
try {
const acceptType = getMediaType(request.headers.accept);
const data = await server.methods.getTopTracks({
getTopTracks,
lastfmApi,
});
if (acceptType === 'text/html') {
return h.view('index'); // <-- this errors
}
return data;
} catch (error) {
console.warn(error);
}
},
});
The error is generated by a failing test which is
suite('render content', () => {
test.only(`given text/html page should respond with header and footer`, async () => {
const { server } = await setup();
const { payload } = await server.inject({
method: 'GET',
url: '/korin/songs',
headers: {
accept: 'text/html',
},
});
expect(payload).to.contain(`<header>`);
expect(payload).to.contain(`<footer>`);
});
});
// test setup
const setup = async options => {
const server = new Hapi.Server();
// truncated for brevity
await server.register({
plugin: require('../../server/api'),
options: {
...defaults,
...options,
},
});
await server.register({
plugin: require('../../server/views'),
options: { path: path.join(__dirname, '../views/templates') },
});
return {
server
};
};
Is there something I'm missing? I've tried running a console.log and the code seems to be running in the right order but failing anyway.

There is an old thread on GitHub about this. TL;DR: the reference to server passed to the plugin when registering is slightly not the same as the "root" server. Some difference about realms, apparently still an issue.
Indeed: in the plugin, server.getViewsManager() (decorated by vision) after registration of vision and server.views will show something, whereas the same call in your route (so, after plugin registration) will show null. So much for "references".
I just tried a similar structure to you, got the same error, and this thread pointed me to a workaround: when registering your views plugin, just pass along a reference to the "real" server in the options.
// plugin
const ejs = require('ejs');
const Vision = require('vision');
module.exports = {
name: 'views',
version: '0.0.1',
register: async (server, { path, realServer }) => { // <= added option
await realServer.register(Vision); // <= replaced server w/ realServer
realServer.views({ // <= replaced server w/ realServer
engines: { ejs },
path,
});
},
};
// test setup
// ...
const server = new Hapi.Server();
// ...
await server.register({
plugin: require('../../server/views'),
options: {
path: path.join(__dirname, '../views/templates'),
realServer: server // <= added option
}
});
And, obviously, have the same options everywhere you register this plugin.

Related

Unit Testing with Jest for Strapi v4

I am trying to perform unit tests with Jest for the new version of Strapi, v4 which was just released a couple of weeks ago. In accordance with their documentation, the old guide for unit testing no longer runs as expected. I have, however, modified the code to work to a certain extent. Currently I have the following:
./test/helpers/strapi.js:
const Strapi = require("#strapi/strapi");
let instance;
async function setupStrapi() {
if (!instance) {
/** the following code in copied from `./node_modules/strapi/lib/Strapi.js` */
await Strapi().load();
instance = strapi; // strapi is global now
await instance.server
.use(instance.server.router.routes()) // populate KOA routes
.use(instance.server.router.allowedMethods()); // populate KOA methods
await instance.server.mount();
}
return instance;
}
module.exports = {
setupStrapi
};
./tests/app.test.js:
const fs = require("fs");
const { setupStrapi } = require("./helpers/strapi");
beforeAll(async () => {
await setupStrapi();
});
afterAll(async () => {
const dbSettings = strapi.config.get("database.connection.connection");
//close server to release the db-file
await strapi.server.destroy();
//DATABASE_FILENAME=.tmp/test.db
//delete test database after all tests
if (dbSettings && dbSettings.filename) {
const tmpDbFile = `${dbSettings.filename}`;
if (fs.existsSync(tmpDbFile)) {
fs.unlinkSync(tmpDbFile);
}
}
});
it("should return hello world", async () => {
await request(strapi.server.httpServer).get("/api/hello").expect(200); // Expect response http code 200
});
./config/env/test/database.js
const path = require("path");
module.exports = ({ env }) => ({
connection: {
client: "sqlite",
connection: {
filename: path.join(
__dirname,
"../../../",
env("DATABASE_FILENAME", ".tmp/test.db")
),
},
useNullAsDefault: true,
},
});
The route /api/hello is a custom API endpoint. This works perfectly when running strapi develop, and all permissions are set correctly.
The tests run, but every endpoint that is not / or /admin returns 403 Forbidden, meaning there is a problem with the permissions. It would seem that the database file .tmp/data.db (used in development) is not replicated correctly in .tmp/test.db. In other words, this is close to working, but the permissions for API endpoints are not set correctly.
I have been searching through StackOverflow and the Stapi Forums over the past few days but to no avail. I would greatly appreciate some pointers as to how to fix this :)
It seems you need to grant the right privileges to your routes on your test DB.
For that you can create a function, lets call it grantPriviledge, and call it in your test in the function beforeAll.
// Here I want to grant the route update in my organization collection
beforeAll(async () => {
await grantPrivilege(1, 'permissions.application.controllers.organization.update');
});
And here is the function grantPriviledge:
// roleID is 1 for authenticated and 2 for public
const grantPrivilege = async (roleID = 1, value, enabled = true, policy = '') => {
const updateObj = value
.split('.')
.reduceRight((obj, next) => ({ [next]: obj }), { enabled, policy });
const roleName = roleID === 1 ? 'Authenticated' : 'Public';
const roleIdInDB = await strapi
.query('role', 'users-permissions')
.findOne({ name: roleName });
return strapi.plugins['users-permissions'].services.userspermissions.updateRole(
roleIdInDB,
updateObj,
);
};
Let me know if that helps
So in order for that to work in v4, this is how I did.
This was based in this and this but Stf's posting in this saying "inject them in your database during the bootstrap phase like it is made in the templates" was what really set me in the right track.
So if you look here you will see this function:
async function setPublicPermissions(newPermissions) {
// Find the ID of the public role
const publicRole = await strapi
.query("plugin::users-permissions.role")
.findOne({
where: {
type: "public",
},
});
// Create the new permissions and link them to the public role
const allPermissionsToCreate = [];
Object.keys(newPermissions).map((controller) => {
const actions = newPermissions[controller];
const permissionsToCreate = actions.map((action) => {
return strapi.query("plugin::users-permissions.permission").create({
data: {
action: `api::${controller}.${controller}.${action}`,
role: publicRole.id,
},
});
});
allPermissionsToCreate.push(...permissionsToCreate);
});
await Promise.all(allPermissionsToCreate);
}
Later on the code, this function is called like this:
await setPublicPermissions({
article: ["find", "findOne"],
category: ["find", "findOne"],
author: ["find", "findOne"],
global: ["find", "findOne"],
about: ["find", "findOne"],
});
So in my case I modified this function a bit to accept between authenticated (1) and public (2) roles inspired by Sidney C answer above.
This is how I did it:
const grantPrivilege = async (roleID = 1, newPermissions) => {
const roleName = roleID === 1 ? "authenticated" : "public";
// Find the ID of the public role
const roleEntry = await strapi
.query("plugin::users-permissions.role")
.findOne({
where: {
type: roleName,
},
});
// Create the new permissions and link them to the public role
const allPermissionsToCreate = [];
Object.keys(newPermissions).map((controller) => {
const actions = newPermissions[controller];
const permissionsToCreate = actions.map((action) => {
return strapi.query("plugin::users-permissions.permission").create({
data: {
action: `api::${controller}.${controller}.${action}`,
role: roleEntry.id,
},
});
});
allPermissionsToCreate.push(...permissionsToCreate);
});
await Promise.all(allPermissionsToCreate);
};
And then in my beforeAll block I call it like this:
await grantPrivilege(1, {
"my-custom-collection": ["create", "update"],
category: ["find", "findOne"],
author: ["find", "findOne"],
});

Node/Express: Use a function request from another file

Firstly, I'm a frontend developer so I'm sorry if I use wrong terms in my explanations.
In my company, we are actually building 1 web app and 2 API apps. So the users use the web app which talks to the first API which talks to the second API.
Here, we are in the first API, in the server.js file:
server.js
---------
var app = express();
const cats = require("./api/cats");
app.get("/animals/cats", cats.listTheCats); // listTheCats() returns an array of cats
In cats.js, we can see with listTheCats() we are sending another request to the second API:
cats.js
-------
const listTheCats = (req, res) => {
axios({
method: "get",
url: "http://second-api-url.com/animals/cats",
params: req.query,
})
.then((ans) => {
res.status(ans.data.status).json(ans.data.data);
})
.catch((err) => {
console.log(err);
res.sendStatus(503);
});
};
module.exports = listTheCats;
The code above works fine on the web app. But now, in the first api, in another file called "cuteAnimals.js" I need to call listTheCats(). So I tried to do this but it doesn't work:
cuteAnimals.js
--------------
const { listTheCats } = require("./cats");
const fetchCats = async () => {
const params = {
type: "cute",
};
const cuteCats = await axios.get(`animals/cats`, {
params,
});
// or const cuteCats = await listTheCats(params);
console.log("cuteCats", cuteCats);
};
fetchCats();
This is the error: "Request failed with status code 400"
In cuteAnimals.js, is it right to use axios from a file to another file of the same server project?
You need to export the function in order to use it in another file, you can do it simply by writing this line at the end of cats.js
module.exports = listTheCats

A metric with the name has already been registered | prometheus custom metric in nodejs app

Getting metric has already been registered when trying to publish metrics from service. To avoid that I used register.removeSingleMetric("newMetric"); but problem is that it clear register and past records every time a new call comes in.
Wondering what to address this problem:
export function workController(req: Request, res: Response) {
resolveFeaturePromises(featurePromises).then(featureAggregate => {
return rulesService.runWorkingRulesForHook(featureAggregate, hook.name, headers)
.then(([shouldLog, result]) => {
...
// publish metric
publishHookMetrics(result);
// publish metric
sendResponse(res, new CoreResponse(result.isWorking, result.responseData), req, featureAggregate, hook);
});
}).catch(err => {
sendResponse(res, new CoreResponse(Action.ALLOW, null), req, null, hook);
console.error(err);
});
}
function publishCustomMetrics(customInfoObject: CustomInfoObject) {
const counter = new promClient.Counter({
name: "newMetric",
help: "metric for custom detail",
labelNames: ["name", "isWorking"]
});
counter.inc({
name: customInfoObject.hook,
isWorking: customInfoObject.isWorking
});
}
stack trace
[Node] [2020-07-30T17:40:09+0500] [ERROR] Error: A metric with the name newMetric has already been registered.
application.ts
export async function startWebServer(): Promise<Server> {
if (!isGlobalsInitilized) {
throw new Error("Globals are noit initilized. Run initGlobals() first.");
}
// Setup prom express middleware
const metricsMiddleware = promBundle({
includeMethod: true,
includePath: true,
metricsPath: "/prometheus",
promClient: {
collectDefaultMetrics: {
}
}
});
// start http server
const app = require("express")();
app.use(bodyParser.json());
app.use(metricsMiddleware);
const routeConfig = require("./config/route-config");
routeConfig.configure(app);
const port = process.env.PORT || 3000;
return app.listen(port, function () {
console.log("service listening on port", port);
});
}
versions:
express-prom-bundle: 6.0.0
prom-client: 12.0.0
The counter should be initialize only once, then used for each call. Simplest modification from code you gave would be something like this.
const counter = new promClient.Counter({
name: "newMetric",
help: "metric for custom detail",
labelNames: ["name", "isWorking"]
});
export function publishCustomMetrics(customInfoObject: CustomInfoObject) {
counter.inc({
name: customInfoObject.hook,
isWorking: customInfoObject.isWorking
});
}
BTW, I found this thread while searching a way to avoid the "already been registered" error in unit tests. Many tests was instantiating the same class (from a library) that was initializing a counter in the constructor.
As it is in tests, and I didn't need consistency over the metrics, I found an easy solution is to clear metrics registers at the beginning of each test.
import { register } from "prom-client";
// ...
register.clear();

Using react hooks inside NextJS /pages/api [duplicate]

I need a graphql client lib to run on node.js for some testing and some data mashup - not in a production capacity. I'm using apollo everywhere else (react-apollo, apollo's graphql-server-express). My needs are pretty simple.
Is apollo-client a viable choice? I can find no examples or docs on using it on node - if you're aware of any, please share.
Or maybe I should/can use the reference graphql client on node?
Apollo Client should work just fine on Node. You only have to install cross-fetch.
Here is a complete TypeScript implementation of Apollo Client working on Node.js.
import { ApolloClient, gql, HttpLink, InMemoryCache } from "#apollo/client";
import { InsertJob } from "./graphql-types";
import fetch from "cross-fetch";
const client = new ApolloClient({
link: new HttpLink({ uri: process.env.PRODUCTION_GRAPHQL_URL, fetch }),
cache: new InMemoryCache(),
});
client.mutate<InsertJob.AddCompany, InsertJob.Variables>({
mutation: gql`mutation insertJob($companyName: String!) {
addCompany(input: { displayName: $companyName } ) {
id
}
}`,
variables: {
companyName: "aaa"
}
})
.then(result => console.log(result));
Newer Apollo version provide a simpler approach to perform this, as described in Apollo docs, check the section "Standalone". Basically one can simply use ApolloLink in order to perform a query or mutation.
Below is copy of the example code from the docs as of writing this, with node-fetch usage as config to createHttpLink. Check the docs for more details on how to use these tools.
import { execute, makePromise } from 'apollo-link';
import { createHttpLink } from 'apollo-link-http';
import gql from 'graphql-tag';
import fetch from 'node-fetch';
const uri = 'http://localhost:4000/graphql';
const link = createHttpLink({ uri, fetch });
const operation = {
query: gql`query { hello }`,
variables: {} //optional
operationName: {} //optional
context: {} //optional
extensions: {} //optional
};
// execute returns an Observable so it can be subscribed to
execute(link, operation).subscribe({
next: data => console.log(`received data: ${JSON.stringify(data, null, 2)}`),
error: error => console.log(`received error ${error}`),
complete: () => console.log(`complete`),
})
// For single execution operations, a Promise can be used
makePromise(execute(link, operation))
.then(data => console.log(`received data ${JSON.stringify(data, null, 2)}`))
.catch(error => console.log(`received error ${error}`))
If someone is looking for a JavaScript version:
require('dotenv').config();
const gql = require('graphql-tag');
const ApolloClient = require('apollo-boost').ApolloClient;
const fetch = require('cross-fetch/polyfill').fetch;
const createHttpLink = require('apollo-link-http').createHttpLink;
const InMemoryCache = require('apollo-cache-inmemory').InMemoryCache;
const client = new ApolloClient({
link: createHttpLink({
uri: process.env.API,
fetch: fetch
}),
cache: new InMemoryCache()
});
client.mutate({
mutation: gql`
mutation popJob {
popJob {
id
type
param
status
progress
creation_date
expiration_date
}
}
`,
}).then(job => {
console.log(job);
})
You can make apollo-client work, but it's not the best option for this use case.
Try graphql-request instead.
Minimal GraphQL client supporting Node and browsers for scripts or simple apps
Features per npmjs:
Most simple & lightweight GraphQL client
Promise-based API (works with async / await)
Typescript support
Isomorphic (works with Node / browsers)
example:
import { request, gql } from 'graphql-request'
const query = gql`
{
Movie(title: "Inception") {
releaseDate
actors {
name
}
}
}
`
request('https://api.graph.cool/simple/v1/movies', query).then((data) => console.log(data))
I have no affiliation with this package.
Here is simple node js implementation.
'graphiql' client is good enough for development activities.
1. run npm install
2. start server with "node server.js"
3. hit "http://localhost:8080/graphiql" for graphiql client
server.js
var graphql = require ('graphql').graphql
var express = require('express')
var graphQLHTTP = require('express-graphql')
var Schema = require('./schema')
// This is just an internal test
var query = 'query{starwar{name, gender,gender}}'
graphql(Schema, query).then( function(result) {
console.log(JSON.stringify(result,null," "));
});
var app = express()
.use('/', graphQLHTTP({ schema: Schema, pretty: true, graphiql: true }))
.listen(8080, function (err) {
console.log('GraphQL Server is now running on localhost:8080');
});
schema.js
//schema.js
var graphql = require ('graphql');
var http = require('http');
var StarWar = [
{
"name": "default",
"gender": "default",
"mass": "default"
}
];
var TodoType = new graphql.GraphQLObjectType({
name: 'starwar',
fields: function () {
return {
name: {
type: graphql.GraphQLString
},
gender: {
type: graphql.GraphQLString
},
mass: {
type: graphql.GraphQLString
}
}
}
});
var QueryType = new graphql.GraphQLObjectType({
name: 'Query',
fields: function () {
return {
starwar: {
type: new graphql.GraphQLList(TodoType),
resolve: function () {
return new Promise(function (resolve, reject) {
var request = http.get({
hostname: 'swapi.co',
path: '/api/people/1/',
method: 'GET'
}, function(res){
res.setEncoding('utf8');
res.on('data', function(response){
StarWar = [JSON.parse(response)];
resolve(StarWar)
console.log('On response success:' , StarWar);
});
});
request.on('error', function(response){
console.log('On error' , response.message);
});
request.end();
});
}
}
}
}
});
module.exports = new graphql.GraphQLSchema({
query: QueryType
});
In response to #YakirNa 's comment:
I can't speak to the other needs I described, but I have done a fair amount of testing. I ended up doing all of my testing in-process.
Most testing ends up being resolver testing, which I do via a jig that invokes the graphql library's graphql function with a test query and then validates the response.
I also have an (almost) end-to-end test layer that works at the http-handling level of express. It creates a fake HTTP request and verifies the response in-process. This is all within the server process; nothing goes over the wire. I use this lightly, mostly for testing JWT authentication and other request-level behavior that's independent of the graphql request body.
I was running into your same question, because I wanted to create a middleware service to prepare data from graphQL to a final frontend application,
to have :
optimised data representation (and standard output data interface)
faster response time
assuming that graphQL server is provided by an external provider , so no ownership to data model, directly with GQL
So I didn't want to implement GraphQL Apolloclient directly in a frontend framework like React / Angular, Vuejs... but manage the queries via Nodejs at backend of a REST API.
So this is the class wrapper for Apolloclient I was able to assemble (using typescript):
import ApolloClient from "apollo-client";
import { ApolloLink } from 'apollo-link'
import { HttpLink } from 'apollo-link-http'
import { onError } from 'apollo-link-error'
import fetch from 'node-fetch'
import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory'
import introspectionQueryResultData from '../../fragmentTypes.json';
import { AppConfig } from 'app-config';
const config: AppConfig = require('../../../appConfig.js');
export class GraphQLQueryClient {
protected apolloClient: any;
constructor(headers: { [name: string]: string }) {
const api: any = {
spaceId: config.app.spaceId,
environmentId: config.app.environmentId,
uri: config.app.uri,
cdnApiPreviewToken: config.cdnApiPreviewToken,
};
// console.log(JSON.stringify(api));
const ACCESS_TOKEN = api.cdnApiPreviewToken;
const uri = api.uri;
console.log(`Apollo client setup to query uri: ${uri}`);
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData
});
this.apolloClient = new ApolloClient({
link: ApolloLink.from([
onError(({ graphQLErrors, networkError }:any) => {
if (graphQLErrors) {
graphQLErrors.map((el:any) =>
console.warn(
el.message || el
)
)
graphQLErrors.map(({ message, locations, path }:any) =>
console.warn(
`[GraphQL error - Env ${api.environmentId}]: Message: ${message}, Location: ${JSON.stringify(locations)}, Path: ${path}`
)
)
}
if (networkError) console.log(`[Network error]: ${networkError}`)
}),
new HttpLink({
uri,
credentials: 'same-origin',
headers: {
Authorization: `Bearer ${ACCESS_TOKEN}`
},
fetch
})
]),
cache: new InMemoryCache({ fragmentMatcher }),
// fetchPolicy as network-only avoids using the cache.
defaultOptions: {
watchQuery: {
fetchPolicy: 'network-only',
errorPolicy: 'ignore',
},
query: {
fetchPolicy: 'network-only',
errorPolicy: 'all',
},
}
});
}
}
After this constructor I run queries like :
let response = await this.apolloClient.query({ query: gql`${query}` });
As you might have noticed:
I needed to inject fetch on Httplink
I had to setup Authorization headers to access external provider graphQL endpoint
I used IntrospectionFragmentMatcher in order to use Fragments in my queries, along with building schema type ("fragmentTypes.json" with an init script)
Posting this to just add my experience and maybe more info for the question.
Also looking forward for comments and points of improvement for this wrapper.

Electron: How to securely inject global variable into BrowserWindow / BrowserView?

I want to load an external webpage in Electron using BrowserView. It has pretty much the same API as BrowserWindow.
const currentWindow = remote.getCurrentWindow();
const view = new remote.BrowserView({
webPreferences: {
// contextIsolation: true,
partition: 'my-view-partition',
enableRemoteModule: false,
nodeIntegration: false,
preload: `${__dirname}/preload.js`,
sandbox: true,
},
});
view.setAutoResize({ width: true, height: true });
view.webContents.loadURL('http://localhost:3000');
In my preload.js file, I simply attach a variable to the global object.
process.once('loaded', () => {
global.baz = 'qux';
});
The app running on localhost:3000 is a React app which references the value like this:
const sharedString = global.baz || 'Not found';
The problem is I have to comment out the setting contextIsolation: true when creating the BrowserView. This exposes a security vulnerability.
Is it possible to (one way - from Electron to the webpage) inject variables into a BrowserView (or BrowserWindow) while still using contextIsolation to make the Electron environment isolated from any changes made to the global environment by the loaded content?
Update:
One possible approach could be intercepting the network protocol, but I'm not sure about this 🤔
app.on('ready', () => {
const { protocol } = session.fromPartition('my-partition')
protocol.interceptBufferProtocol('https', (req, callback) => {
if (req.uploadData) {
// How to handle file uploads?
callback()
return
}
// This is electron.net, docs: https://electronjs.org/docs/api/net
net
.request(req)
.on('response', (res) => {
const chunks = []
res.on('data', (chunk) => {
chunks.push(Buffer.from(chunk))
})
res.on('end', () => {
const blob = Buffer.concat(chunks)
const type = res.headers['content-type'] || []
if (type.includes('text/html') && blob.includes('<head>')) {
// FIXME?
const pos = blob.indexOf('<head>')
// inject contains the Buffer with the injected HTML script
callback(Buffer.concat([blob.slice(0, pos), inject, blob.slice(pos)]))
} else {
callback(blob)
}
})
})
.on('error', (err) => {
console.error('error', err)
callback()
})
.end()
})
})
After doing some digging, I found a few pull requests for Electron that detail the issue you are having. The first describes a reproducible example very similar to the problem you are describing.
Expected Behavior
https://electronjs.org/docs/tutorial/security#3-enable-context-isolation-for-remote-content
A preload script should be able to attach anything to the window or document with contextIsolation: true.
Actual behavior
Anything attached to the window in the preload.js just disappears in the renderer.
It seems the final comment explains that the expected behavior no longer works
It was actually possible until recently, a PR with Isolated Worlds has changed the behavior.
The second has a user suggest what they have found to be their solution:
After many days of research and fiddling with the IPC, I've concluded that the best way is to go the protocol route.
I looked at the docs for BrowserWindow and BrowserView as well as an example that shows the behavior that you desire, but these PRs suggest this is no longer possible (along this route).
Possible Solution
Looking into the documentation, the webContents object you get from view.webContents has the function executeJavaScript, so you could try the following to set the global variable.
...
view.setAutoResize({ width: true, height: true });
view.webContents.loadURL('http://localhost:3000');
view.webContents.executeJavaScript("global.baz = 'qux';");
...
Other answers are outdated, use contextBridge be sure to use sendToHost() instead of send()
// Preload (Isolated World)
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld(
'electron',
{
doThing: () => ipcRenderer.sendToHost('do-a-thing')
}
)
// Renderer (Main World)
window.electron.doThing()
So, executeJavaScript as suggested by Zapparatus ended up being part of the solution.
This is what's going on in renderer.js.
view.webContents.executeJavaScript(`
window.communicator = {
request: function(data) {
const url = 'prefix://?data=' + encodeURIComponent(JSON.stringify(data))
const req = new XMLHttpRequest()
req.open('GET', url)
req.send();
},
receive: function(data) {
alert('got: ' + JSON.stringify(data))
}
};
`)
const setContent = data => view.webContents.executeJavaScript(
`window.communicator.receive(${JSON.stringify(data)})`
)
ipcRenderer.on('communicator', (event, message) => {
setContent(`Hello, ${message}!`)
})
We ended up setting up a custom protocol, similar to how its been done here. In your main.js file set up the following:
const { app, session, protocol } = require('electron')
const { appWindows } = require('./main/app-run')
const { URL } = require('url')
protocol.registerSchemesAsPrivileged([
{
scheme: 'prefix',
privileges: {
bypassCSP: true, // ignore CSP, we won't need to patch CSP
secure: true // allow requests from https context
}
}
])
app.on('ready', () => {
const sess = session.fromPartition('my-view-partition')
// https://electronjs.org/docs/tutorial/security#4-handle-session-permission-requests-from-remote-content
sess.setPermissionRequestHandler((webContents, permission, callback) => {
// Denies the permissions request
const decision = false
return callback(decision)
})
sess.protocol.registerStringProtocol('prefix', (req, callback) => {
const url = new URL(req.url)
try {
const data = JSON.parse(url.searchParams.get('data'))
appWindows.main.webContents.send('prefix', data)
} catch (e) {
console.error('Could not parse prefix request!')
}
const response = {
mimeType: 'text/plain',
data: 'ok'
}
callback(response)
})
})
No preload.js or postMessage needed.

Categories

Resources