Compare commits

...

32 Commits

Author SHA1 Message Date
Keith Maika d0bcd90f67 Remove deb from build target name.
Update README with new target name and AppImage link.
2022-05-11 09:03:32 +02:00
Keith Maika c016bab4b3 Add AppImage build target. 2022-05-11 09:03:32 +02:00
Keith Maika ff8eaa91ea Allow the create account page to display properly.
Make the reconnect menu option always reload the index.html page instead of whatever the current page may be.
2022-04-13 09:56:59 +02:00
JC Brand 3c52c0daff Remove unnecessary `build` recipe from Makefile and update package-lock 2022-04-09 07:03:14 +02:00
JC Brand cf822e54f8 Set version for builds 2022-04-07 14:54:06 +02:00
Keith Maika adfdde2f20 v9.1.0
- Update dependencies.
- Update changelog
- Update screenshots
2022-04-07 14:26:48 +02:00
Keith Maika 9436bb9802
Applied style guide spacing. 2022-03-29 17:31:30 -04:00
Keith Maika 50c0978148
Small fixes and code cleanup
- Fix "false" showing in username field.
 - Delay showing the window until the page is rendered to prevent flashing
 - Fix various issues highlighted by Webstorm's inspections.
2022-03-29 17:30:38 -04:00
Keith Maika 2697d61ab1 Prevent a second instance. 2022-03-29 22:43:50 +02:00
JC Brand 5aad0dfe34 Add a `clean` command 2022-03-29 20:07:12 +02:00
JC Brand 3f4aabf734 Add package-lock.json and .nvmrc 2022-03-29 20:07:06 +02:00
JC Brand cc565b5d1f Don't add automatic fallback to the Converse connection manager 2022-03-29 20:06:14 +02:00
Keith Maika 2740bac95b
Remove connection manager menu option.
Converse.js now includes a connection manager option on the login form so this option is not necessary.
2022-03-26 00:24:15 -04:00
JC Brand 6b9910aa13 Merge branch 'kicken-pr-electron-16' 2022-03-23 21:56:57 +01:00
Keith Maika 6f5cc13b2e Fix icon path on MacOS.
Remove unnecessary variable.
2022-03-15 22:27:47 -04:00
Keith Maika 933c853395
Delay loading the content file until after all the handlers are configured. 2022-03-14 18:40:14 -04:00
Keith Maika 702c174fb0
Update to converse.js with windows fixes. 2022-03-14 18:29:06 -04:00
Keith Maika 7f7703b537
Use minified version which fixes emojis not loading. 2022-02-16 15:57:17 -05:00
Keith Maika e38d68579d
Allow configuration of the connection manager URL. 2022-02-16 15:57:17 -05:00
Keith Maika 4b6cf4b86a
Update to Electron 16
Re-implement tray envelope icon for unread messages.

Fix minimize on close bug.
2022-02-16 15:57:16 -05:00
Keith Maika 4ce02148e2 Re-implement minimize on close. 2022-01-21 19:36:18 +01:00
JC Brand 1a62af799b Refactor converse-desktop and bump to Converse 9
This removes all AngularJS code

Angular version 1 is unmaintained and largely obsolete since years.

Additionally, I'd rather re-use the conventions and libraries from
Converse instead of having different ones in converse-desktop.

This means we're losing some functionality, such as the app settings
and the about modal.

This is unfortunate, but ideally (non-Electron) app settings should
be implemented in Converse itself and not in this repo.
2022-01-21 19:36:18 +01:00
JC Brand cc3dae61ad Update README 2021-12-01 11:52:14 +01:00
Keith Maika 4c6ce2e905 Fix window not re-showing on MacOS 2021-02-13 12:07:22 -05:00
Keith Maika ff0bf695a8 Fix build commands 2021-02-12 22:17:05 -05:00
Keith Maika 8f106c65ec Update version information in about screen. 2021-02-12 22:01:52 -05:00
Keith Maika a9d3f89f3f Upgrade to converse 7 2021-02-12 21:59:16 -05:00
Keith Maika fb72aee403 Update electron and related modules. 2021-02-12 21:52:49 -05:00
JC Brand fce4f0af8e
Add README section describing the licensing situation 2020-08-25 14:34:05 +03:00
Nick Denry 58c73e367f Update linux build section due to new scoped name 2020-08-07 21:47:32 +03:00
Nick Denry 4111bb1050 Update installers 2020-08-07 21:47:21 +03:00
Nick Denry fe46a0698a Changelog update 2020-08-07 20:53:14 +03:00
56 changed files with 9309 additions and 1247 deletions

22
.eslintrc.js Normal file
View File

@ -0,0 +1,22 @@
module.exports = {
"env": {
"browser": true,
"es2021": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 13,
"sourceType": "module"
},
"globals": {
"__dirname": true,
"converse": true,
"exports": true,
"module": true,
"process": true,
"require": true,
},
"rules": {
"prefer-const": "error",
}
};

3
.gitignore vendored
View File

@ -1,3 +1,4 @@
node_modules
.DS_*
package-lock.json
dist/
.idea/

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
v16.13.1

View File

