From ea71fced95a6923926c92ada523840ebdbd0ef64 Mon Sep 17 00:00:00 2001 From: Stefan Malzner Date: Mon, 8 Apr 2019 15:18:58 +0200 Subject: Replace invoices & subscription info with links to website --- src/environment.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'src/environment.js') diff --git a/src/environment.js b/src/environment.js index 73b1c7ab2..68fa45173 100644 --- a/src/environment.js +++ b/src/environment.js @@ -1,6 +1,13 @@ import isDev from 'electron-is-dev'; -import { LIVE_API, DEV_API, LOCAL_API } from './config'; +import { + LIVE_API, + DEV_API, + LOCAL_API, + LOCAL_API_WEBSITE, + DEV_API_WEBSITE, + LIVE_API_WEBSITE, +} from './config'; export const isDevMode = isDev; export const useLiveAPI = process.env.LIVE_API; @@ -19,12 +26,17 @@ export const ctrlKey = isMac ? '⌘' : 'Ctrl'; export const cmdKey = isMac ? 'Cmd' : 'Ctrl'; let api; +let web; if (!isDevMode || (isDevMode && useLiveAPI)) { api = LIVE_API; + web = LIVE_API_WEBSITE; } else if (isDevMode && useLocalAPI) { api = LOCAL_API; + web = LOCAL_API_WEBSITE; } else { api = DEV_API; + web = DEV_API_WEBSITE; } export const API = api; +export const WEBSITE = web; -- cgit v1.2.3-70-g09d2 From 25fc276d5e3f754f915500e91229b8607febc478 Mon Sep 17 00:00:00 2001 From: Stefan Malzner Date: Tue, 9 Apr 2019 01:20:35 +0200 Subject: Add settings websocket --- package-lock.json | 43 +++++++++++---- package.json | 3 +- src/config.js | 4 ++ src/environment.js | 8 +++ src/features/settingsWS/actions.js | 10 ++++ src/features/settingsWS/index.js | 35 ++++++++++++ src/features/settingsWS/state.js | 13 +++++ src/features/settingsWS/store.js | 107 +++++++++++++++++++++++++++++++++++++ src/stores/FeaturesStore.js | 2 + 9 files changed, 214 insertions(+), 11 deletions(-) create mode 100755 src/features/settingsWS/actions.js create mode 100755 src/features/settingsWS/index.js create mode 100755 src/features/settingsWS/state.js create mode 100755 src/features/settingsWS/store.js (limited to 'src/environment.js') diff --git a/package-lock.json b/package-lock.json index e9b2cdcf2..34fd0875c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1945,13 +1945,13 @@ "requires": { "@mdi/js": "^3.3.92", "@mdi/react": "^1.1.0", - "@meetfranz/theme": "^1.0.7", + "@meetfranz/theme": "^1.0.13", "react-html-attributes": "^1.4.3", "react-loader": "^2.4.5" }, "dependencies": { "@meetfranz/theme": { - "version": "1.0.9", + "version": "1.0.13", "bundled": true, "requires": { "color": "^3.1.0" @@ -1970,12 +1970,12 @@ "requires": { "@mdi/js": "^3.3.92", "@mdi/react": "^1.1.0", - "@meetfranz/theme": "^1.0.7", + "@meetfranz/theme": "^1.0.13", "react-loader": "^2.4.5" }, "dependencies": { "@meetfranz/theme": { - "version": "1.0.9", + "version": "1.0.13", "bundled": true, "requires": { "color": "^3.1.0" @@ -2994,6 +2994,11 @@ "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=", "dev": true }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + }, "async-settle": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", @@ -6543,6 +6548,16 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", "dev": true + }, + "ws": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", + "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", + "dev": true, + "requires": { + "options": ">=0.0.5", + "ultron": "1.0.x" + } } } }, @@ -6580,6 +6595,16 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", "dev": true + }, + "ws": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", + "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", + "dev": true, + "requires": { + "options": ">=0.0.5", + "ultron": "1.0.x" + } } } }, @@ -19292,13 +19317,11 @@ } }, "ws": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", - "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", - "dev": true, + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", + "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", "requires": { - "options": ">=0.0.5", - "ultron": "1.0.x" + "async-limiter": "~1.0.0" } }, "wtf-8": { diff --git a/package.json b/package.json index 8f5c2e8c8..481e44af1 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,8 @@ "semver": "^5.4.1", "smoothscroll-polyfill": "^0.3.4", "tar": "^4.0.2", - "uuid": "^3.0.1" + "uuid": "^3.0.1", + "ws": "6.2.1" }, "devDependencies": { "@adlk/misty": "^0.1.1", diff --git a/src/config.js b/src/config.js index 4423e61e9..05ee07ee5 100644 --- a/src/config.js +++ b/src/config.js @@ -13,6 +13,10 @@ export const LOCAL_API = 'http://localhost:3000'; export const DEV_API = 'https://dev.franzinfra.com'; export const LIVE_API = 'https://api.franzinfra.com'; +export const LOCAL_WS_API = 'ws://localhost:3000'; +export const DEV_WS_API = 'wss://dev.franzinfra.com'; +export const LIVE_WS_API = 'wss://api.franzinfra.com'; + export const LOCAL_API_WEBSITE = 'http://localhost:3333'; export const DEV_API_WEBSITE = 'https://meetfranz.com'; export const LIVE_API_WEBSITE = 'https://meetfranz.com'; diff --git a/src/environment.js b/src/environment.js index 68fa45173..e47b373b0 100644 --- a/src/environment.js +++ b/src/environment.js @@ -7,6 +7,9 @@ import { LOCAL_API_WEBSITE, DEV_API_WEBSITE, LIVE_API_WEBSITE, + LIVE_WS_API, + LOCAL_WS_API, + DEV_WS_API, } from './config'; export const isDevMode = isDev; @@ -26,17 +29,22 @@ export const ctrlKey = isMac ? '⌘' : 'Ctrl'; export const cmdKey = isMac ? 'Cmd' : 'Ctrl'; let api; +let wsApi; let web; if (!isDevMode || (isDevMode && useLiveAPI)) { api = LIVE_API; + wsApi = LIVE_WS_API; web = LIVE_API_WEBSITE; } else if (isDevMode && useLocalAPI) { api = LOCAL_API; + wsApi = LOCAL_WS_API; web = LOCAL_API_WEBSITE; } else { api = DEV_API; + wsApi = DEV_WS_API; web = DEV_API_WEBSITE; } export const API = api; +export const WS_API = wsApi; export const WEBSITE = web; diff --git a/src/features/settingsWS/actions.js b/src/features/settingsWS/actions.js new file mode 100755 index 000000000..631670c8a --- /dev/null +++ b/src/features/settingsWS/actions.js @@ -0,0 +1,10 @@ +import PropTypes from 'prop-types'; +import { createActionsFromDefinitions } from '../../actions/lib/actions'; + +export const settingsWSActions = createActionsFromDefinitions({ + greet: { + name: PropTypes.string.isRequired, + }, +}, PropTypes.checkPropTypes); + +export default settingsWSActions; diff --git a/src/features/settingsWS/index.js b/src/features/settingsWS/index.js new file mode 100755 index 000000000..1e268f184 --- /dev/null +++ b/src/features/settingsWS/index.js @@ -0,0 +1,35 @@ +import { reaction, runInAction } from 'mobx'; +import { SettingsWSStore } from './store'; +import state, { resetState } from './state'; + +const debug = require('debug')('Franz:feature:settingsWS'); + +let store = null; + +export default function initAnnouncements(stores, actions) { + const { features } = stores; + + // Toggle workspace feature + reaction( + () => ( + features.features.isSettingsWSEnabled + ), + (isEnabled) => { + if (isEnabled) { + debug('Initializing `settingsWS` feature'); + store = new SettingsWSStore(stores, null, actions, state); + store.initialize(); + runInAction(() => { state.isFeatureActive = true; }); + } else if (store) { + debug('Disabling `settingsWS` feature'); + runInAction(() => { state.isFeatureActive = false; }); + store.teardown(); + store = null; + resetState(); // Reset state to default + } + }, + { + fireImmediately: true, + }, + ); +} diff --git a/src/features/settingsWS/state.js b/src/features/settingsWS/state.js new file mode 100755 index 000000000..7b16b2b6e --- /dev/null +++ b/src/features/settingsWS/state.js @@ -0,0 +1,13 @@ +import { observable } from 'mobx'; + +const defaultState = { + isFeatureActive: false, +}; + +export const settingsWSState = observable(defaultState); + +export function resetState() { + Object.assign(settingsWSState, defaultState); +} + +export default settingsWSState; diff --git a/src/features/settingsWS/store.js b/src/features/settingsWS/store.js new file mode 100755 index 000000000..21fd3440e --- /dev/null +++ b/src/features/settingsWS/store.js @@ -0,0 +1,107 @@ +import { observable, reaction } from 'mobx'; +import WebSocket from 'ws'; +import ms from 'ms'; + +import Store from '../../stores/lib/Store'; +import { WS_API } from '../../environment'; + +const debug = require('debug')('Franz:feature:settingsWS:store'); + +export class SettingsWSStore extends Store { + ws = null; + + @observable connected = false; + + pingTimeout = null; + + reconnectTimeout = null; + + constructor(stores, api, actions, state) { + super(stores, api, actions); + this.state = state; + } + + setup() { + this.connect(); + + reaction(() => !this.connected, this.reconnect.bind(this)); + } + + connect() { + try { + const wsURL = `${WS_API}/ws/${this.stores.user.data.id}`; + debug('Setting up WebSocket to', wsURL); + + this.ws = new WebSocket(wsURL); + + this.ws.on('open', () => { + debug('Opened WebSocket'); + this.send({ + action: 'authorize', + token: this.stores.user.authToken, + }); + + this.connected = true; + + this.heartbeat(); + }); + + this.ws.on('message', (data) => { + const resp = JSON.parse(data); + debug('Received message', resp); + + if (resp.id) { + this.stores.user.getUserInfoRequest.patch((result) => { + if (!result) return; + + debug('Patching user object with new values'); + Object.assign(result, resp); + }); + } + }); + + this.ws.on('ping', this.heartbeat.bind(this)); + } catch (err) { + console.err(err); + } + } + + heartbeat() { + debug('Heartbeat'); + clearTimeout(this.pingTimeout); + + // Use `WebSocket#terminate()` and not `WebSocket#close()`. Delay should be + // equal to the interval at which your server sends out pings plus a + // conservative assumption of the latency. + this.pingTimeout = setTimeout(() => { + debug('Terminating connection reconnecting in 35'); + this.ws.terminate(); + + this.connected = false; + }, ms('35s')); + } + + send(data) { + if (this.ws) { + this.ws.send(JSON.stringify(data)); + debug('Sending data', data); + } else { + debug('WebSocket is not initialized'); + } + } + + reconnect() { + if (!this.connected) { + debug('Trying to reconnect in 30s'); + this.reconnectTimeout = setInterval(() => { + debug('Trying to reconnect'); + this.connect(); + }, ms('30s')); + } else { + debug('Clearing reconnect interval'); + clearInterval(this.reconnectTimeout); + } + } +} + +export default SettingsWSStore; diff --git a/src/stores/FeaturesStore.js b/src/stores/FeaturesStore.js index d2842083c..e60d79075 100644 --- a/src/stores/FeaturesStore.js +++ b/src/stores/FeaturesStore.js @@ -8,6 +8,7 @@ import spellchecker from '../features/spellchecker'; import serviceProxy from '../features/serviceProxy'; import basicAuth from '../features/basicAuth'; import shareFranz from '../features/shareFranz'; +import settingsWS from '../features/settingsWS'; import { DEFAULT_FEATURES_CONFIG } from '../config'; @@ -58,5 +59,6 @@ export default class FeaturesStore extends Store { serviceProxy(this.stores, this.actions); basicAuth(this.stores, this.actions); shareFranz(this.stores, this.actions); + settingsWS(this.stores, this.actions); } } -- cgit v1.2.3-70-g09d2