Auto-updates to Electron - windows

I'm looking to deploy an auto-update feature to an Electron installation that I have, however I am finding it difficult to find any resources on the web.
I've built a self contained application using Adobe Air before and it seemed to be a lot easier writing update code that effectively checked a url and automatically downloaded and installed the update across Windows and MAC OSX.
I am currently using the electron-boilerplate for ease of build.
I have a few questions:
How do I debug the auto update feature? Do I setup a local connection and test through that using a local Node server or can I use any web server?
In terms of signing the application I am only looking to run apps on MAC OSX and particularly Windows. Do I have to sign the applications in order to run auto-updates? (I managed to do this with Adobe Air using a local certificate.
Are there any good resources that detail how to implement the auto-update feature? As I'm having difficulty finding some good documentation on how to do this.

I am also new to Electron but I think there is no simple auto-update from electron-boilerplate (which I also use). Electron's auto-updater uses Squirrel.Windows installer which you also need to implement into your solution in order to use it.
I am currently trying to use this:
https://www.npmjs.com/package/electron-installer-squirrel-windows
And more info can be found here:
https://github.com/atom/electron/blob/master/docs/api/auto-updater.md
https://github.com/squirrel/squirrel.windows
EDIT: I just opened the project to try it for a while and it looks it works. Its pretty straightforward. These are pieces from my gulpfile.
In current configuration, I use electron-packager to create a package.
var packager = require('electron-packager')
var createPackage = function () {
var deferred = Q.defer();
packager({
//OPTIONS
}, function done(err, appPath) {
if (err) {
gulpUtil.log(err);
}
deferred.resolve();
});
return deferred.promise;
};
Then I create an installer with electron-installer-squirrel-windows.
var squirrelBuilder = require('electron-installer-squirrel-windows');
var createInstaller = function () {
var deferred = Q.defer();
squirrelBuilder({
// OPTIONS
}, function (err) {
if (err)
gulpUtil.log(err);
deferred.resolve();
});
return deferred.promise;
}
Also you need to add some code for the Squirrel to your electron background/main code. I used a template electron-squirrel-startup.
if(require('electron-squirrel-startup')) return;
The whole thing is described on the electron-installer-squirrel-windows npm documentation mentioned above. Looks like the bit of documentation is enough to make it start.
Now I am working on with electron branding through Squirrel and with creating appropriate gulp scripts for automation.

You could also use standard Electron's autoUpdater module on OS X and my simple port of it for Windows: https://www.npmjs.com/package/electron-windows-updater

I followed this tutorial and got it working with my electron app although it needs to be signed to work so you would need:
certificateFile: './path/to/cert.pfx'
In the task config.
and:
"build": {
"win": {
"certificateFile": "./path/to/cert.pfx",
"certificatePassword": "password"
}
},
In the package.json

Are there any good resources that detail how to implement the auto-update feature? As I'm having difficulty finding some good documentation on how to do this.
You don't have to implement it by yourself. You can use the provided autoUpdater by Electron and just set a feedUrl. You need a server that provides the update information compliant to the Squirrel protocol.
There are a couple of self-hosted ones (https://electronjs.org/docs/tutorial/updates#deploying-an-update-server) or a hosted service like https://www.update.rocks

Question 1:
I use Postman to validate that my auto-update server URLs return the response I am expecting. When I know that the URLs provide the expected results, I know I can use those URLs within the Electron's Auto Updater of my Application.
Example of testing Mac endpoint with Postman:
Request:
https://my-server.com/api/macupdates/checkforupdate.php?appversion=1.0.5&cpuarchitecture=x64
JSON Response when there is an update available:
{
"url": "https:/my-server.com/updates/darwin/x64/my-electron=app-x64-1.1.0.zip",
"name": "1.1.0",
"pub_date": "2021-07-03T15:17:12+00:00"
}
Question 2:
Yes, your Electron App must be code signed to use the auto-update feature on Mac. On Windows I'm not sure because my Windows Electron app is code signed and I did not try without it. Though it is recommended that you sign your app even if the auto-update could work without it (not only for security reasons but mainly because otherwise your users will get scary danger warnings from Windows when they install your app for the first time and they might just delete it right away).
Question 3:
For good documentation, you should start with the official Electron Auto Updater documentation, as of 2021-07-07 it is really good.
The hard part, is figuring out how to make things work for Mac. For Windows it's a matter of minutes and you are done. In fact...
For Windows auto-update, it is easy to setup - you just have to put the RELEASES and nupkg files on a server and then use that URL as the FeedURL within your Electron App's autoUpdater. So if your app's update files are located at https://my-server.com/updates/win32/x64/ - you would point the Electron Auto Updater to that URL, that's it.
For Mac auto-update, you need to manually specify the absolute URL of the latest Electron App .zip file to the Electron autoUpdater. So, in order to make the Mac autoUpdater work, you will need to have a way to get a JSON response in a very specific format. Sadly, you can't just put your Electron App's files on your server and expect it to work with Mac just like that. Instead, the autoUpdater needs a URL that will return the aforementioned JSON response. So to do that, you need to pass Electron's Auto Updater feedURL the URL that will be able to return this expected kind of JSON response.
The way you achieve this can be anything but I use PHP just because that's the server I already paid for.
So in summary, with Mac, even if your files are located at https://my-server.com/updates/darwin/x64/ - you will not provide that URL to Electron's Auto Updater FeedURL. Instead will provide another URL which returns the expected JSON response.
Here's an example of my main.js file for the Electron main process of my App:
// main.js (Electron main process)
function registerAutoUpdater() {
const appVersion = app.getVersion();
const os = require('os');
const cpuArchitecture = os.arch();
const domain = 'https://my-server.com';
const windowsURL = `${domain}/updates/win32/x64`;
const macURL = `${domain}/api/macupdates/checkforupdate.php?appversion=${appVersion}&cpuarchitecture=${cpuArchitecture}`;
//init the autoUpdater with proper update feed URL
const autoUpdateURL = `${isMac ? macURL : windowsURL}`;
autoUpdater.setFeedURL({url: autoUpdateURL});
log.info('Registered autoUpdateURL = ' + (isMac ? 'macURL' : 'windowsURL'));
//initial checkForUpdates
autoUpdater.checkForUpdates();
//Automatic 2-hours interval loop checkForUpdates
setInterval(() => {
autoUpdater.checkForUpdates();
}, 7200000);
}
And here's an example of the checkforupdate.php file that returns the expected JSON response back to the Electron Auto Updater:
<?php
//FD Electron App Mac auto update API endpoint.
// The way Squirrel.Mac works is by checking a given API endpoint to see if there is a new version.
// If there is no new version, the endpoint should return HTTP 204. If there is a new version,
// however, it will expect a HTTP 200 JSON-formatted response, containing a url to a .zip file:
// https://github.com/Squirrel/Squirrel.Mac#server-support
$clientAppVersion = $_GET["appversion"] ?? null;
if (!isValidVersionString($clientAppVersion)) {
http_response_code(204);
exit();
}
$clientCpuArchitecture = $_GET["cpuarchitecture"] ?? null;
$latestVersionInfo = getLatestVersionInfo($clientAppVersion, $clientCpuArchitecture);
if (!isset($latestVersionInfo["versionNumber"])) {
http_response_code(204);
exit();
}
// Real logic starts here when basics did not fail
$isUpdateVailable = isUpdateAvailable($clientAppVersion, $latestVersionInfo["versionNumber"]);
if ($isUpdateVailable) {
http_response_code(200);
header('Content-Type: application/json;charset=utf-8');
$jsonResponse = array(
"url" => $latestVersionInfo["directZipFileURL"],
"name" => $latestVersionInfo["versionNumber"],
"pub_date" => date('c', $latestVersionInfo["createdAtUnixTimeStamp"]),
);
echo json_encode($jsonResponse);
} else {
//no update: must respond with a status code of 204 No Content.
http_response_code(204);
}
exit();
// End of execution.
// Everything bellow here are function declarations.
function getLatestVersionInfo($clientAppVersion, $clientCpuArchitecture): array {
// override path if client requests an arm64 build
if ($clientCpuArchitecture === 'arm64') {
$directory = "../../updates/darwin/arm64/";
$baseUrl = "https://my-server.com/updates/darwin/arm64/";
} else if (!$clientCpuArchitecture || $clientCpuArchitecture === 'x64') {
$directory = "../../updates/darwin/";
$baseUrl = "https://my-server.com/updates/darwin/";
}
// default name with version 0.0.0 avoids failing
$latestVersionFileName = "Finance D - Tenue de livres-darwin-x64-0.0.0.zip";
$arrayOfFiles = scandir($directory);
foreach ($arrayOfFiles as $file) {
if (is_file($directory . $file)) {
$serverFileVersion = getVersionNumberFromFileName($file);
if (isVersionNumberGreater($serverFileVersion, $clientAppVersion)) {
$latestVersionFileName = $file;
}
}
}
return array(
"versionNumber" => getVersionNumberFromFileName($latestVersionFileName),
"directZipFileURL" => $baseUrl . rawurlencode($latestVersionFileName),
"createdAtUnixTimeStamp" => filemtime(realpath($directory . $latestVersionFileName))
);
}
function isUpdateAvailable($clientVersion, $serverVersion): bool {
return
isValidVersionString($clientVersion) &&
isValidVersionString($serverVersion) &&
isVersionNumberGreater($serverVersion, $clientVersion);
}
function getVersionNumberFromFileName($fileName) {
// extract the version number with regEx replacement
return preg_replace("/Finance D - Tenue de livres-darwin-(x64|arm64)-|\.zip/", "", $fileName);
}
function removeAllNonDigits($semanticVersionString) {
// use regex replacement to keep only numeric values in the semantic version string
return preg_replace("/\D+/", "", $semanticVersionString);
}
function isVersionNumberGreater($serverFileVersion, $clientFileVersion): bool {
// receives two semantic versions (1.0.4) and compares their numeric value (104)
// true when server version is greater than client version (105 > 104)
return removeAllNonDigits($serverFileVersion) > removeAllNonDigits($clientFileVersion);
}
function isValidVersionString($versionString) {
// true when matches semantic version numbering: 0.0.0
return preg_match("/\d\.\d\.\d/", $versionString);
}

Related

CORS error with UnityWebRequest from ASP.NET Web API 6.0 with CORS enabled

I'm working on a WebGL game with Unity in which I am attempting to call a custom built ASP.NET Web API that's built specifically to work with the game. I've added the code I thought was necessary to get CORS working for making the API requests from the browser, but I keep getting the same error: "Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource."
I've tried several different variations of how to implement CORS with the API in the program.cs script. I tried:
builder.Services.AddCors(p => p.AddPolicy("corspolicy", build =>
{
build.WithOrigins("*").AllowAnyMethod().AllowAnyHeader();
}));
And
builder.Services.AddCors(o =>
{
o.AddPolicy("corspolicy", build =>
build.WithOrigins("*")
.AllowAnyMethod()
.AllowAnyHeader()
});
Of course, I also included:
app.UseHttpsRedirection();
app.UseCors();
app.UseAuthorization();
app.MapControllers();
app.Run();
According to the documentation and the tutorials I followed, it looks like I added the correct code in the correct places, but when I publish the API to Azure and try to call it from the WebGL game, I always get that same error that I listed above.
I also tried using the AddDefaultPolicy() with just UseCors(), but that had the same result.
I've tried plugging the URL into Postman and HTTPie along with the header: "Origin": "http://127.0.0.1:5500", to mimic the game running on my local machine, and when I include that, it does return an "Acces-Control-Allow-Origin" header in the response set to either "*" or "http://127.0.0.1:5500" (I've tried both), depending on what I used in WithOrigins().
In Unity I'm making the API call like this:
public void MakeAPIRequest()
{
_webRequest = CreateRequest($"https://apiurl...");
_APICallType = APICallType.login;
_webRequestAsyncOperation = _webRequest.SendWebRequest();
_webRequestAsyncOperation.completed += GetRequestAsyncOperation_completed;
}
private UnityWebRequest CreateRequest(string path, RequestType type = RequestType.GET, object data = null)
{
var request = new UnityWebRequest(path, type.ToString());
if (data != null)
{
var bodyRaw = Encoding.UTF8.GetBytes(JsonUtility.ToJson(data));
request.uploadHandler = new UploadHandlerRaw(bodyRaw);
}
request.downloadHandler = new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/json");
return request;
}
I'm currently at a loss for how to get this working, so if anyone here has any advice about what to try or look into, I would really appreciate it.

Properly implement In-App Updates in App Center?

I am reading this documentation/article from Microsoft on how to Distribute Mobile apps with app center. The problem is I really don't understand how to implement this. I have a app on app center (Android) I want to implement mandatory update so that I can eliminate the bugs of the previous version. I tried to distribute the app with mandatory update enabled and it is not working. How can I fix this?
https://learn.microsoft.com/en-us/appcenter/distribution/
Here is what I did I added this code on my App.xaml.cs (XAMARIN FORMS PROJECT):
protected override void OnStart ()
{
AppCenter.Start("android={Secret Code};", typeof(Analytics), typeof(Crashes), typeof(Distribute));
Analytics.SetEnabledAsync(true);
Distribute.SetEnabledAsync(true);
Distribute.ReleaseAvailable = OnReleaseAvailable;
}
bool OnReleaseAvailable(ReleaseDetails releaseDetails)
{
string versionName = releaseDetails.ShortVersion;
string versionCodeOrBuildNumber = releaseDetails.Version;
string releaseNotes = releaseDetails.ReleaseNotes;
Uri releaseNotesUrl = releaseDetails.ReleaseNotesUrl;
var title = "Version " + versionName + " available!";
Task answer;
if (releaseDetails.MandatoryUpdate)
{
answer = Current.MainPage.DisplayAlert(title, releaseNotes, "Download and Install");
}
else
{
answer = Current.MainPage.DisplayAlert(title, releaseNotes, "Download and Install", "Ask Later");
}
answer.ContinueWith((task) =>
{
if (releaseDetails.MandatoryUpdate || (task as Task<bool>).Result)
{
Distribute.NotifyUpdateAction(UpdateAction.Update);
}
else
{
Distribute.NotifyUpdateAction(UpdateAction.Postpone);
}
});
return true;
}
And here is what I added on my MainActivity.cs(ANDROID PROJECT):
AppCenter.Start("{Secret Code}", typeof(Analytics), typeof(Crashes), typeof(Distribute));
Looking at this App Center documentation here for Xamarin Forms -
You can customize the default update dialog's appearance by implementing the ReleaseAvailable callback. You need to register the callback before calling AppCenter.Start
It looks like you need to swap your current ordering to get in-app updates working.
There could be a lot of different reasons as to why they are not working. As you can see in the Notes here and here,
Did your testers download the app from the default browser?
Are cookies enabled for the browser in their settings?
Another important point you'll read in the links, is that the feature is only available for listed distribution group users. It is not for all your members. You could use a simple version checker for your purpose instead or you could use a plugin.

Firefox Native Messaging runtime.LastError not giving any errors in case of no Native application installed on Connectnative

I am trying to check whether the Native app is installed or not , If it is not I have to prompt the user to download it from the webpage. For chrome I used to achieve by checking the error messages from runtime.LastError. However in case of Firefox it gives error only in console No such native application extension_name and not catching it in the runtime.LastError method.
Is there any way that we can identify whether corresponding Native app is installed or not ?
I am facing issue when Native app is not installed and browser.runtime.lastError is not giving any error.
Can you please suggest if there is any way in Firefox Webextension that we can catch such errors and identify it in code whether the corresponding Native app is installed or not on the user machine.
It will really helpful if someone can provide some info on this.
for e.g. :
startNativeApp: function(force){
// note that when the native app is opened and ready, it will call "_ABC_onAgentReady"
ABC.log('Starting native app.');
if (!ABC.appConnected) {
try {
ABC.nativeAppPort = browser.runtime.connectNative(_ABC_native_app_id);
ABC.nativeAppPort.onMessage.addListener(ABC.onNativeMessageReceived);
ABC.nativeAppPort.onDisconnect.addListener(ABC.onNativeAppDisconnected);
ABC.appInstalled = true;
ABC.appConnected = true;
} catch(e) {
ABC.log('Error starting native app: ' + e.message, 'ERR');
}
} else if (force === true) {
ABC.log('Native app is already running; attempting to stop and will restart in 750ms.');
ABC.stopNativeApp();
setTimeout(function() { ABC.startNativeApp(true); }, 750);
}
},
onNativeAppDisconnected: function(message) {
console.log("ABC LastError : "+browser.runtime.lastError);
console.log("ABC LastError : "+ABC.nativeAppPort.error);
console.log("ABC LastError : "+JSON.stringify(message));
ABC.appConnected = false;
ABC.nativeAppPort = null;
ABC.appInstalled = false;
if (browser.runtime.lastError && (browser.runtime.lastError.message.indexOf("No such native application") !== -1 )) {
ABC.appInstalled = false;
}
// cleanup: reset the sig data so that it is re-requested on the next scan
_ABC_sa_data = "";
_ABC_sigs = "";
if (browser.storage && browser.storage.local) {
browser.storage.local.set({ uid: _ABC_be_uid }, null);
}
ABC.log('Send message to page to stop.');
ABC.sendMessageToPage({ onNativeAppDisconnected: '' });
ABC.log('Native app disconnected.');
},
Issue here was that port.error was not giving any error response in Firefox versions less than 52 , Due to which I was facing problem in identifying whether native app is installed or not.
After discussion on Mozilla Community (https://discourse.mozilla-community.org/t/firefox-native-messaging-runtime-lasterror-not-giving-any-errors-in-case-of-no-native-application-installed-on-connectnative/12880/4) , we found that it is actually missed and a bug is already reported : https://bugzilla.mozilla.org/show_bug.cgi?id=12994116
which will be resolved in Firefox 52.
However , I need to support Firefox 50 also , so the alternate I am using is to call native application in starting to find out whether it is installed or not.
If I got back response than it is installed otherwise it is not.
However specific error messages will be available from Firefox52.
Right now at chrome 109 the following approaches won't work after connectNative:
chrome.runtime.lastError. The error is printed because it is visible in the log but right after the call it is undefined.
console.error = function (arg) {/**/}. Is not working to replace the default function.
port.name is "" in both cases (error or no error).
port.onDisconnect is not called if the application is missing.
The only solution left is to call a third checker:
const promise=chrome.runtime.sendNativeMessage("appname", { /*text: ""*/ });//,check_response
promise.then(check_response,check_error);
In Firefox there is no runtime.lastError.
The listener function you pass to runtime.Port.onDisconnect isn't passed the message, it's passed the port itself.
You then want port.error.
See the documentation for onDisconnect here https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/runtime/Port

cordova record audio not work windows 10 mobile

I work for some time with the media cordova plugin on android and ios mobile on to record audio files. It works well. However on windows, while recording no error occurred, but no file exists. Just when the application goes into background and we return it, an error with code like 2147483648 (I have not found any information relevant to my problem with this code).
function recordAudio() {
var src = "ms-appdata:///temp/sound.m4a";;
var mediaRec = new Media(src,
// success callback
function() {
console.log("recordAudio():Audio Success");
},
// error callback
function(err) {
console.log("recordAudio():Audio Error: "+ err.code);
});
// Record audio
mediaRec.startRecord();
}
I can not find solutions or similar problems. The rest github does not included the problems.
In MediaProxy.js (plugins/cordova-plugin-media/mediaProxy.js)
There is this constant:
var PARAMETER_IS_INCORRECT = -2147024809;
Is this the error you are getting? If so, this only seems to be used in one place, if there is no scheme for the path. Take a look at setTemporaryFsByDefault() in that same file.

Detecting Gmail attachment downloads

Is there a way to detect if a particular file that is being downloaded is a Gmail attachment?
I am looking for a way to write a Greasemonkey script which would help me organize the downloads, based on their download sources, say Gmail email attachments would have a different behavior from other stuff.
So far, I've found out that attachments redirect to https://mail-attachment.googleusercontent.com/attachment/u/0/ , which I guess is not sufficient.
EDIT
Since an add-on would be more powerful than a userscript, I've decided to pursue the Add On idea. However, the problem of detection remains unsolved.
This is too complicated for just one question; it has at least these major parts:
Do you want to redirect downloads when the user clicks, or automatically download select files? Clarify the question.
Your GM script must identify the appropriate download links, and on which pages, and for which views? For gMail, this is not a trivial task, and the question needs to be clearer. It's worthy of a whole question just on this issue given the variety of views and AJAX involved.
Once identified, the script probably needs to intercept clicks on those links. (Depends on your goal (clarify!) and what the Firefox extension can do.)
Greasemonkey needs to interact with an extension that either intercepts the user-initiated download, or allows for an automatic download. I've detailed the auto-download approach, below.
Once your script has identified the appropriate file URLs and/or links (Open a new question for more help with that, and include pictures of the types of pages and links you want.), it can interface with a Firefox add-on, like the one below, to automatically save those files.
Automatically saving files from Greasemonkey with the help of an additional Add-on:
WARNING: The following is a working proof of concept for education only. It has no security features, and if you use it as-is, for actual surfing, some webpage or script writer or extension writer will use it to completely pwn your computer.
If you use the Add-on builder or SDK to install or "Test" the DANGER. DANGER. DANGER. File download utility,
Then you can use a Greasemonkey script, like this, to automatically save files:
// ==UserScript==
// #name _Call our File download add-on to trigger a file download.
// #include https://mail.google.com/mail/*
// #include https://stackoverflow.com/questions/14440362/*
// #require http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js
// #grant GM_addStyle
// ==/UserScript==
/*- The #grant directive is needed to work around a design change
introduced in GM 1.0. It restores the sandbox.
*/
var fileURL = "http://userscripts.org/scripts/source/29222.user.js";
var savePath = "D:\\temp\\";
var extensionLoaded = false;
window.addEventListener ("ImAlivefromExtension", function (zEvent) {
console.log ("The test extension appears to be loaded!", zEvent.detail);
extensionLoaded = true;
} );
window.addEventListener ("ReplyToDownloadRequest", function (zEvent) {
//var xxxx = JSON.parse (zEvent.detail);
console.log ("Extension replied: ", zEvent.detail);
} );
$("body").prepend ('<button id="gmFileDownloadBtn">Click to File download request.</button>');
$("#gmFileDownloadBtn").click ( function () {
if (extensionLoaded) {
detailVal = JSON.stringify (
{targFileURL: fileURL, targSavePath: savePath}
);
var zEvent = new CustomEvent (
"SuicidalDownloadRequestToAddOn",
{"detail": detailVal }
);
window.dispatchEvent (zEvent);
}
else {
alert ("The file download extension is not loaded!");
}
} );
You can test the script on this SO question page.
Note that any other extension, userscript, web page, or plugin can listen to or send spoof events, the only security, so far, is to limit which pages the extension runs on.
For reference, the extension source files are below. The rest is supplied by Firefox's Add-on SDK.
The content script:
var zEvent = new CustomEvent ("ImAlivefromExtension",
{"detail": "GM, DANGER, DANGER, DANGER, File download utility" }
);
window.dispatchEvent (zEvent)
window.addEventListener ("SuicidalDownloadRequestToAddOn", function (zEvent) {
console.log ("Extension received download request: ", zEvent.detail);
//-- Relay request to extension main.js
self.port.emit ("SuicidalDownloadRequestRelayed", zEvent.detail);
//-- Reply back to GM, or whoever is pretending to be GM.
var zEvent = new CustomEvent ("ReplyToDownloadRequest",
{"detail": "Your funeral!" }
);
window.dispatchEvent (zEvent)
} );
The background JS:
//--- For security, MAKE THESE AS RESTRICTIVE AS POSSIBLE!
const includePattern = [
'https://mail.google.com/mail/*',
'https://stackoverflow.com/questions/14440362/*'
];
let {Cc, Cu, Ci} = require ("chrome");
Cu.import ("resource://gre/modules/Services.jsm");
Cu.import ("resource://gre/modules/XPCOMUtils.jsm");
Cu.import ("resource://gre/modules/FileUtils.jsm");
let data = require ("sdk/self").data;
let pageMod = require ('sdk/page-mod');
let dlManageWindow = Cc['#mozilla.org/download-manager-ui;1'].getService (Ci.nsIDownloadManagerUI);
let fileURL = "";
let savePath = "";
let activeWindow = Services.wm.getMostRecentWindow ("navigator:browser");
let mod = pageMod.PageMod ( {
include: includePattern,
contentScriptWhen: 'end',
contentScriptFile: [ data.url ('ContentScript.js') ],
onAttach: function (worker) {
console.log ('DANGER download utility attached to: ' + worker.tab.url);
worker.port.on ('SuicidalDownloadRequestRelayed', function (message) {
var detailVal = JSON.parse (message);
fileURL = detailVal.targFileURL;
savePath = detailVal.targSavePath;
console.log ("Received request to \ndownload: ", fileURL, "\nto:", savePath);
downloadFile (fileURL, savePath);
} );
}
} );
function downloadFile (fileURL, savePath) {
dlManageWindow.show (activeWindow, 1);
try {
let newFile;
let fileURIToDownload = Services.io.newURI (fileURL, null, null);
let persistWin = Cc['#mozilla.org/embedding/browser/nsWebBrowserPersist;1']
.createInstance (Ci.nsIWebBrowserPersist);
let fileName = fileURIToDownload.path.slice (fileURIToDownload.path.lastIndexOf ('/') + 1);
let fileObj = new FileUtils.File (savePath);
fileObj.append (fileName);
if (fileObj.exists ()) {
console.error ('*** Error! File "' + fileName + '" already exists!');
}
else {
let newFile = Services.io.newFileURI (fileObj);
let newDownload = Services.downloads.addDownload (
0, fileURIToDownload, newFile, fileName, null, null, null, persistWin, false
);
persistWin.progressListener = newDownload;
persistWin.savePrivacyAwareURI (fileURIToDownload, null, null, null, "", newFile, false);
}
} catch (exception) {
console.error ("Error saving the file! ", exception);
dump (exception);
}
}
So far from what you are saying,the only thing you can do is making add-on(Firefox) and Extension(for chrome if you want).
If you have closer look at download of attachment,it happens when:
1) You click on icon of attachments
2) If you click download
For these two things you can find the click event of <a> tag containing download_url value.You can easily do that using js/jquery for creting extension.
So you can write the functionality when user tries to download attachment.
You could use Gmail contextual gadgets to modify the behavior on the Google side:
Gmail Contexual Gadgets
Contextual Gadgets don't have direct access to attachments but server side, you could use IMAP to access the attachment (based on the Gmail message ID identified by the gadget):
Gmail IMAP Extensions
Using gadgets and server-side IMAP has the advantage of being browser-agnostic.
It's not entirely clear what you want to do differently with the downloaded Gmail attachment as opposed to any given download (save it to a different location? Perform actions upon the attachment data?) But the contextual gadget and IMAP should give you some chance to modify the attachment data as needed before the browser download begins.

Resources