@ -1,3 +1,15 @@
## 9.1.0 (2022-04-07)
- Update to converse.js 9.1.0
- Update to Electron to 18.0.1
- Remove angularjs and simplify application
## 0.1.0 (2020-08-07)
- Fix error handling for version check
- Relicensed under MPL-2.0 (@see libsignal-protocol.js exclusion at README section)
- First release since forked from [Chimeverse project](https://github.com/nick-denry/Chimeverse)
## 0.0.1 (Unreleased)
- Forked from [Chimeverse](https://github.com/nick-denry/Chimeverse)

19
Makefile Normal file
View File

@ -0,0 +1,19 @@
BIN ?= ./node_modules/.bin/
ESLINT ?= ./node_modules/.bin/eslint
clean:
npm run clean
node_modules: package.json package-lock.json
npm i
serve: node_modules
npm start
dist: node_modules
npm dist
.PHONY: eslint
eslint: node_modules
$(ESLINT) *.js
$(ESLINT) app/**/*.js

View File

@ -1,62 +1,88 @@
<h2 style="text-align: center">
<a href="https://conversejs.org" target="_blank" rel="noopener">
<img alt="Converse-Desktop" src="https://github.com/conversejs/converse.js/blob/master/logo/readme.png" width="480">
</a>
</h2>
# Converse Desktop
This project started as a fork of Nick Denry's [Chimeverse](https://github.com/conversejs/converse-desktop).
## Jabber/XMPP client based on Converse.js and Electron
#### Jabber/XMPP client based on Converse.js and Electron
[![XMPP Chat](https://conference.conversejs.org/muc_badge/discuss@conference.conversejs.org)](https://inverse.chat/#converse/room?jid=discuss@conference.conversejs.org)
![Version](https://img.shields.io/npm/v/chimeverse/latest.svg)
![Downloads](https://img.shields.io/npm/dt/chimeverse.svg)
![License](https://img.shields.io/npm/l/chimeverse.svg)
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=6MZ5YRYEDSVSQ&source=url" title="Donate once-off to this project using Paypal">
<img src="https://img.shields.io/badge/paypal-donate-yellow.svg" alt="PayPayl donate button" />
</a>
A basic integration of [Converse.js](https://conversejs.org/) and Electron. With OMEMO.
It should happen once. A very basic integration of [Converse.js](https://conversejs.org/) and Electron. With OMEMO.
## Screenshots and features
#### Screenshots and feautures
<p float="left">
<img width="403" alt="Account form" src="https://user-images.githubusercontent.com/1450983/89672948-33bc0e80-d8ee-11ea-983f-21bbb707b45d.png">
<img width="403" alt="Main window" src="https://user-images.githubusercontent.com/1450983/89673019-4f271980-d8ee-11ea-8058-0ac6269983aa.png">
<img width="403" alt="Chat" src="https://user-images.githubusercontent.com/1450983/89673064-68c86100-d8ee-11ea-86c4-137e1b95dae7.png">
<img width="403" alt="Settings screen" src="https://user-images.githubusercontent.com/1450983/89673104-7847aa00-d8ee-11ea-8d30-8f84e7709e7c.png">
[![Login screen](https://user-images.githubusercontent.com/6234547/161444310-ed1157fe-4f09-4334-b133-f16a8b1ead86.jpg)](https://user-images.githubusercontent.com/6234547/161444142-87008557-a0ae-414d-ab81-9740502dab30.jpg)
[![One-to-one chat](https://user-images.githubusercontent.com/6234547/161444320-62179698-d4cb-4522-8ee4-5fd727bbff0d.jpg)](https://user-images.githubusercontent.com/6234547/161444152-8a44b284-48a6-4c8e-a16e-95399b4def16.jpg)
[![Multi-user chat](https://user-images.githubusercontent.com/6234547/161444323-5fe7e478-1923-47c3-9e99-84020fb44009.jpg)](https://user-images.githubusercontent.com/6234547/161444156-eb2224a7-6082-4fe7-aa55-44eec093e04d.jpg)
</p>
- Permanent account storage
- Tray icon
- Tray notifications
- All the best from Converse.js like system notifications, MAM, OMEMO etc. See details at [Converse.js](https://conversejs.org/)
#### Changelog
## Changelog
See [CHANGES.md](https://github.com/conversejs/converse-desktop/blob/master/CHANGES.md)
#### Run with npm
```
#### Latest release installers
| Operation System | Download link |
|------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------|
| macOS | [Converse_Desktop-9.1.0_x64.dmg](https://github.com/conversejs/converse-desktop/releases/download/v9.1.0/Converse.Desktop-9.1.0.dmg) |
| Windows | [Converse_Desktop_Setup-9.1.0_x64.exe](https://github.com/conversejs/converse-desktop/releases/download/v9.1.0/Converse.Desktop.Setup.9.1.0.exe) |
| Linux DEB | [converse_desktop-9.1.0_amd64.deb](https://github.com/conversejs/converse-desktop/releases/download/v9.1.0/converse_desktop-9.1.0_amd64.deb) |
| Linux AppImage | [converse_desktop-9.1.0_x86_64.AppImage](https://github.com/conversejs/converse-desktop/releases/download/v9.1.0/converse_desktop-9.1.0_x86_64.AppImage) |
| Linux other | [converse_desktop-9.1.0_x64.tar.gz](https://github.com/conversejs/converse-desktop/releases/download/v9.1.0/converse_desktop-9.1.0_x64.tar.gz) |
- [All releases](https://github.com/conversejs/converse-desktop/releases)
## Build from source
```bash
git clone https://github.com/conversejs/converse-desktop.git
cd converse-desktop
npm i
$(npm bin)/electron-rebuild
```
Then, to run:
```bash
npm start
```
### Development
### Build targets:
Prepare
```
git clone https://github.com/conversejs/converse-desktop.git
cd converse-desktop
npm i
$(npm bin)/electron-rebuild
```
Build targets
| Operation System | Target |
-------------------|----------------
| macOS | `npm run dist` |
| Windows | `npm run dist:win64` |
| Linux DEB | `npm run dist:linux64deb` |
| Operation System | Target |
|------------------|------------------------|
| macOS | `npm run dist` |
| Windows | `npm run dist:win64` |
| Linux | `npm run dist:linux64` |
More targets could be added via `package.json`. See [electron builder docs](https://www.electron.build/configuration/configuration).
## License
Like Converse.js, Converse Desktop's files are released under the Mozilla Public License version 2 (MPLv2). The gist of this license is that the covered files must stay open source, and modifications to them need to be released under the same license, but new files (for example for your own plugin) don't have to be released under the same license.
However, libsignal library, which is required for OMEMO support is released under the GPLv3. The MPLv2 license is compatible with GPLv3 and when GPLv3 code is included, the entire project effectively is licensed under the GPLv3.
Any custom build of Converse Desktop without libsignal included will again be licensed
under the MPLv2.
## Acknowledgements
This project started as a fork of Nick Denry's [Chimeverse](https://github.com/conversejs/converse-desktop).
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=6MZ5YRYEDSVSQ&source=url" title="Donate once-off to this project using Paypal">
<img src="https://img.shields.io/badge/paypal-donate-yellow.svg" alt="PayPal donate button" />
</a>

View File

@ -1,10 +0,0 @@
let angApp = require(__dirname+'/../init')
angApp.controller('AboutController', function($scope, AppStateService, AppInfo) {
$scope.appInfo = AppInfo
$scope.closeAbout = () => {
AppStateService.set(AppStateService.APP_STATE_DEFAULT)
}
})

View File

@ -1,47 +0,0 @@
let angApp = require(__dirname+'/../init')
angApp.controller('DefaultController', function($scope, $timeout, $http, AppInfo) {
$scope.appInfo = AppInfo
let getUpdateInfo = () => {
$http({
url: $scope.appInfo.APP_RELEASES_CHECK_URL,
method: 'GET'
}).then((response) => {
let releaseTag = response.data[0].tag_name
let releaseVersion = parseInt(releaseTag.replace(/v|\./g, ''))
let appVersion = parseInt($scope.appInfo.APP_VERSION.replace(/v|\./g, ''))
if (appVersion < releaseVersion ) {
$scope.checkingForUpdate = 'updateAvailable'
}
else {
$scope.checkingForUpdate = 'latest'
}
}).catch((error) => {
$scope.checkingForUpdate = 'checkErr'
})
}
let checkForUpdate = (timeout = 5000) => {
$scope.checkingForUpdate = 'inProgress'
$timeout(() => {
getUpdateInfo()
}, timeout)
}
let checkForUpdateDelayed = (timeout = 5000) => {
$timeout(() => {
checkForUpdate()
}, timeout)
}
checkForUpdateDelayed()
$scope.checkRetry = ($event) => {
$event.preventDefault()
checkForUpdate()
}
})

View File

@ -1,7 +0,0 @@
let angApp = require(__dirname+'/../init')
angApp.controller('FooterController', function($scope, AppInfo) {
$scope.appInfo = AppInfo
})

View File

@ -1,21 +0,0 @@
let angApp = require(__dirname+'/../init')
angApp.controller('LoginController', function($scope, DesktopService, CredentialsServise) {
$scope.help = {}
$scope.showHelp = (item) => {
$scope.help[item] = typeof $scope.help[item] === 'undefined' ? true : !$scope.help[item];
}
$scope.addAccountAndLoginAction = () => {
CredentialsServise.addCredentials($scope.credentials.connectionManager,
$scope.credentials.login,
$scope.credentials.password
)
DesktopService.getCredentialsAndLogin()
$scope.accountForm.$setPristine()
$scope.accountForm.$setUntouched()
$scope.credentials = {}
}
})

View File

@ -1,40 +0,0 @@
let angApp = require(__dirname+'/../init')
angApp.controller('SettingsController', function ($scope, $rootScope, AppStateService, SettingsService) {
let formInitialized = false
$scope.settingsChanged = false
$scope.settingsSaved = false
const settingsSetPristine = () => {
$scope.settingsChanged = false
formInitialized = false
}
$scope.closeSettings = () => {
$scope.settings = SettingsService.loadAll()
settingsSetPristine()
AppStateService.set(AppStateService.APP_STATE_DEFAULT)
}
$scope.saveSettings = () => {
$scope.settingsSaved = true
SettingsService.saveAll($scope.settings)
settingsSetPristine()
}
$scope.restartApp = () => {
$rootScope.$broadcast('app:restart')
}
$scope.settings = SettingsService.loadAll()
$scope.$watch("settings", () => {
if (!formInitialized) {
formInitialized = true
} else {
$scope.settingsChanged = true
}
}, true)
})

View File

@ -0,0 +1,25 @@
const credentials = await import('../credentials.js');
converse.plugins.add('converse-desktop-credentials', {
initialize () {
const { _converse } = this;
const { api } = _converse;
api.listen.on('afterResourceBinding', () => {
if (_converse.connection.pass) {
credentials.addCredentials(
_converse.connection.service,
_converse.bare_jid,
_converse.connection.pass
).catch((reason) => {
console.log(reason);
});
}
});
api.listen.on('logout', () => {
credentials.getCredentials().then((result) => credentials.removeCredentials(result.login))
});
}
});

View File

@ -0,0 +1,24 @@
/* global api */
converse.plugins.add('converse-desktop-trayicon', {
initialize () {
const { _converse } = this;
let envelopeIsShowing = false;
async function hideEnvelope () {
if (envelopeIsShowing) {
await api.trayService.hideEnvelope();
envelopeIsShowing = false;
}
}
window.addEventListener('focus', hideEnvelope);
_converse.api.listen.on('chatBoxInitialized', hideEnvelope);
_converse.api.listen.on('chatBoxFocused', hideEnvelope);
_converse.api.listen.on('messageNotification', async () => {
await api.trayService.showEnvelope();
envelopeIsShowing = true;
});
}
});

33
app/credentials.js Normal file
View File

@ -0,0 +1,33 @@
/* global api */
async function addCredentials (connectionManager, login, password) {
const xmppService = login.split('@').pop()
await api.settings.set('connectionManager', connectionManager)
await api.settings.set('login', login)
await api.keytar.setPassword(xmppService, login, password)
}
async function getCredentials () {
const credentials = {}
credentials.login = (await api.settings.get('login')) || '';
if (credentials.login) {
credentials.connectionManager = await api.settings.get('connectionManager') || null
credentials.xmppService = credentials.login.split('@').pop()
credentials.password = await api.keytar.getPassword(credentials.xmppService, credentials.login)
}
return credentials;
}
async function removeCredentials (login) {
const xmppService = login.split('@').pop();
await api.keytar.deletePassword(xmppService, login);
await api.settings.unset('login');
await api.settings.unset('connectionManager');
}
export {
addCredentials,
getCredentials,
removeCredentials
}

View File

@ -1,12 +0,0 @@
const angular = require('angular')
let angApp = angular.module('app', [])
angApp.constant('AppInfo', {
APP_NAME: 'Converse Desktop',
APP_VERSION: 'v0.1.0',
APP_HOME: 'https://github.com/conversejs/converse-desktop',
APP_RELEASES_CHECK_URL: 'https://api.github.com/repos/conversejs/converse-desktop/releases',
APP_RELEASES_URL: 'https://github.com/conversejs/converse-desktop/releases'
});
module.exports = angApp

View File

@ -1,22 +0,0 @@
let angApp = require(__dirname + '/../init')
angApp.factory('AppStateService', [ '$rootScope', ($rootScope) => {
let stateService = {}
stateService.APP_STATE_LOGIN = 'login'
stateService.APP_STATE_DEFAULT = 'default'
stateService.APP_STATE_SETTINGS = 'settings'
stateService.APP_STATE_ABOUT = 'about'
stateService.set = (state) => {
stateService.previousState = typeof stateService.state !== 'undefined' ?
stateService.state : stateService.APP_STATE_DEFAULT
stateService.state = state
$rootScope.$broadcast('app:state:changed', stateService.state)
}
stateService.set(stateService.APP_STATE_DEFAULT)
return stateService
}])

View File

@ -1,54 +0,0 @@
let angApp = require(__dirname+'/../init')
angApp.factory('CredentialsServise', () => {
const keytar = require('keytar')
const settings = require('electron-settings')
let credentialsService = {}
credentialsService.getCredentials = () => {
let credentials = {}
credentials.login = settings.get('login')
let promise = new Promise((resolve, reject) => {
if (credentials.login) {
credentials.connectionManager = settings.get('connectionManager')
credentials.xmppService = credentials.login.split('@').pop()
let password = keytar.getPassword(credentials.xmppService, credentials.login)
password.then((result) => {
credentials.password = result
resolve(credentials)
})
}
else {
reject(Error('No login stored'))
}
})
return promise
}
credentialsService.addCredentials = (connectionManager, login, password) => {
let xmppService = login.split('@').pop()
settings.set('connectionManager', connectionManager)
settings.set('login', login)
keytar.setPassword(xmppService, login, password)
}
credentialsService.removeCredentials = (login) => {
let xmppService = login.split('@').pop()
passwordDelete = keytar.deletePassword(xmppService, login)
let promise = new Promise((resolve, reject) => {
passwordDelete.then((result) => {
settings.delete('login')
settings.delete('connectionManager')
resolve()
}, (error) => {
reject(error)
})
})
return promise
}
return credentialsService
})

View File

@ -1,96 +0,0 @@
let angApp = require(__dirname + '/../init')
const desktopPlugin = require(__dirname +'/../../libs/converse.js/converse-desktop/desktop-plugin')
angApp.factory('DesktopService', (
$window, $timeout, CredentialsServise, SystemService, AppStateService,
SettingsService, XmppHelperService
) => {
let desktopService = {}
desktopService._notifyMessage = () => {
SystemService.playAudio()
SystemService.showEnvelope()
}
desktopService._hideNotifyMessage = () => {
SystemService.hideEnvelope()
}
desktopService.logout = () => {
let credentials = CredentialsServise.getCredentials()
credentials.then((result) => {
let remove = CredentialsServise.removeCredentials(result.login)
console.log('Remove credentials on logout')
remove.then(() => {
AppStateService.set(AppStateService.APP_STATE_LOGIN)
})
})
}
desktopService.initConverse = (connectionManager, login, password) => {
AppStateService.set(AppStateService.APP_STATE_DEFAULT) // Always set to default state before init
desktopPlugin.register(login)
let lang = navigator.language
let allowBookmarks = SettingsService.get('allowBookmarks')
let omemoDefault = SettingsService.get('omemoDefault')
let xmppResource = XmppHelperService.getResourceFromJid(login)
if (!xmppResource) {
xmppResource = '.' + (Math.random().toString(36)+'00000000000000000').slice(2, 7) // Generate 5 char unique str
login = login + '/converseDesktop'+xmppResource
}
let conversejsParams = {
assets_path: './node_modules/converse.js/dist/',
allow_bookmarks: allowBookmarks,
auto_login: true,
auto_reconnect: true,
// debug: true,
i18n: lang,
jid: login,
omemo_default: omemoDefault,
password: password,
play_sounds: false,
priority: 50,
view_mode: 'embedded',
whitelisted_plugins: ['converseDesktop'],
}
if (connectionManager.startsWith('ws')) {
conversejsParams.websocket_url = connectionManager
} else {
conversejsParams.bosh_service_url = connectionManager
}
$timeout(() => {
converse.initialize(conversejsParams)
}, 50)
}
desktopService.getCredentialsAndLogin = () => {
let credentials = CredentialsServise.getCredentials()
credentials.then((result) => {
desktopService.initConverse(result.connectionManager, result.login, result.password)
}, (error) => {
AppStateService.set(AppStateService.APP_STATE_LOGIN)
})
}
desktopService.chatToOpen = null
$window.document.addEventListener('conversejs-logout', function (e) {
desktopService.logout()
})
$window.document.addEventListener('conversejs-unread', function (e) {
let sender = e.detail
desktopService.chatToOpen = sender
desktopService._notifyMessage()
})
$window.document.addEventListener('conversejs-no-unread', function (e) {
desktopService._hideNotifyMessage()
})
return desktopService
})

View File

@ -1,109 +0,0 @@
let angApp = require(__dirname+'/../init')
angApp.factory('SettingsService', () => {
let settingsService = {}
const electronSettings = require('electron-settings')
const settings = {
converseDesktop: {
runMinimized: {
default: false,
title: 'Run minimized',
hint: 'Default: false. Whether run Converse Desktop minimized to tray or not.'
},
minimizeOnClose: {
default: false,
title: 'Minimize on close',
hint: 'Default: false. Minimize or close Converse Desktop window.'
},
preserveWindowSize: {
default: true,
title: 'Preserve window size',
hint: 'Default: true, 800x600 otherwise.'
},
preserveWindowPosition: {
default: true,
title: 'Preserve window position',
hint: 'Default: true, screen center otherwise.'
}
},
conversejs: {
allowBookmarks: {
default: false,
title: 'Allow server bookmarks',
hint: 'Default: false. Enables/disables chatroom bookmarks functionality.'
},
omemoDefault: {
default: false,
title: 'Use OMEMO encryption by default',
hint: 'Default: false. Use OMEMO encryption by default when the chat supports it.'
}
}
}
const iterateSettings = (callback, settingsObj) => {
if(typeof settingsObj === "undefined") {
settingsObj = settings
}
angular.forEach(settingsObj, (value, key) => {
let settingsList = settingsObj[key]
angular.forEach(settingsList, (value, key) => {
let itemDefault = settingsList[key]['default']
callback(key, itemDefault, settingsList)
})
})
}
// Callback
// TODO: replace with promise?
const saveDefault = (key, value) => {
if (!electronSettings.has(key)) {
electronSettings.set(key, value)
}
}
// Callback
const save = (key, defaultValue, settingsList) => {
let value = settingsList[key]['value']
electronSettings.set(key, value)
}
// Callback
const loadAll = (key, defaultValue, settingsList) => {
if (!electronSettings.has(key)) {
settingsList[key]['value'] = defaultValue
}
settingsList[key]['value'] = electronSettings.get(key)
}
/**
* SettingsService
*/
settingsService.initDefaults = () => {
iterateSettings(saveDefault)
// Logout for versions with BOSH only
if (electronSettings.has('bosh')) {
electronSettings.delete('bosh')
electronSettings.delete('login')
}
}
settingsService.get = (key) => {
return electronSettings.get(key)
}
settingsService.loadAll = () => {
let currentSettings = angular.copy(settings)
iterateSettings(loadAll, currentSettings)
return currentSettings
}
settingsService.saveAll = (currentSettings) => {
iterateSettings(save, currentSettings)
}
return settingsService
})

View File

@ -1,28 +0,0 @@
let angApp = require(__dirname + '/../init')
angApp.factory('SystemService', () => {
const remote = require('electron').remote
let systemService = {}
systemService.playAudio = () => {
var audio = new Audio(__dirname + '/../../resources/sounds/graceful.ogg')
audio.play()
}
systemService.showEnvelope = () => {
remote.require('./main').trayService.showEnvelope()
}
systemService.hideEnvelope = () => {
remote.require('./main').trayService.hideEnvelope()
}
systemService.reloadWindow = () => {
remote.getCurrentWindow().reload()
}
return systemService
})

View File

@ -1,25 +0,0 @@
let angApp = require(__dirname + '/../init')
angApp.factory('XmppHelperService', [() => {
let xmppHelperService = {}
/**
* Use function copy from Strophe js lib because converse.js Strophe library
* is under private _api and unavailable before converse.js is initialized.
* This function is used _before_ converse.js is initialized.
*
* Get the resource portion of a JID String.
* @param {string} jid A JID.
* @return {string | null} A String containing the resource.
*/
xmppHelperService.getResourceFromJid = (jid) => {
if (!jid) { return null }
const s = jid.split("/")
if (s.length < 2) { return null }
s.splice(0, 1)
return s.join('/')
}
return xmppHelperService
}])

View File

@ -1,35 +0,0 @@
<div class="page-about" ng-controller="AboutController">
<div class="about-card">
<div class="about-card__content">
<div class="about-card__logo">
<img src="./resources/images/logo.png" srcset="./resources/images/logo@2x.png 2x" alt=""
class="chimeverse-branding__img" />
</div>
<div class="about-card__description">
<h3 class="about__title">About {{appInfo.APP_NAME}} {{appInfo.APP_VERSION}}</h3>
<div class="about__description">Jabber/XMPP client based on Converse.js and Electron</div>
<div>&nbsp;</div>
<div class="about__converse-version">
Version of <a href="https://conversejs.org/" target="_blank">converse.js</a> is 6.0.1
</div>
<div class="about__electron-version">
Version of <a href="https://www.electronjs.org/" target="_blank">electron</a> is 4.2.12
</div>
<div>&nbsp;</div>
<div class="about__thanks">
Thanks to
<a href="https://github.com/nick-denry/Chimeverse/graphs/contributors" target="_blank">all contributors</a>
</div>
<div>&nbsp;</div>
</div>
</div>
<div class="about__action">
<button class="about__close-button" ng-click="closeAbout()">OK</button>
</div>
<div>&nbsp;</div>
<div class="about__copyright">Copyright &copy; 2019-2020 Nick Denry</div>
</div>
</div>
<ng-include src="'./app/views/shared/_footer.html'"></ng-include>

View File

@ -1,24 +0,0 @@
<div class="page-default" ng-controller="DefaultController">
<div class="chimeverse-branding noselect">
<img src="./resources/images/logo.png" srcset="./resources/images/logo@2x.png 2x" alt=""
class="chimeverse-branding__img" />
<h3 class="chimeverse-branding__header">{{appInfo.APP_NAME}}</h3>
<div class="chimeverse-branding__version">
<span>{{appInfo.APP_VERSION}} </span>
<span class="chimeverse-branding__update">
<span class="update__checking" ng-show="checkingForUpdate == 'inProgress'">checking for update...</span>
<span class="update__error" ng-show="checkingForUpdate == 'checkErr'">
&gt;_&lt; check for update failed <a href="#" ng-click="checkRetry($event)">retry</a>
</span>
<span class="update__latest" ng-show="checkingForUpdate == 'latest'">latest version</span>
<a class="update__available" href="{{appInfo.APP_RELEASES_URL}}" target="_blank"
ng-show="checkingForUpdate == 'updateAvailable'">
Update available
</a>
</span>
</div>
</div>
<div class="conversejs-adoption">
<div id="conversejs"></div>
</div>
</div>

View File

@ -1,43 +0,0 @@
<div class="login-form">
<h3 class="login-form__title">Welcome to Converse Desktop</h3>
<div class="login-form__description">Jabber/XMPP client based on Converse.js and Electron</div>
<div class="login-form__card">
<form name="accountForm" ng-controller="LoginController">
<div class="login-form__wrapper">
<div class="login-form__input-group">
<span class="group-prepend oi" data-glyph="link-intact"></span>
<input ng-model="credentials.connectionManager" class="login-form__input login-form__input--with-append" type="text" placeholder="Connection manager url" required>
<span class="group-append--backgrounded oi" data-glyph="info" ng-click="showHelp('connectionManager')"></span>
</div>
<div class="login-form__input-help" ng-show="help.connectionManager == true"">
<div>
Should be a
<a href="https://m.conversejs.org/docs/html/setup.html#bosh-section" target="_blank">BOSH service</a>
or a
<a href="https://m.conversejs.org/docs/html/setup.html#websocket-section" target="_blank">Websocket</a>
URL
</div>
</div>
</div>
<div class="login-form__wrapper">
<div class="login-form__input-group">
<span class="group-prepend oi" data-glyph="person"></span>
<input ng-model="credentials.login" class="login-form__input" type="text" placeholder="jid@jabber.org" required>
</div>
</div>
<div class="login-form__wrapper">
<div class="login-form__input-group">
<span class="group-prepend oi" data-glyph="lock-locked"></span>
<input ng-model="credentials.password" class="login-form__input" type="password" placeholder="Password" required>
</div>
</div>
<div class="login-form__wrapper">
<button class="login-form__button" ng-click="addAccountAndLoginAction()" ng-disabled="accountForm.$invalid" disabled>Add account</button>
</div>
<div class="login-form__credentials-message">
Credentials stored via <a href="https://atom.github.io/node-keytar/" target="_blank">Keychain</a>
</div>
</form>
</div>
</div>
<ng-include src="'./app/views/shared/_footer.html'"></ng-include>

View File

@ -1,28 +0,0 @@
<div class="settings-page" ng-controller="SettingsController">
<form name="settignsForm">
<h3 class="settings-page__title">Chimeverse settings</h3>
<div class="form-item" ng-repeat="(key, item) in settings.converseDesktop">
<label>
<input type="checkbox" name="{{key}}" ng-model="item.value" /> {{item.title}}
</label>
<div class="form-item__hint">{{item.hint}}</div>
</div>
<h3 class="settings-page__title">Converse.js settings</h3>
<div class="form-item" ng-repeat="(key, item) in settings.conversejs">
<label>
<input type="checkbox" name="{{key}}" ng-model="item.value" /> {{item.title}}
</label>
<div class="form-item__hint">{{item.hint}}</div>
</div>
<div class="form-item" ng-show="settingsSaved">
<a href="#" ng-click="restartApp()" class="form-item__restart-app">
Click here to restart the app and apply your changes
</a>
</div>
<div class="form-actions">
<button class="form-item__save-button" ng-class="{'active': settingsChanged == true}" ng-click="saveSettings()">Save</button>
<button class="form-item__cancel-button" ng-click="closeSettings()">Cancel</button>
</div>
</form>
</div>
<ng-include src="'./app/views/shared/_footer.html'"></ng-include>

View File

@ -1,4 +0,0 @@
<div class="page__footer" ng-controller="FooterController">
<span class="footer__version">{{appInfo.APP_NAME}} {{appInfo.APP_VERSION}}.</span>
<a class="github-button" href="{{appInfo.APP_HOME}}" data-icon="octicon-star" aria-label="Star nick-denry/Chimeverse on GitHub">Star</a>
</div>

View File

@ -3,61 +3,19 @@
<head>
<meta charset="UTF-8">
<title>Converse Desktop</title>
<link rel="stylesheet" type="text/css" media="screen" href="node_modules/open-iconic/font/css/open-iconic.css">
<link rel="stylesheet" type="text/css" media="screen" href="node_modules/converse.js/dist/converse.min.css">
<link rel="stylesheet" type="text/css" href="resources/css/app.css">
<link rel="stylesheet" type="text/css" href="resources/css/_footer.css">
<link rel="stylesheet" type="text/css" href="resources/css/page-about.css">
<link rel="stylesheet" type="text/css" href="resources/css/page-default.css">
<link rel="stylesheet" type="text/css" href="resources/css/page-login.css">
<link rel="stylesheet" type="text/css" href="resources/css/page-settings.css">
</head>
<base href="./">
<body ng-app="app">
<div class="main-background"></div>
<div class="main-window" ng-controller="AppController" ng-cloak>
<div ng-show="state == 'default'">
<ng-include src="'./app/views/default/page.html'"></ng-include>
</div>
<div ng-show="state == 'login'">
<ng-include src="'./app/views/login/page.html'"></ng-include>
</div>
<div ng-show="state == 'about'">
<ng-include src="'./app/views/about/page.html'"></ng-include>
</div>
<div ng-show="state == 'settings'">
<ng-include src="'./app/views/settings/page.html'"></ng-include>
<base href="./index.html">
<body class="converse-fullscreen">
<div class="main-window">
<div class="page-default">
<div id="conversejs-bg"></div>
</div>
</div>
<!--
@see comment https://github.com/signalapp/libsignal-protocol-javascript/issues/6#issuecomment-247208665
-->
<script>
window.nodeRequire = require
delete window.require
</script>
<!-- Place libsignal at libs dir as it's no more distributed with converse.js -->
<script src="libs/converse.js/3rdparty/libsignal-protocol.js"></script>
<script>
window.require = window.nodeRequire
delete window.nodeRequire
</script>
<script>
// You can also require other files to run in this process
require('./node_modules/converse.js/dist/emojis.js')
require('./node_modules/converse.js/dist/converse.js')
require('./renderer.js')
</script>
<script>
function loadJs(url) {
var script = document.createElement('script')
script.src = url
script.setAttribute('async', 'true')
document.documentElement.firstChild.appendChild(script)
}
setTimeout(function () {
loadJs("./node_modules/github-buttons/dist/buttons.min.js")
}, 500)
</script>
<script src="./3rdparty/libsignal-protocol.js"></script>
<script src="./node_modules/converse.js/dist/converse.min.js"></script>
<script type="module" src="./setup.js"></script>
</body>
</html>
</html>

View File

@ -1,67 +0,0 @@
let desktopPlugin = {}
desktopPlugin.register = (login) => {
converse.plugins.add('converseDesktop', {
initialize: (event) => {
let _converse = event.properties._converse
let Strophe = converse.env.Strophe
/**
* Check if message stanza has some body payload
* @param {*} stanzaNodes
*/
let isBodyMessage = (stanzaNodes) => {
let result = false
Object.keys(stanzaNodes).some((key) => {
if (stanzaNodes[key].nodeName == 'body') {
result = true
}
})
return result
}
Promise.all([
_converse.api.waitUntil('rosterContactsFetched'),
_converse.api.waitUntil('chatBoxesFetched')
]).then(() => {
_converse.api.listen.on('logout', () => {
let event = new CustomEvent('conversejs-logout')
document.dispatchEvent(event)
})
_converse.api.listen.on('message', (data) => {
// Display notifications only for "payloaded" messages
if (isBodyMessage(data.stanza.childNodes)) {
let sender = data.stanza.attributes.from.nodeValue
let senderJid = Strophe.getBareJidFromJid(sender)
let loginJid = Strophe.getBareJidFromJid(login)
if (senderJid != loginJid) {
console.log(senderJid)
let event = new CustomEvent('conversejs-unread', {detail: senderJid})
document.dispatchEvent(event)
}
}
})
_converse.api.listen.on('chatBoxFocused', () => {
let event = new CustomEvent('conversejs-no-unread')
document.dispatchEvent(event)
//chimeverseService._hideNotifyMessage()
})
window.document.addEventListener('converse-force-logout', function (e) {
console.log('Get converse-force-logout event')
console.log('Logout form plugin')
_converse.api.user.logout()
//chimeverseService.logout()
})
window.document.addEventListener('conversejs-open-chat', function (e) {
let chatToOpen = e.detail
console.log('Get open-unread-chat event: '+chatToOpen)
if (chatToOpen !== null) {
_converse.api.chats.open(chatToOpen)
}
})
})
}
})
}
module.exports = desktopPlugin

146
main.js
View File

@ -1,19 +1,24 @@
// Modules to control application life and create native browser window
const { app, BrowserWindow, ipcMain, shell } = require('electron')
const path = require('path');
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow
// Require other app modules
const trayService = require(__dirname+'/modules/tray-service')
const menuService = require(__dirname+'/modules/menu-service')
const settingsService = require(__dirname+'/modules/settings-service')
const trayService = require(__dirname + '/modules/tray-service')
const menuService = require(__dirname + '/modules/menu-service')
const settingsService = require(__dirname + '/modules/settings-service')
const isMac = process.platform === 'darwin'
const isWin = process.platform === 'win32'
function initApp() {
function initApp () {
if (!app.requestSingleInstanceLock()) {
app.quit();
}
createWindow()
// Set Windows platform notifications
if (isWin) {
@ -23,88 +28,39 @@ function initApp() {
function createWindow () {
// Main window options
let mainWindowOptions = {
width: 800,
height: 600,
minWidth: 780,
minHeight: 560,
const mainWindowOptions = {
zoomToPageWidth: true,
show: false,
webPreferences: {
nodeIntegration: true
}
}
// Load app settings
let runMinimized = settingsService.get('runMinimized')
if (runMinimized) {
mainWindowOptions.show = !runMinimized
}
let preserveWindowSize = settingsService.get('preserveWindowSize')
if (preserveWindowSize) {
let width = settingsService.get('windowWidth')
let height = settingsService.get('windowHeight')
if (width) mainWindowOptions.width = width
if (height) mainWindowOptions.height = height
}
let preserveWindowPosition = settingsService.get('preserveWindowPosition')
if (preserveWindowPosition) {
let windowX = settingsService.get('windowX')
let windowY = settingsService.get('windowY')
if (windowX) mainWindowOptions.x = windowX
if (windowY) mainWindowOptions.y = windowY
preload: path.join(__dirname, 'preload.js')
},
icon: './resources/images/logo.png',
}
// Create the browser window.
mainWindow = new BrowserWindow(mainWindowOptions)
// and load the index.html of the app.
mainWindow.loadFile('index.html')
// Init tray
trayService.initTray(mainWindow)
// Init menu
menuService.createMenu()
menuService.createMenu(mainWindow)
// Open the DevTools.
// mainWindow.webContents.openDevTools()
// Before close
let minimizeOnClose = settingsService.get('minimizeOnClose')
if (minimizeOnClose) {
mainWindow.on('close', (e) => {
if (!app.isQuitting) {
e.preventDefault()
mainWindow.hide()
}
})
}
// Save window size
if (preserveWindowSize) {
mainWindow.on('resize', (e) => {
let newSize = mainWindow.getSize()
let width = newSize[0]
let height = newSize[1]
settingsService.set('windowWidth', width)
settingsService.set('windowHeight', height)
})
}
// Save window position
if (preserveWindowPosition !== 'undefined') {
mainWindow.on('move', (e) => {
let newPosition = mainWindow.getPosition()
let windowX = newPosition[0]
let windowY = newPosition[1]
settingsService.set('windowX', windowX)
settingsService.set('windowY', windowY)
})
}
mainWindow.on('close', (e) => {
if (!app.isQuitting && settingsService.get('minimizeOnClose')) {
e.preventDefault()
mainWindow.hide()
}
return false;
})
// Handle shutdown event on Mac with minimizeOnClose
// to prevent shutdown interrupt
if (isMac && minimizeOnClose) {
if (isMac) {
const { powerMonitor } = require('electron')
powerMonitor.on('shutdown', () => {
app.isQuitting = true
@ -113,10 +69,9 @@ function createWindow () {
}
// Handle restart
ipcMain.on('app-restart', (evt, arg) => {
ipcMain.on('app-quit', () => {
app.isQuitting = true
app.relaunch()
app.exit()
app.quit();
})
// Emitted when the window is closed.
@ -128,10 +83,31 @@ function createWindow () {
})
// Open links on system default browser
mainWindow.webContents.on('new-window', function(e, url) {
e.preventDefault()
shell.openExternal(url)
mainWindow.webContents.setWindowOpenHandler(function (details) {
shell.openExternal(details.url).catch((reason) => {
console.log(reason);
});
return { action: 'deny' };
})
ipcMain.handle('settings', (e, method, ...args) => {
return settingsService[method].apply(settingsService, args);
});
ipcMain.handle('trayService', (e, method, ...args) => {
return trayService[method].apply(trayService, args);
});
mainWindow.on('ready-to-show', () => {
mainWindow.maximize();
});
// and load the index.html of the app.
mainWindow.loadFile('index.html').catch((reason) => {
console.log(reason);
app.isQuitting = true;
app.quit();
});
}
// This method will be called when Electron has finished
@ -141,7 +117,7 @@ app.on('ready', initApp)
// Quit when all windows are closed.
app.on('window-all-closed', function () {
// On macOS it is common for applications and their menu bar
// On macOS, it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
// if (process.platform !== 'darwin')
// ^^^^ NOPE ;)
@ -150,19 +126,19 @@ app.on('window-all-closed', function () {
})
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) createWindow()
if (mainWindow === null) {
createWindow()
} else {
mainWindow.show();
}
})
app.on('second-instance', function () {
mainWindow.show();
});
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
// Allow to play audio automatically
// Allow audio to play automatically
app.commandLine.appendSwitch('autoplay-policy', 'no-user-gesture-required')
/**
* Export functions
*/
exports.trayService = trayService

View File

@ -1,59 +1,37 @@
/**
* Module for Menu functions.
*/
const { app, Menu, MenuItem } = require('electron')
const settingsService = require(__dirname + '/../modules/settings-service')
const {app, Menu, BrowserWindow} = require('electron')
let menuService = {}
const menuService = {}
menuService.createMenu = () => {
const isMac = process.platform === 'darwin'
const about = {
label: 'About Converse Desktop',
click: () => {
// @see https://github.com/electron/electron/issues/16558#issuecomment-484460276
// let activeWindow = BrowserWindow.getFocusedWindow()
let activeWindow = BrowserWindow.getAllWindows()[0]
activeWindow.show()
activeWindow.webContents.send('about-page-event')
}
}
const application = {
label: 'Converse Desktop',
submenu: [
... isMac ? [about] : [],
menuService.createMenu = (window) => {
let converse;
const application = new Menu();
application.append(new MenuItem({
label: 'Converse Desktop'
, submenu: converse = Menu.buildFromTemplate([
{
label: 'Reconnect',
accelerator: 'CmdOrCtrl+R',
click: () => {
let activeWindow = BrowserWindow.getAllWindows()[0]
activeWindow.show()
activeWindow.reload()
window.show()
window.loadFile('index.html').catch((reason) => {
console.log(reason);
app.isQuitting = true;
app.quit();
});
}
},
{
label: 'Force logout',
accelerator: 'CmdOrCtrl+D',
label: 'Minimize on close',
type: 'checkbox',
id: 'minimize-on-close',
checked: settingsService.get('minimizeOnClose'),
click: () => {
let activeWindow = BrowserWindow.getAllWindows()[0]
activeWindow.show()
activeWindow.webContents.send('force-logout-event')
}
},
{
type: 'separator',
},
{
label: 'Preferences',
accelerator: 'CmdOrCtrl+,',
click: () => {
let activeWindow = BrowserWindow.getAllWindows()[0]
activeWindow.show()
activeWindow.webContents.send('preferences-event')
settingsService.set('minimizeOnClose', converse.getMenuItemById('minimize-on-close').checked);
}
},
{
@ -67,12 +45,11 @@ menuService.createMenu = () => {
app.quit()
},
},
],
}
const edit = {
label: 'Edit',
submenu: [
])
}));
application.append(new MenuItem({
label: 'Edit'
, submenu: Menu.buildFromTemplate([
{
label: 'Undo',
accelerator: 'CmdOrCtrl+Z',
@ -104,27 +81,22 @@ menuService.createMenu = () => {
accelerator: 'CmdOrCtrl+A',
role: 'selectAll',
},
],
}
const help = {
label: 'Help',
submenu: [
... !isMac ? [about] : [],
])
}));
application.append(new MenuItem({
label: 'Help'
, submenu: Menu.buildFromTemplate([
{
label: 'Debug info',
accelerator: 'F12',
click: () => {
let activeWindow = BrowserWindow.getAllWindows()[0]
activeWindow.webContents.openDevTools()
window.webContents.openDevTools()
}
}
]
}
])
}));
const template = [application, edit, help]
Menu.setApplicationMenu(Menu.buildFromTemplate(template))
Menu.setApplicationMenu(application);
}
module.exports = menuService

View File

@ -1,5 +1,5 @@
/**
* Module for getting settigns in Main process.
* Module for getting settings in Main process.
*/
const electronSettings = require('electron-settings')
@ -7,7 +7,7 @@ const electronSettings = require('electron-settings')
let settingsService = {}
settingsService.get = (itemKey) => {
settingValue = electronSettings.get(itemKey)
const settingValue = electronSettings.getSync(itemKey)
if (typeof settingValue === 'undefined' || settingValue === null) {
return false
}
@ -15,7 +15,11 @@ settingsService.get = (itemKey) => {
}
settingsService.set = (itemKey, settingValue) => {
electronSettings.set(itemKey, settingValue)
electronSettings.setSync(itemKey, settingValue)
}
module.exports = settingsService
settingsService.has = (itemKey) => electronSettings.hasSync(itemKey);
settingsService.unset = (itemKey) => electronSettings.unsetSync(itemKey);
module.exports = settingsService

View File

@ -2,50 +2,42 @@
* Module for Tray functions.
*/
const { BrowserWindow, Tray } = require('electron')
const { Tray } = require('electron')
const path = require('path')
let trayServiceWindow = null
let tray = null
let trayService = {}
const trayService = {}
let getTrayServiceIcon = (iconName = 'icon') => {
let iconImage = ''
if (process.platform === 'darwin') {
iconImage = iconName+'Template'
const getTrayServiceIcon = (iconName = 'icon') => {
let iconImage;
if (process.platform === 'darwin' || process.platform === 'win32') {
iconImage = iconName + '-16x16'
} else {
iconImage = iconName + '-48x48'
}
else if (process.platform === 'win32') {
iconImage = iconName+'-16x16'
}
else {
iconImage = iconName+'-48x48'
}
return path.join(__dirname, '/../resources/images/' + iconImage + '.png')
return path.join(__dirname, '..', 'resources', 'images', iconImage + '.png')
}
trayService.initTray = (window) => {
trayServiceWindow = window
let iconPath = getTrayServiceIcon()
const iconPath = getTrayServiceIcon()
tray = new Tray(iconPath)
tray.setToolTip('Converse Desktop')
tray.on('click', function() {
// Sent open-related-chat event only on click
let activeWindow = BrowserWindow.getAllWindows()[0]
activeWindow.webContents.send('open-unread-chat')
tray.on('click', function () {
window.webContents.send('open-unread-chat')
trayService.hideEnvelope()
trayServiceWindow.show()
window.show()
})
}
trayService.showEnvelope = () => {
let iconPath = getTrayServiceIcon('envelope')
const iconPath = getTrayServiceIcon('envelope')
tray.setImage(iconPath)
}
trayService.hideEnvelope = () => {
let iconPath = getTrayServiceIcon()
const iconPath = getTrayServiceIcon()
tray.setImage(iconPath)
}

8862
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,18 @@
{
"name": "@converse/desktop",
"version": "0.1.0",
"version": "9.1.0",
"description": "Desktop Jabber/XMPP client based on Converse.js and Electron",
"main": "main.js",
"scripts": {
"clean": "rm -rf node_modules",
"postinstall": "electron-builder install-app-deps",
"start": "electron .",
"pack": "build --dir",
"dist": "build",
"dist:win64": "build --platform win --arch x64",
"dist:linux64deb": "build --platform linux --arch x64"
"pack": "electron-builder --dir",
"dist": "electron-builder --mac",
"dist:win64": "electron-builder --win --x64",
"dist:linux64": "electron-builder --linux --x64"
},
"author": "The Converse Desktop Developers",
"repository": "https://github.com/conversejs/converse-desktop",
"keywords": [
"Jabber",
@ -20,22 +22,17 @@
"Electron",
"OMEMO"
],
"author": "Nick Denry <nick@denry.ru>",
"license": "MPL-2.0",
"devDependencies": {
"electron": "^4.2.12",
"electron-builder": "^20.44.4",
"electron-packager": "^13.1.1",
"electron-rebuild": "^1.10.1"
"electron": "^18.0.0",
"electron-builder": "^22.14.0",
"electron-packager": "^15.2.0",
"eslint": "^8.12.0"
},
"dependencies": {
"angular": "^1.7.9",
"converse.js": "^6.0.0",
"electron-settings": "^3.2.0",
"github-buttons": "^2.8.0",
"keytar": "^4.13.0",
"n": "^6.5.1",
"open-iconic": "^1.1.1"
"converse.js": "^9.1.0",
"electron-settings": "^4.0.2",
"keytar": "^7.9.0"
},
"build": {
"appId": "com.denry.converse-desktop",
@ -60,13 +57,18 @@
]
},
"linux": {
"maintainer": "Keith Maika <keithm@aoeex.com>",
"artifactName": "converse_desktop-${version}_${arch}.${ext}",
"icon": "resources/images/logo.png",
"target": [
"deb"
"deb",
"tar.gz",
"appImage"
]
},
"win": {
"target": "nsis",
"icon": "resources/images/logo.ico"
"icon": "resources/images/logo.png"
}
}
}

43
preload.js Normal file
View File

@ -0,0 +1,43 @@
const { ipcRenderer, contextBridge } = require('electron');
const keytar = require('keytar');
contextBridge.exposeInMainWorld('api', {
settings: {
has (setting) {
return ipcRenderer.invoke('settings', 'has', setting);
},
set (setting, value) {
return ipcRenderer.invoke('settings', 'set', setting, value);
},
unset (setting) {
return ipcRenderer.invoke('settings', 'unset', setting);
},
get (setting) {
return ipcRenderer.invoke('settings', 'get', setting);
}
},
trayService: {
showEnvelope () {
return ipcRenderer.invoke('trayService', 'showEnvelope');
},
hideEnvelope () {
return ipcRenderer.invoke('trayService', 'hideEnvelope');
}
},
keytar: {
getPassword (service, login) {
return keytar.getPassword(service, login);
},
setPassword (service, login, password) {
return keytar.setPassword(service, login, password);
},
deletePassword (service, login) {
return keytar.deletePassword(service, login);
}
},
app: {
quit () {
ipcRenderer.send('app-quit');
}
}
});

View File

@ -1,64 +0,0 @@
// This file is required by the index.html file and will
// be executed in the renderer process for that window.
// All of the Node.js APIs are available in this process.
var angApp = require('./app/init')
require('./app/services/credentials-service')
require('./app/services/settings-service')
require('./app/services/system-service')
require('./app/services/app-state-service')
require('./app/services/xmpp-helper-service')
require('./app/services/desktop-service')
require('./app/controllers/settings-controller')
require('./app/controllers/login-controller')
require('./app/controllers/default-controller')
require('./app/controllers/about-controller')
require('./app/controllers/footer-controller')
angApp.controller('AppController', function ($scope, $timeout, DesktopService, SettingsService, AppStateService) {
const { ipcRenderer } = require('electron')
// Menu force logout event
ipcRenderer.on('force-logout-event', () => {
DesktopService.logout()
let event = new CustomEvent("converse-force-logout") // Dispatch to the plugin
document.dispatchEvent(event)
})
// Menu settings event
ipcRenderer.on('preferences-event', () => {
AppStateService.set(AppStateService.APP_STATE_SETTINGS)
})
// Menu about event
ipcRenderer.on('about-page-event', () => {
AppStateService.set(AppStateService.APP_STATE_ABOUT)
})
// Menu about event
ipcRenderer.on('open-unread-chat', () => {
let event = new CustomEvent('conversejs-open-chat', {detail: DesktopService.chatToOpen})
document.dispatchEvent(event)
})
AppStateService.set(AppStateService.APP_STATE_DEFAULT)
$scope.$on('app:state:changed', (event, data) => {
// @see https://docs.angularjs.org/error/$rootScope/inprog
$timeout(() => {
$scope.state = data
console.log('Switch to the "' + $scope.state +'" state')
}, 0)
})
$scope.$on('app:restart', (event, data) => {
ipcRenderer.send('app-restart')
})
SettingsService.initDefaults()
DesktopService.getCredentialsAndLogin()
})

View File

@ -1,15 +0,0 @@
.page__footer {
bottom: 20px;
color: #777;
display: flex;
left: 50%;
position: absolute;
text-align: center;
transform: translate(-50%, 0%);
}
.footer__version {
font-size: 13px;
margin-right: 30px;
}

View File

@ -1,7 +1,3 @@
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
display: none !important;
}
.noselect {
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Safari */
@ -20,22 +16,12 @@ a {
color: #777;
}
.main-background {
background: #f8f8f8;
height: 100%;
left: 0px;
position: fixed;
top: 0px;
width: 100%;
z-index: -1;
}
.conversejs-adoption {
background: transparent;
pointer-events: none;
position: fixed;
left: 0px;
top: 0px;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
@ -46,4 +32,13 @@ a {
#conversejs .converse-chatboxes > * {
pointer-events: auto;
}
}
.chat-body {
overflow: hidden;
}
#conversejs.converse-embedded .chatbox {
flex-grow: 1 !important;
flex-shrink: 1 !important;
}

View File

@ -1,39 +0,0 @@
.page-about {
color: #777;
left: 50%;
position: absolute;
top: 47%;
transform: translate(-50%, -50%);
}
.about-card {
background: #fff;
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1);
margin-top: 30px;
min-width: 400px;
padding: 30px;
}
.about-card__content {
display: flex;
}
.about__action {
text-align: center;
}
.about__close-button {
border: 0px solid #ddd;
background: rgb(5,93,228);
background: linear-gradient(0deg, rgba(5,93,228,1) 0%, rgba(76,145,255,1) 100%);
color: #fff;
cursor: pointer;
display: inline-block;
font-size: 18px;
outline: none;
padding: 10px 30px;
}
.about__copyright {
text-align: center;
}

View File

@ -1,44 +0,0 @@
.chimeverse-branding {
left: 50%;
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
text-align: center;
}
.chimeverse-branding__img {
margin-left: 20px;
}
.chimeverse-branding__header {
font-size: 26px;
font-weight: normal;
color: #999;
margin: 0px;
}
.chimeverse-branding__version {
font-size: 12px;
color: #999;
margin-top: 10px;
}
.update__latest {
background: #bbb;
color: #fff;
border-radius: 3px;
padding: 3px 5px;
}
.update__available {
background: rgb(5,93,228);
background: linear-gradient(0deg, rgba(5,93,228,1) 0%, rgba(76,145,255,1) 100%);
padding: 3px 5px;
color: #fff;
text-decoration: none;
border-radius: 3px;
}
.update__error {
color: #da0000;
}

View File

@ -1,100 +0,0 @@
.login-form {
color: #777;
left: 50%;
position: absolute;
top: 47%;
transform: translate(-50%, -50%);
}
.login-form__title {
font-size: 28px;
text-align: center;
font-weight: normal;
margin: 0px 0px 15px 0px;
}
.login-form__description {
text-align: center;
}
.login-form__card {
background: #fff;
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1);
margin-top: 30px;
padding: 30px;
}
.login-form__wrapper {
margin-bottom: 15px;;
}
.login-form__input-group {
border: 1px solid #ddd;
border-radius: 0px;
padding: 0px 0px 0px 30px;
position: relative;
}
.login-form__input-group:active,
.login-form__input-group:focus,
.login-form__input-group:hover {
border: 1px solid #aaa;
}
.login-form__input-group .group-prepend {
position: absolute;
left: 15px;
top: 12px;
}
.login-form__input-group .group-append--backgrounded {
background: #f2f2f2;
cursor: pointer;
padding: 12px;
position: absolute;
right: 0px;
top: 0 px;
}
.login-form__input {
border: 0px;
color: #222;
font-size: 18px;
outline: none;
padding: 10px;
width: 290px;
}
.login-form__input::placeholder { /* Most modern browsers support this now. */
color: #ccc;
}
.login-form__input--with-append {
width: 260px;
}
.login-form__input-help {
font-size: 13px;
margin: 4px;
}
.login-form__button {
border: 1px solid #ddd;
background: rgb(5,93,228);
background: linear-gradient(0deg, rgba(5,93,228,1) 0%, rgba(76,145,255,1) 100%);
color: #fff;
display: inline-block;
font-size: 18px;
outline: none;
padding: 10px 30px;
width: 100%;
}
.login-form__button:disabled {
background: #eee;
}
.login-form__credentials-message {
font-size: 14px;
text-align: center;
}

View File

@ -1,16 +0,0 @@
.settings-page {
padding: 0px 20px 0px 20px;
}
.form-item {
margin-bottom: 15px;
}
.form-item__hint {
color: #777;
font-size: 13px;
}
.form-item__save-button.active {
color: blue;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 488 B

After

Width:  |  Height:  |  Size: 438 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 362 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 719 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 701 B

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 399 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 895 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 17 KiB

45
setup.js Normal file
View File

@ -0,0 +1,45 @@
/* global api */
await import('./app/converse-plugins/desktop-credentials.js')
await import('./app/converse-plugins/desktop-trayicon.js')
const getCredentials = (await import('./app/credentials.js')).getCredentials;
let websocket_url, bosh_service_url;
const { connectionManager, login, password } = await getCredentials()
if (connectionManager?.startsWith('ws')) {
websocket_url = connectionManager
} else if (connectionManager?.startsWith('http')) {
bosh_service_url = connectionManager
}
converse.plugins.add('converse-debug', {
initialize () {
const { _converse } = this;
window._converse = _converse;
}
});
converse.initialize({
assets_path: './node_modules/converse.js/dist/',
auto_login: login && password,
bosh_service_url,
i18n: navigator.language,
jid: login,
loglevel: 'debug',
muc_respect_autojoin: true,
muc_show_logs_before_join: true,
password: password,
play_sounds: false,
priority: 50,
prune_messages_above: 250,
theme: 'concord',
view_mode: 'fullscreen',
websocket_url,
whitelisted_plugins: ['converse-debug', 'converse-desktop-credentials', 'converse-desktop-trayicon'],
show_connection_url_input: true
}).catch((reason) => {
console.log(reason);
api.app.quit();
});