Use AudioWorklet within electron (DOMException: The user aborted a request) - javascript

I am trying to use an AudioWorklet within my electron app for metering etc. which works fine when executed in dev mode where the worklet is being served by an express dev server like http://localhost:3000/processor.js.
However if I try to run the app in prod mode the file is being served locally like file://tmp/etc/etc/build/processor.js and in the developer-console I can even see the file correctly being previewed but I get this error message:
Uncaught (in promise) DOMException: The user aborted a request.
I saw that someone else had a similar problem before over here but unfortunately my reputation on stack overflow is not high enough to comment directly. The suggestion there to change the mime-type to application/javascript or text/javascript sounds good but I have no idea how to force electron to use a specific mime-type for a specific file. Furthermore in the developer-console in the network tab it seems like chromium is actually already assuming a javascript file for my processor.js.
I already tried to load the worklet with a custom protocol like that
protocol.registerStandardSchemes(['worklet']);
app.on('ready', () => {
protocol.registerHttpProtocol('worklet', (req, cb) => {
fs.readFile(req.url.replace('worklet://', ''), (err, data) => {
cb({ mimeType: 'text/javascript', data });
});
});
});
and then when adding the worklet
await ctx.audioWorklet.addModule('worklet://processor.js');
unfortunately this only ends in these errors followed by the first error
GET worklet://processor.js/ 0 ()
Uncaught Error: The error you provided does not contain a stack trace.
...

I found a hacky solution if anybody is interested.
To force a mime-type electron / chromium is happy with I load the worklet file with the file api as a string, convert it to a blob with mime-type text/javascript and then create an object url from that
const processorPath = isDevMode ? 'public/processor.js' : `${global.__dirname}/processor.js`;
const processorSource = await readFile(processorPath); // just a promisified version of fs.readFile
const processorBlob = new Blob([processorSource.toString()], { type: 'text/javascript' });
const processorURL = URL.createObjectURL(processorBlob);
await ctx.audioWorklet.addModule(processorURL);
Hope this helps anyone having the same problem...

If you're using webpack to compile your source you should be able to use the web-worker loader for your custom worker scripts.

Related

How to intercept file protocol and convert it to https in electron app

My question is similar to this issue. But the solution provided there does not address it.
I'm trying to integrate Tealium tags into my electron app. I have added the code snippet as per documentation as below:
<script type="text/javascript">
(function(a,b,c,d) {
a='//tags.tiqcdn.com/utag/ACCOUNT/PROFILE/ENVIRONMENT/utag.js';
b=document;c='script';d=b.createElement(c);d.src=a;
d.type='text/java'+c;d.async=true;
a=b.getElementsByTagName(c)[0];a.parentNode.insertBefore(d,a)})();
</script>
The above script in turn has calls to multiple utag.x.js. The issue is that the utag files are not locally available and hence the file protocol to locate these fails with ERR_FILE_NOT_FOUND.
I changed the value of variable 'a' to start with 'https' then utag.js is downloaded successfully but the next call to utag.x.js fails. To solve this I'm trying to intercept the file protocol using the API interceptFileProtocol().
app
.whenReady()
.then(() => {
protocol.interceptFileProtocol('file', (request, callback) => {
if (request.url.includes('utag')) {
console.log('UTAG request...');
request.url = `https://${request.url.slice('file://'.length)}`;
}
callback(request);
})
});
In the console I can see that the call is intercepted and the url gets updated. But the network call to download the resource still has the file protocol instead of https and hence get ERR_FILE_NOT_FOUND.
Am I correct in using the interceptFileProtocol or is there any other way to do it?

Mixed Content Error when testing via Cypress

