diff --git a/app.js b/app.js index 0540da48..c7a3f9cb 100644 --- a/app.js +++ b/app.js @@ -28,6 +28,10 @@ ipc.on('showAbout', function(event, message) { ipc.on('showPreferences', function(event, message) { !Ext.cq1('preferences') ? Ext.create('Rambox.view.preferences.Preferences').show() : ''; }); +ipc.on('grantPermissions', async function() { + await require('electron').remote.systemPreferences.askForMediaAccess('microphone'); + await require('electron').remote.systemPreferences.askForMediaAccess('camera'); +}); ipc.on('autoUpdater:check-update', function() { Rambox.app.checkUpdate(); }); diff --git a/app/Application.js b/app/Application.js index 830a12b3..17dbfe80 100644 --- a/app/Application.js +++ b/app/Application.js @@ -60,6 +60,48 @@ Ext.define('Rambox.Application', { }) })(); + if ( !localStorage.getItem('hideMacPermissions') && process.platform === 'darwin' && (require('electron').remote.systemPreferences.getMediaAccessStatus('microphone') !== 'granted' || require('electron').remote.systemPreferences.getMediaAccessStatus('camera') !== 'granted') ) { + console.info('Checking mac permissions...'); + Ext.cq1('app-main').addDocked({ + xtype: 'toolbar' + ,dock: 'top' + ,style: {background: '#30BBF3'} + ,items: [ + '->' + ,{ + xtype: 'label' + ,html: 'Rambox CE needs permissions to use Microphone and Camera for the apps.' + } + ,{ + xtype: 'button' + ,text: 'Grant permissions' + ,ui: 'decline' + ,handler: async function(btn) { + ipc.send('grantPermissions'); + Ext.cq1('app-main').removeDocked(btn.up('toolbar'), true); + } + } + ,{ + xtype: 'button' + ,text: 'Never ask again' + ,ui: 'decline' + ,handler: function(btn) { + Ext.cq1('app-main').removeDocked(btn.up('toolbar'), true); + localStorage.setItem('hideMacPermissions', true); + } + } + ,'->' + ,{ + glyph: 'xf00d@FontAwesome' + ,baseCls: '' + ,style: 'cursor:pointer;' + ,handler: function(btn) { Ext.cq1('app-main').removeDocked(btn.up('toolbar'), true); } + } + ] + }); + } + + Ext.getStore('ServicesList').load(function (records, operations, success) { if (!success) { diff --git a/app/store/ServicesList.js b/app/store/ServicesList.js index 2a96cd5a..59d42328 100644 --- a/app/store/ServicesList.js +++ b/app/store/ServicesList.js @@ -10,7 +10,7 @@ Ext.define('Rambox.store.ServicesList', { ,proxy: { type: 'ajax', - url: 'https://us-central1-rambox-d1326.cloudfunctions.net/ceApps', + url: 'https://raw.githubusercontent.com/saenzramiro/rambox/gh-pages/api/services.json', reader: { type: 'json', rootProperty: 'responseText' diff --git a/app/ux/WebView.js b/app/ux/WebView.js index 3e627164..f4848dd6 100644 --- a/app/ux/WebView.js +++ b/app/ux/WebView.js @@ -238,6 +238,7 @@ Ext.define('Rambox.ux.WebView',{ return { xtype: 'statusbar' + ,id: me.id+'statusbar' ,hidden: !me.record.get('statusbar') ,keep: me.record.get('statusbar') ,y: floating ? '-18px' : 'auto' @@ -275,7 +276,8 @@ Ext.define('Rambox.ux.WebView',{ if ( !me.record.get('enabled') ) return; var webview = me.getWebView(); - let googleLoginURLs = ['accounts.google.com/signin/oauth', 'accounts.google.com/ServiceLogin'] + let googleLoginURLs = ['accounts.google.com/signin', 'accounts.google.com/ServiceLogin', ] + me.errorCodeLog = [] // Google Analytics Event ga_storage._trackEvent('Services', 'load', me.type, 1, true); @@ -321,6 +323,60 @@ Ext.define('Rambox.ux.WebView',{ me.onSearchText(e.result) }); + // On search text + webview.addEventListener('did-fail-load', function(e) { + console.info('The service fail at loading', me.src, e); + me.errorCodeLog.push(e.errorCode) + + var attempt = me.errorCodeLog.filter(function(code) { return code === e.errorCode }); + + // Error codes: https://cs.chromium.org/chromium/src/net/base/net_error_list.h + var msg = [] + msg[-2] = 'NET error: failed.' + msg[-3] = 'An operation was aborted (due to user action)' + msg[-7] = 'Connection timeout.' + msg[-21] = 'Network change.' + msg[-100] = 'The connection was reset. Check your internet connection.' + msg[-101] = 'The connection was reset. Check your internet connection.' + msg[-105] = 'Name not resolved. Check your internet connection.' + msg[-106] = 'There is no active internet connection.' + msg[-118] = 'Connection timed out. Check your internet connection.' + msg[-130] = 'Proxy connection failed. Please, check the proxy configuration.' + msg[-300] = 'The URL is invalid.' + msg[-324] = 'Empty response. Check your internet connection.' + + switch ( e.errorCode ) { + case 0: + break + case -3: // An operation was aborted (due to user action) I think that gmail an other pages that use iframes stop some of them making this error fired + if ( attempt.length <= 4 ) return + setTimeout(() => me.reloadService(me), 200); + me.errorCodeLog = [] + break; + case -2: + case -7: + case -21: + case -118: + case -324: + case -100: + case -101: + case -105: + attempt.length > 4 ? me.onFailLoad(msg[e.errorCode]) : setTimeout(() => me.reloadService(me), 2000); + break; + case -106: + me.onFailLoad(msg[e.errorCode]) + break; + case -130: + // Could not create a connection to the proxy server. An error occurred + // either in resolving its name, or in connecting a socket to it. + // Note that this does NOT include failures during the actual "CONNECT" method + // of an HTTP proxy. + case -300: + attempt.length > 4 ? me.onFailLoad(msg[e.errorCode]) : me.reloadService(me); + break; + } + }); + // Open links in default browser webview.addEventListener('new-window', function(e) { switch ( me.type ) { @@ -691,6 +747,12 @@ Ext.define('Rambox.ux.WebView',{ } } + ,onFailLoad: function(v) { + let me = this + me.errorCodeLog = [] + setTimeout(() => Ext.getCmp(me.id+'statusbar').setStatus({ text: ' The service failed at loading, Error: '+ v }), 1000); + } + ,showSearchBox: function(v) { var me = this; if ( !me.record.get('enabled') ) return; diff --git a/app/view/add/Add.js b/app/view/add/Add.js index c26af425..5cddec02 100644 --- a/app/view/add/Add.js +++ b/app/view/add/Add.js @@ -68,7 +68,7 @@ Ext.define('Rambox.view.add.Add',{ ,emptyText: me.record.get('url') === '___' ? 'https://' : '' ,validator: function(v) { if ( !me.edit ? me.record.get('url') !== '___' : me.service.get('url').indexOf('https://___') === 0 ) return true - if ( v.match(/^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?$/i) === null && v.match(/^http:\/\/\w+(\.\w+)*(:[0-9]+)?\/?(\/[.\w]*)*$/) === null ) return false; + if ( v.match(/^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?$/i) === null && v.match(/^https:\/\/\w+(\.\w+)*(:[0-9]+)?\/?(\/[.\w]*)*$/) === null && v.match(/^http:\/\/\w+(\.\w+)*(:[0-9]+)?\/?(\/[.\w]*)*$/) === null ) return false; return true; } ,listeners: { diff --git a/electron/menu.js b/electron/menu.js index 4a5729a4..a49c9882 100644 --- a/electron/menu.js +++ b/electron/menu.js @@ -1,6 +1,7 @@ 'use strict'; const os = require('os'); const electron = require('electron'); +const { systemPreferences } = require('electron') const app = electron.app; const BrowserWindow = electron.BrowserWindow; const shell = electron.shell; @@ -229,7 +230,7 @@ module.exports = function(config) { } ]; - if (process.platform === 'darwin') { + if ( process.platform === 'darwin' ) { tpl.unshift({ label: appName, submenu: [ @@ -288,6 +289,18 @@ module.exports = function(config) { } ] }); + helpSubmenu.push({ + type: 'separator' + }); + helpSubmenu.push({ + label: 'Grant Microphone and Camera permissions', + visible: systemPreferences.getMediaAccessStatus('microphone') !== 'granted' || systemPreferences.getMediaAccessStatus('camera') !== 'granted', + click(item, win) { + const webContents = win.webContents; + const send = webContents.send.bind(win.webContents); + send('grantPermissions'); + } + }); } else { tpl.unshift({ label: '&'+locale['menu.file[0]'], diff --git a/package-lock.json b/package-lock.json index 062f358c..89575469 100644 --- a/package-lock.json +++ b/package-lock.json @@ -843,7 +843,8 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.0.1.tgz", "integrity": "sha512-HRZPIjPcbwAVQvOTxR4YE3o8Xs98NqbbL1iEZDCz7CL8ql0Lt5iOyJFxfnAB0oFs8Oh02F/lLlg30Mexv46LjA==", - "dev": true + "dev": true, + "optional": true }, "boxen": { "version": "3.2.0", @@ -2280,6 +2281,59 @@ "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-2.2.17.tgz", "integrity": "sha512-v+Af5W5z99ehhaLOfE9eTSXUwjzh2wFlQjz51dvkZ6ZIrET6OB/zAZPvsuwT6tm3t5x+M1r+Ed3U3xtPZYAyuQ==" }, + "electron-notarize": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/electron-notarize/-/electron-notarize-0.2.1.tgz", + "integrity": "sha512-oZ6/NhKeXmEKNROiFmRNfytqu3cxqC95sjooG7kBXQVEUSQkZnbiAhxVh5jXngL881G197pbwpeVPJyM7Ikmxw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "fs-extra": "^8.1.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "graceful-fs": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", + "dev": true + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, "electron-osx-sign": { "version": "0.4.10", "resolved": "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.4.10.tgz", @@ -3613,7 +3667,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } diff --git a/package.json b/package.json index 830a0ab3..eab3e858 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Rambox", "productName": "Rambox", - "version": "0.7.3", + "version": "0.7.4", "description": "Free and Open Source messaging and emailing app that combines common web applications into one.", "main": "electron/main.js", "repository": { @@ -66,18 +66,27 @@ "appId": "com.grupovrs.ramboxce", "asar": true, "electronDownload": { - "version": "7.1.1" + "version": "7.1.12" }, "mac": { "category": "public.app-category.productivity", "artifactName": "Rambox-${version}-mac.${ext}", "target": [ "default" - ] + ], + "hardenedRuntime": true, + "gatekeeperAssess": false, + "entitlements": "resources/installer/entitlements.mac.plist", + "entitlementsInherit": "resources/installer/entitlements.mac.plist", + "extendInfo": { + "NSMicrophoneUsageDescription": "Apps inside Rambox CE may need access to your microphone. Please, grant access to have a better experience.", + "NSCameraUsageDescription": "Apps inside Rambox CE may need access to your camera. Please, grant access to have a better experience." + } }, "dmg": { "title": "Rambox", "iconSize": 128, + "sign": false, "contents": [ { "x": 355, @@ -189,6 +198,7 @@ "csvjson": "4.3.3", "electron": "7.1.12", "electron-builder": "21.2.0", + "electron-notarize": "0.2.1", "electron-packager": "^12.1.0", "mocha": "^5.2.0", "spectron": "^3.8.0" diff --git a/resources/installer/entitlements.mac.plist b/resources/installer/entitlements.mac.plist new file mode 100644 index 00000000..7d10056a --- /dev/null +++ b/resources/installer/entitlements.mac.plist @@ -0,0 +1,14 @@ + + + + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.device.microphone + + com.apple.security.device.camera + + com.apple.security.device.audio-input + + + diff --git a/resources/installer/notarize.js b/resources/installer/notarize.js new file mode 100644 index 00000000..5d2b5562 --- /dev/null +++ b/resources/installer/notarize.js @@ -0,0 +1,17 @@ +const { notarize } = require('electron-notarize'); + +exports.default = async function notarizing(context) { + const { electronPlatformName, appOutDir } = context; + if (electronPlatformName !== 'darwin') { + return; + } + + const appName = context.packager.appInfo.productFilename; + + return await notarize({ + appBundleId: 'com.grupovrs.ramboxce', + appPath: `${appOutDir}/${appName}.app`, + appleId: 'saenzramiro@gmail.com', + appleIdPassword: process.env.APPLE_ID_PWD + }); +}; \ No newline at end of file