I'm trying to use a preload script to work around a CORS header issue in Electron 4.2.3. However, I can't get the preload script to run. A minimal reproduction case:
package.json
{
"name": "your-app",
"version": "0.1.0",
"main": "main.js",
"dependencies": {
"electron": "^4.2.3"
}
}
main.js
const { app, BrowserWindow } = require('electron')
app.on('ready', function() {
const win = new BrowserWindow({
webPreferences: {
preload: `file://${__dirname}/preload.js`,
}
})
win.webContents.openDevTools()
win.loadFile('index.html')
})
preload.js
window.preloadWasRun = 'preload was run'
index.html
<body>
<script>
document.write(window.preloadWasRun || 'preload was not run')
</script>
</body>
No matter what settings I use for webSecurity, nodeIntegration and contextIsolation, it seems that my preload script is just getting ignored. Even if I make a syntax error in the script, it doesn't show any errors anywhere.
Turns out it has to be an absolute path name, not an absolute URL. None of these work:
preload: `file://${__dirname}/preload.js`,
preload: './preload.js',
preload: 'preload.js',
But this works as advertised:
preload: `${__dirname}/preload.js`,
Since it seems to be a filesystem path rather than a URL, it might also be wise to use path.join instead, to account for platforms with weird path separators:
preload: path.join(__dirname, 'preload.js'),
Related
The short question I have: based on the below code, why do I have to 'import' the components below twice to get my code to work?
I am working in a pretty locked-down environment, so cannot use Webpack or .vue SFCs at the moment, or npm (for all intents and purposes).
I've cobbled together a working version of a small vue app using typescript files, but am confused why it worked :S.
I have to import the component file, then require it as a component. I'd like to clean this up if I could, as we will be rolling this out as a P.O.C. with developers that are also just learning Vue, so I'd like to avoid bad practices at the start if I could.
index.ts
import * as Vue from "vue";
import * as Apple from "./App"; <-----
Vue.component('apple2', Apple.default); <----- wat?
let v = new Vue({
el: "#app",
components: { Apple}, <-----
template: `
<div>
<apple2/> <-----
</div>`,
data: {
name: "World"
},
});
App.ts
import * as Vue from "vue";
import * as fred from "./Hello"; <----
Vue.component('fred2', fred.default); <----
export default Vue.extend({
name: 'Apple',
template: `
<div>
<fred2 :name="name" :initialEnthusiasm="4"/> <-----
</div>`,
data() {
return { name: "World" }
},
components: { fred } <-----
});
Index.html
<!doctype html>
<html>
<head>
<script src="scripts/vue.min.js"></script>
<script data-main="scripts/build/index" src="scripts/lib/require.min.js">
</script></head>
<body>
<div id="app"></div>
</body>
tsConfig
{"compileOnSave": true,
"compilerOptions": {
"module": "amd",
"moduleResolution": "node",
"noImplicitAny": true,
"noEmitOnError": false,
"outDir": "./scripts/build",
"removeComments": false,
"sourceMap": true,
"target": "es5",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true
},
"exclude": [
"node_modules",
"wwwroot"
],
"include": [
"./scripts/**/*"
]
}
You're mixin up two different concepts, when you do this:
Vue.component('apple2', Apple.default);
You're actually registering the component definition object (Apple.default) with the name apple2 with the global Vue instance, making it available to all the components that are rendered by the previously referred Vue instance. In this case you could remove this part of your code in the index.ts:
components: { Apple}
And in theory your app should still work.
But because you're using typescript you can make your app work as if it was using a module system, allowing you to import the used sub-components in each parent component, allowing you to do something like this:
App.ts
export default const component = {
template: '<div>My component</div>'
}
index.ts
import Vue from 'vue';
import component from './App';
new Vue({
el: '#app',
components: {
'my-imported-component': component
}
});
And in your template:
<div id="app">
<my-imported-component/>
</div>
This would be, in my opinion a better approach because that you won't pollute the global Vue instance with all your components, but it's a matter of taste and what works for your scenario.
For more information take look at this link:
https://v2.vuejs.org/v2/guide/components-registration.html
We have an electron crypto app that signs transactions (among other things).
We want other websites to have the ability to have a button that opens that electron app, pre-filled with some params (the transaction information).
flow is:
user clicks "make transaction" on some-crypto-site.com
electron app opens up with pre-filled params
user clicks "sign transaction" in electron app
electron app does stuff behind the scenes
electron app closes and sends a message to some-crypto-site.com
This could be done at runtime, or install time.
What I tried (linux, chrome)
calling app.setAsDefaultProtocolClient with the code of this gist, which is basically:
app.setAsDefaultProtocolClient("my-app")
But after I put my-app://foo?bar=baz in chrome browser, I get the following popup, and pressing open-xdg does nothing (other than dismissing the popup)
I looked into
Electron protocol api which seems to handle in-app protocols only
webtorrent .desktop file This might be the way to go, I'm just not sure how to go about it.
Maybe there's a way to do so at install time through electron builder?
Thanks in advance for the help, I have no idea how to proceed here!
Resources that might be useful
github repo with mac+window example
github comment for linux
github comment for linux 2
SO answer for all 3 OSs
SO windows answer
npm package for windows registery
SO mac answer
SO linux answer
microsoft docs for windows
windows article
github comment for windows
github comment for mac
info.plst for mac
old repo for mac and win
Since this may be relevant to what I’m doing at work, I decided to give it a go.
I’ve only tested this on OSX though!
I looked at the documentation for app.setAsDefaultProtocolClient and it says this:
Note: On macOS, you can only register protocols that have been added to your app's info.plist, which can not be modified at runtime. You can however change the file with a simple text editor or script during build time. Please refer to Apple's documentation for details.
These protocols can be defined when packaging your app with electron-builder. See build:
{
"name": "foobar",
"version": "1.0.0",
"main": "main.js",
"scripts": {
"start": "electron .",
"dist": "electron-builder"
},
"devDependencies": {
"electron": "^3.0.7",
"electron-builder": "^20.38.2"
},
"dependencies": {},
"build": {
"appId": "foobar.id",
"mac": {
"category": "foo.bar.category"
},
"protocols": {
"name": "foobar-protocol",
"schemes": [
"foobar"
]
}
}
}
In your main thread:
const {app, BrowserWindow} = require('electron');
let mainWindow;
function createWindow () {
mainWindow = new BrowserWindow({width: 800, height: 600})
mainWindow.loadFile('index.html');
}
app.on('ready', createWindow);
var link;
// This will catch clicks on links such as open in foobar
app.on('open-url', function (event, data) {
event.preventDefault();
link = data;
});
app.setAsDefaultProtocolClient('foobar');
// Export so you can access it from the renderer thread
module.exports.getLink = () => link;
In your renderer thread:
Notice the use of the remote API to access the getLink function exported in the main thread
<!DOCTYPE html>
<html>
<body>
<p>Received this data <input id="data"/></p>
<script>
const {getLink} = require('electron').remote.require('./main.js');
document.querySelector('#data').value = getLink();
</script>
</body>
</html>
Example
open in foobar
This also allows you to launch from the command line:
open "foobar://xyz=1"
How do you get back to the original caller?
I suppose that when you launch the app you could include the caller url:
<a href="foobar://abc=1&caller=example.com”>open in foobar</a>
When your electron app finishes processing data, it would simply ping back that url
Credits
Most of my findings are based on:
From this GitHub issue
And the excellent work from #oikonomopo
All little bit different from above.
open-url fires before the ready event so you can store it in a variable and use within the widow did-finish-load.
let link;
let mainWindow;
function createWindow() {
mainWindow = new BrowserWindow({
width: 1280,
height: 720,
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
});
mainWindow.openDevTools();
mainWindow.setContentProtection(true);
mainWindow.loadFile('index.html');
mainWindow.webContents.on("did-finish-load", function() {
mainWindow.webContents.send('link', link);
});
}
app.on('ready', createWindow);
// This will catch clicks on links such as open in foobar
app.on('open-url', function(event, url) {
link = url;
if (mainWindow?.webContents) {
mainWindow.webContents.send('link', link);
}
});
app.setAsDefaultProtocolClient('protocols');
You can then use the value in your render html like this.
<!DOCTYPE html>
<html>
<head></head>
<body>
<script>
const ipc = require("electron").ipcRenderer;
ipc.on("link", function (event, url) {
console.log(url);
console.log(parseQuery(decodeURI(url)));
});
function parseQuery(queryString) {
queryString = queryString.substring(queryString.indexOf("://") + 3);
var query = {};
var pairs = (queryString[0] === "?" ? queryString.substr(1) : queryString).split("&");
for (var i = 0; i < pairs.length; i++) {
var pair = pairs[i].split("=");
query[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || "");
}
return query;
}
</script>
</body>
</html>
Context
I'm using parcel-plugin-sw-precache which wraps around sw-precache to make it work with Parcel.js. Everything was working as expected, and I have been testing my offline app.
Problem
I added react-pdf.js into my project, one of the dependencies for this library doesn't get added into the service worker when it is generated by the sw-precache. I know this because the file "pdf.worker.entry.7ce4fb6a.js" gives a 304 error when I switch to offline mode.
What I have tried
I'm trying to add the file manually to the package.json parcel-plugin-sw-precache config using this code:
"sw-precache": {
"maximumFileSizeToCacheInBytes": 10485760,
"staticFileGlobs": [
"/pdf.worker.entry.7ce4fb6a.js"
]
},
I'm not sure if the file path should be relative to package.json or relative the generated service worker. In anycase, the manually specified file doesn't get added to generate services worker as I would expect. As seen below.
self.__precacheManifest = [{
"url": "index.html",
"revision": "ac5ace7a43a0fef7ae65fd3119987d1f"
}, {
"url": "castly.e31bb0bc.css",
"revision": "657409f7159cb857b9409c44a15e653f"
}, {
"url": "castly.e31bb0bc.js",
"revision": "018d4664d809ec19d167421b359823ad"
}, {
"url": "/",
"revision": "af5513bb330deae3098ab289d69a40c7"
}]
The question
If the sw-precache or parcel-plugin-sw-precache seem to be missing some files, how can I make sure they get added to the generated service worker?
In my exploration for an answer. I gave up on using parcel-plugin-sw-precache and instead I switched to using workbox. If you are interested in creating an offline app with Parcel.js. Then I recommend Workbox as it is the next generation of sw-precache.
There is how I got it working:
Learning
Learn what Workbox is and what is does with this code lab.
Implimenting
1) Install the Workbox CLI globally.
2) create a placeholding service worker in the root directory. e.g. sw_shell.js
- The shell is a holding file. The Workbox wizard will pick it up and generate a
new sw.js file automatically.
3) Add to the sw_config.js the following code:
importScripts("https://storage.googleapis.com/workbox-cdn/releases/3.6.3/workbox-sw.js");
if (workbox) {
workbox.skipWaiting();
workbox.clientsClaim();
workbox.precaching.suppressWarnings();
// The next line came from the code lab
workbox.precaching.precacheAndRoute([]);
workbox.routing.registerNavigationRoute("/index.html");
} else {
console.log(`Boo! Workbox didn't load 😬`);
}
4) Run this code from a command line opened in your project's root directory.
workbox wizard --injectManifest
5) Follow the steps in the wizard. For dev purposes point the "root web app" to your parcel dist folder. Workbox does it's magic and picks up those files to be hashed into a new sw.js file.
6) The wizard will ask for your existing sw.js file. In my case I use the sw_shell.js.
a:Workbox picks up the sw_shell.js.
c:Generates as new sw.js file in a location specfied when running the wizard, and injects the files to run offline.
In my case I let the new sw.js generate in my root folder because Parcel picks it up automatically as per the script in my index.js.
'use strict';
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('sw.js').then(function(reg) {
console.log('Worker registration started')
reg.onupdatefound = function() {
console.log('update found')
var installingWorker = reg.installing;
installingWorker.onstatechange = function() {
console.log('installing worker')
switch (installingWorker.state) {
case 'installed':
if (navigator.serviceWorker.controller) {
console.log('New or updated content is available.');
} else {
console.log('Content is now available offline!');
}
break;
case 'redundant':
console.error('The installing service worker became redundant.');
break;
}
};
};
}).catch(function(e) {
console.error('Error during service worker registration:', e);
});
});
}
7) Add workbox injectManifest to your package.json to make sure Workbox picks up any changes to your files:
"scripts": {
"start": "parcel index.html workbox injectManifest"
}
Let me know if you want to know more about this. There is a video here that helped me a little bit also.
In our swagger.json we are setting basePath to /api, however, when the application is deployed in docker container, the context path is not /api. This could be different thing and we don't know what it is so we can't hard code it.
I am trying to set requestInterceptor as per the following guide, in order to catch the request and modify the url path perhaps:
https://swagger.io/docs/swagger-tools/#customization-36
But it seems requestInterceptor is being ignored. Is this possible? If not, how can I set the correct path at runtime?
This is my code in index.html
window.onload = function() {
// Build a system
const ui = SwaggerUIBundle({
url: "../api-docs/swagger.json",
dom_id: '#swagger-ui',
deepLinking: true,
requestInterceptor: function(request) {
window.alert(request);
},
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
window.ui = ui
}
We are using Swagger 2.0
Upgrade to the latest version from here, or update your node package. I had the same problem because I downloaded the distribution before requestInterceptor support was added.
I'm using sw-precache in a jekyll website to add offline capabilities with the following configuration:
gulp.task('generate-service-worker', function(cb) {
var path = require('path');
var swPrecache = require('sw-precache');
var rootDir = '_site';
var packageJson = require('./package.json');
swPrecache.write('./service-worker.js', {
staticFileGlobs: [rootDir + '/**/*.{html,css,png,jpg,gif,svg}', rootDir + '/js/*'],
stripPrefix: rootDir + '/',
runtimeCaching: [{
urlPattern: /\/$/,
handler: 'networkOnly'
}],
handleFetch: argv.cacheAssets || false,
maximumFileSizeToCacheInBytes: 10485760, // 10 mb
cacheId: packageJson.name + '-v' + packageJson.version
}, cb);
});
The problem is that, when I change content in the website (for example, text in a blog post, or some text from the index page) the changes won't be shown until the new serviceworker version has been installed and the browser has been refreshed, which of course, is the expected behaviour of cacheFirst.
What I want is to make the request to the index of the site always network first, which is what I'm trying here:
runtimeCaching: [{
urlPattern: /\/$/,
handler: 'networkFirst'
}]
But this isn't working, the index is always getting fetch from the serviceworker and not from network, how can I accomplish this?
My problem is that I was including the actual page contents for precache: '/**/*.{html,css,png,jpg,gif,svg}'.
Excluding the html files works as expected:
'/**/*.{css,png,jpg,gif,svg}'
Change the url pattern to
urlPattern: "'/'"
This is a exact match pattern. Your index will match to this and nothing else.
The solution for this is, treat your index.html as dynamic content.
Change you sw webpack config to
new SWPrecacheWebpackPlugin({
cacheId: 'yourcacheid',
filename: 'service-worker.js',
staticFileGlobs: [
'dist/**/*.{js,css}'
],
minify: true,
stripPrefix: 'dist/',
runtimeCaching: [{
urlPattern: /\/$/,
handler: 'networkFirst'
}]
})
Remove your index.html from staticFileGlobs and add you root index to runtime caching.
Then look at your cache storage. You will see something like $$$toolbox-cache$$$https://your-domain.com as a new cache item. Inspect that and you can see your index cached there.