I am trying to test a player loaded by the webpage I currently test.
When testing manually everything works as expected and via https.
But when I run my cypress test the player is not loading and I get a Mixed Content-Error because it seems to request resources via http. Therefore the player is not loading.
I already tried adding a upgrade-insecure-requests : 1 header to every request and setting chromeWebSecurity : false in the config but neither seems to work.
EDIT:
After some further research I found the html script tags requesting the sources. Their URLs start with // which cypress seems to use http for instead of the protocol used beforehand (https).
Has someone already experienced this or found a working solution?
I had the same problem, you can avoid this error by setting "chromeWebSecurity" to false in your cypress config file.
cypress.config.json
const { defineConfig } = require("cypress");
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
// ...
},
},
"chromeWebSecurity": false //add this param to your config file
});

How can I add a custom chrome extension to my Electron app?

I am facing some trouble adding chrome addons into my Electron BrowserWindow.
Before creating my window (and after the ready event has fired), I try to add a devtools extension that my browser needs to do screen sharing.
BrowserWindow.addDevToolsExtension('/home/USER/.config/chromium/Default/Extensions/dkjdkjlcilokfaigbckcipicchgoazeg/1.5_0');
I followed this Electron guide, and it worked for their example (adding the react develop tool). When I do the exact same thing with my own chrome extension I have this error:
[4735:1116/163422.268391:ERROR:CONSOLE(7701)] "Skipping extension with invalid URL: chrome-extension://extension-name", source: chrome-devtools://devtools/bundled/shell.js (7701)
I don't really get why the error specified is "invalid URL" since I'm doing the exact same thing / process with the react addon without a problem. I also have no idea what to do. Is it possible that my chrome addon is not Electron-compatible?
It looks like you're trying to add a regular Chrome extension instead of a Dev Tools extension.
The BrowserWindow.addExtension(path) method is for regular Chrome extensions:
BrowserWindow.addExtension(path)
path String
Adds Chrome extension located at path, and returns extension's name.
The method will also not return if the extension's manifest is missing or incomplete.
Note: This API cannot be called before the ready event of the app module is emitted.
- https://electronjs.org/docs/api/browser-window#browserwindowaddextensionpath
Conversely, the BrowserWindow.addDevToolsExtension(path) method is for Dev Tools extensions:
BrowserWindow.addDevToolsExtension(path)
path String
Adds DevTools extension located at path, and returns extension's name.
The extension will be remembered so you only need to call this API once, this API is not for programming use. If you try to add an extension that has already been loaded, this method will not return and instead log a warning to the console.
The method will also not return if the extension's manifest is missing or incomplete.
Note: This API cannot be called before the ready event of the app module is emitted.
- https://electronjs.org/docs/api/browser-window#browserwindowadddevtoolsextensionpath
Note that in both cases you need to wait for the ready event from the app module to be emitted:
const { BrowserWindow, app } = require('electron')
let mainWindow = null
function main() {
BrowserWindow.addExtension('/path/to/extension')
mainWindow = new BrowserWindow()
mainWindow.loadURL('https://google.com')
mainWindow.on('close', event => {
mainWindow = null
})
}
app.on('ready', main)
Support for Chromium extensions in Electron is actively being worked on at the moment. The support isn't complete yet, but the GitHub issue seems to have regular updates being posted.
Fingers crossed!
A current pull request is open for 'just enough extensions [api] to load a simple ... extension'
Electron 9 has much more support for extensions!
To load them, use session.loadExtension: https://github.com/electron/electron/blob/master/docs/api/extensions.md
const { app, BrowserWindow, session } = require('electron')
// ... in your createWindow function, which is called after app.whenReady
const mainWindow = new BrowserWindow({...})
const ext = await session.defaultSession.loadExtension('/path/to/unpacked/chrome-ext')
console.log('ext', ext)
// outputs config file
// {
// id: 'dcpdbjjnmhhlnlbibpeeiambicbbndim',
// name: 'Up! – Free Social Bot',
// path: '/Users/caffeinum/Development/GramUp/chrome-ext',
// url: 'chrome-extension://dcpdbjjnmhhlnlbibpeeiambicbbndim/',
// version: '1.7.0',
// manifest: { ... }
// }
Read more: https://github.com/electron/electron/blob/master/docs/api/extensions.md
Also, there's another project that helps you do that, also adds additional functionality: https://github.com/sentialx/electron-extensions
While there is a documented method to register a normal extension, in majority of cases it won't do much, as Electron supports only an accessibility subset of the chrome.* APIs (apparently only the stuff required by Spectron and Devtron) and as they've stated a while ago, they don't have any plans to support Chrome extension APIs at a full scale.

Jasmine : Fixture could not be loaded

So i wanted to get into Test Driven Development and decided to use Jasmine on my project.
The thing is, i can't load fixtures.
The 2 solutions commonly proposed are :
Run chrome with --allow-file-access-from-files
Serve the file from you local server
So i used the first solution, but no result.
Then i set up the routes of my webserver so that localhost/fixture/my_fixture would return the content of my_fixture.html.
So when i manually access localhost/fixture/my_fixture, the content of the fixture is displayed on screen. But in my jasmine spec file, when i use :
jasmine.getFixtures().fixturesPath = 'http://localhost/fixture'
loadFixtures('quizz_fixture')
I get the following errors :
Error: Fixture could not be loaded: http://localhost/fixture/quizz_fixture
(status: error, message: Failed to execute 'send' on 'XMLHttpRequest': Failed to load 'http://localhost/fixture/quizz_fixture?_=1455854875950'.)
When i use the URL given in the error, my browser displays the content of the fixture without errors.
Therefore, i don't understand the reason for this error. Does anyone have an insight?
Edit:
Web server : Apache
Browser : Chrome
OS : Windows 7
Edit 2
The issue comes from jasmine-jquery, on line 139 below, where the fail function is called. I can't figure out what's happening as the URL that supposedly can't be loaded actually loads just fine in my browser :
jasmine.Fixtures.prototype.loadFixtureIntoCache_ = function (relativeUrl) {
var self = this
, url = this.makeFixtureUrl_(relativeUrl)
, htmlText = ''
, request = $.ajax({
async: false, // must be synchronous to guarantee that no tests are run before fixture is loaded
cache: false,
url: url,
dataType: 'html',
success: function (data, status, $xhr) {
htmlText = $xhr.responseText
}
}).fail(function ($xhr, status, err) {
throw new Error('Fixture could not be loaded: ' + url + ' (status: ' + status + ', message: ' + err.message + ')')
})
The result is :
Failed to load 'http://localhost/fixture/quizz_fixture.html?_=1456886216017'
Which works when called in the browser. I just don't get it.
Thanks.
It's really hard to answer without knowing at least a little about the nature of your server, or what the fixture looks like. Is the server just a simple file server like node-http-server, or is this pointing to your app? Is it serving the fixture correctly? Does your fixture have a mistake in it? I can't tell any of that from here.
What I would say though is that if you are just beginning TDD you should probably avoid fixtures entirely. One of the biggest challenges to somebody new to TDD is writing small enough tests, and Jasmine fixtures make it easy to write really big tests.
Instead I would recommend manually adding the bare minimum of DOM you need to the page and removing that in an after hook. jasmine-fixture is a tool that essentially does this. This'll force you to consider how much of the DOM you actually need to write a test, and will make the DOM changes you are making visible in the tests itself.
So i found a very unsatisfying solution, but a solution nonetheless.
To summarize
Using chrome, i tried to load jasmine fixture from a local file, which wouldn't work with chrome (this is something known, disabled for security reasons).
I tried using the chrome flag --allow-file-access-from-files but it didn't work. So i gave up on using a fixture from a local file.
I understood that the fixture file had to be served from my web server, which i did. But it didn't work either, because of some Ajax error related to the caching of fixtures. I tried updating my version of jquery (which was a bit old) but it didn't work. In the end, I wasn't able to understand what the issue was.
I downloaded firefox and tried executing the jasmine specRunner with the configuration of point 3 above (fixture served by web server) but again, it didn't work.
Using firefox, I reverted to the method in point 1, which is using a local fixture file, and it did work. I hate that solution, but i need to go forward, so that will do.
Conclusion
If stuck with that kind of issue, save yourself some time and use firefox which will allow the use of a local fixture file.
In the command line you can write:
start chrome --allow-file-access-from-files "path_to_test/SpecRunner.html"
That solved to me... hope can help some more people.

Implement cross extension message passing in chrome extension and app

I am trying to do cross extension message passing between chrome extension and chrome app according to this article. But I am not sure that how to do it correctly. I used background js to receive and send messages. But no clue whether it is working or not. Actually I want to save file from chrome extension, since it cannot be done I thought this could work. So any idea or suggestion or example is highly welcome.
I have go through many alternatives as also appears in this question. Then one of answer points this example. I found that this example is works fine. I hope that I could use this mechanism to save file using Chrome App's fileSystem API.
The Chrome messaging APIs can only transfer JSON-serializable values. If the files are small, then you could just read the file content using FileReader in the extension, send the message over the external messaging channel to the Chrome App, then save the data using the FileWriter API.
When the files are big, read the file in chunks using file.slice(start, end) then follow the same method as for small files.
Extension:
var app_id = '.... ID of app (32 lowercase a-p characters) ....';
var file = ...; // File or Blob object, e.g. from an <input type=file>
var fr = new FileReader();
fr.onload = function() {
var message = {
blob: fr.result,
filename: file.name,
filetype: file.type
};
chrome.runtime.sendMessage(app_id, message, function(result) {
if (chrome.runtime.lastError) {
// Handle error, e.g. app not installed
console.warn('Error: ' + chrome.runtime.lastError.message);
} else {
// Handle success
console.log('Reply from app: ', result);
}
});
};
fr.onerror = function() { /* handle error */ };
// file or sliced file.
fr.readAsText(file);
App:
chrome.runtime.onMessageExternal.addListener(
function(message, sender, sendResponse) {
// TODO: Validate that sender.id is allowed to invoke the app!
// Do something, e.g. convert back to Blob and do whatever you want.
var blob = new Blob([message.blob], {type: message.filetype});
console.log('TODO: Do something with ' + message.filename + ':', blob);
// Do something, e.g. reply to message
sendResponse('Processed file');
// if you want to send a reply asynchronously, uncomment the next line.
// return true;
});
EDIT: Although the following method using sounded nice in theory, it does not work in practice because a separate SharedWorker process is created for the app / extension.
If you want to send huge files (e.g. Files), then you could implement the following:
Extension: Create proxy.html (content = <script src=proxy.js></script>). (feel free to pick any other name).
Extension: Put proxy.html in web_accessible_resources.
App: Bind a window.onmessage event listener. This event listener will receive messages from the frame you're going to load in the next step.
App: Load chrome-extension://[EXTENSIONID]/proxy.html in a frame within your app. This extension ID can either be hard-coded (see Obtaining Chrome Extension ID for development) or exchanged via the external extension message passing API (make sure that you validate the source - hardcoding the ID would be the best way).
Extension: When proxy.html is loaded, check whether location.ancestorOrigins[0] == 'chrome-extension://[APPID]' to avoid a security leak. Terminate all steps if this condition fails.
Extension: When you want to pass a File or Blob to the app, use parent.postMessage(blob, 'chrome-extension://[APPID]');
App: When it receives the blob from the extension frame, save it to the FileSystem that you obtained through the chrome.fileSystem API.
The last task to solve is getting a file from the extension to the extension frame (proxy.html) that is embedded in the app. This can be done via a SharedWorker, see this answer for an example (you can skip the part that creates a new frame because the extension frame is already created in one of the previous steps).
Note that at the moment (Chrome 35), Files can only be sent with a work-around due to a bug.

Categories

Resources