diff options
author | Stefan Malzner <stefan@adlk.io> | 2019-04-09 01:20:35 +0200 |
---|---|---|
committer | Stefan Malzner <stefan@adlk.io> | 2019-04-09 01:20:35 +0200 |
commit | 25fc276d5e3f754f915500e91229b8607febc478 (patch) | |
tree | 1875b74611d2cc7be8c6b040dae55ede89e51356 | |
parent | update strings (diff) | |
download | ferdium-app-25fc276d5e3f754f915500e91229b8607febc478.tar.gz ferdium-app-25fc276d5e3f754f915500e91229b8607febc478.tar.zst ferdium-app-25fc276d5e3f754f915500e91229b8607febc478.zip |
Add settings websocket
-rw-r--r-- | package-lock.json | 43 | ||||
-rw-r--r-- | package.json | 3 | ||||
-rw-r--r-- | src/config.js | 4 | ||||
-rw-r--r-- | src/environment.js | 8 | ||||
-rwxr-xr-x | src/features/settingsWS/actions.js | 10 | ||||
-rwxr-xr-x | src/features/settingsWS/index.js | 35 | ||||
-rwxr-xr-x | src/features/settingsWS/state.js | 13 | ||||
-rwxr-xr-x | src/features/settingsWS/store.js | 107 | ||||
-rw-r--r-- | src/stores/FeaturesStore.js | 2 |
9 files changed, 214 insertions, 11 deletions
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 @@ | |||
1945 | "requires": { | 1945 | "requires": { |
1946 | "@mdi/js": "^3.3.92", | 1946 | "@mdi/js": "^3.3.92", |
1947 | "@mdi/react": "^1.1.0", | 1947 | "@mdi/react": "^1.1.0", |
1948 | "@meetfranz/theme": "^1.0.7", | 1948 | "@meetfranz/theme": "^1.0.13", |
1949 | "react-html-attributes": "^1.4.3", | 1949 | "react-html-attributes": "^1.4.3", |
1950 | "react-loader": "^2.4.5" | 1950 | "react-loader": "^2.4.5" |
1951 | }, | 1951 | }, |
1952 | "dependencies": { | 1952 | "dependencies": { |
1953 | "@meetfranz/theme": { | 1953 | "@meetfranz/theme": { |
1954 | "version": "1.0.9", | 1954 | "version": "1.0.13", |
1955 | "bundled": true, | 1955 | "bundled": true, |
1956 | "requires": { | 1956 | "requires": { |
1957 | "color": "^3.1.0" | 1957 | "color": "^3.1.0" |
@@ -1970,12 +1970,12 @@ | |||
1970 | "requires": { | 1970 | "requires": { |
1971 | "@mdi/js": "^3.3.92", | 1971 | "@mdi/js": "^3.3.92", |
1972 | "@mdi/react": "^1.1.0", | 1972 | "@mdi/react": "^1.1.0", |
1973 | "@meetfranz/theme": "^1.0.7", | 1973 | "@meetfranz/theme": "^1.0.13", |
1974 | "react-loader": "^2.4.5" | 1974 | "react-loader": "^2.4.5" |
1975 | }, | 1975 | }, |
1976 | "dependencies": { | 1976 | "dependencies": { |
1977 | "@meetfranz/theme": { | 1977 | "@meetfranz/theme": { |
1978 | "version": "1.0.9", | 1978 | "version": "1.0.13", |
1979 | "bundled": true, | 1979 | "bundled": true, |
1980 | "requires": { | 1980 | "requires": { |
1981 | "color": "^3.1.0" | 1981 | "color": "^3.1.0" |
@@ -2994,6 +2994,11 @@ | |||
2994 | "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=", | 2994 | "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=", |
2995 | "dev": true | 2995 | "dev": true |
2996 | }, | 2996 | }, |
2997 | "async-limiter": { | ||
2998 | "version": "1.0.0", | ||
2999 | "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", | ||
3000 | "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" | ||
3001 | }, | ||
2997 | "async-settle": { | 3002 | "async-settle": { |
2998 | "version": "1.0.0", | 3003 | "version": "1.0.0", |
2999 | "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", | 3004 | "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", |
@@ -6543,6 +6548,16 @@ | |||
6543 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", | 6548 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", |
6544 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", | 6549 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", |
6545 | "dev": true | 6550 | "dev": true |
6551 | }, | ||
6552 | "ws": { | ||
6553 | "version": "1.1.5", | ||
6554 | "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", | ||
6555 | "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", | ||
6556 | "dev": true, | ||
6557 | "requires": { | ||
6558 | "options": ">=0.0.5", | ||
6559 | "ultron": "1.0.x" | ||
6560 | } | ||
6546 | } | 6561 | } |
6547 | } | 6562 | } |
6548 | }, | 6563 | }, |
@@ -6580,6 +6595,16 @@ | |||
6580 | "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", | 6595 | "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", |
6581 | "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", | 6596 | "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", |
6582 | "dev": true | 6597 | "dev": true |
6598 | }, | ||
6599 | "ws": { | ||
6600 | "version": "1.1.5", | ||
6601 | "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", | ||
6602 | "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", | ||
6603 | "dev": true, | ||
6604 | "requires": { | ||
6605 | "options": ">=0.0.5", | ||
6606 | "ultron": "1.0.x" | ||
6607 | } | ||
6583 | } | 6608 | } |
6584 | } | 6609 | } |
6585 | }, | 6610 | }, |
@@ -19292,13 +19317,11 @@ | |||
19292 | } | 19317 | } |
19293 | }, | 19318 | }, |
19294 | "ws": { | 19319 | "ws": { |
19295 | "version": "1.1.5", | 19320 | "version": "6.2.1", |
19296 | "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", | 19321 | "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", |
19297 | "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", | 19322 | "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", |
19298 | "dev": true, | ||
19299 | "requires": { | 19323 | "requires": { |
19300 | "options": ">=0.0.5", | 19324 | "async-limiter": "~1.0.0" |
19301 | "ultron": "1.0.x" | ||
19302 | } | 19325 | } |
19303 | }, | 19326 | }, |
19304 | "wtf-8": { | 19327 | "wtf-8": { |
diff --git a/package.json b/package.json index 8f5c2e8c8..481e44af1 100644 --- a/package.json +++ b/package.json | |||
@@ -82,7 +82,8 @@ | |||
82 | "semver": "^5.4.1", | 82 | "semver": "^5.4.1", |
83 | "smoothscroll-polyfill": "^0.3.4", | 83 | "smoothscroll-polyfill": "^0.3.4", |
84 | "tar": "^4.0.2", | 84 | "tar": "^4.0.2", |
85 | "uuid": "^3.0.1" | 85 | "uuid": "^3.0.1", |
86 | "ws": "6.2.1" | ||
86 | }, | 87 | }, |
87 | "devDependencies": { | 88 | "devDependencies": { |
88 | "@adlk/misty": "^0.1.1", | 89 | "@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'; | |||
13 | export const DEV_API = 'https://dev.franzinfra.com'; | 13 | export const DEV_API = 'https://dev.franzinfra.com'; |
14 | export const LIVE_API = 'https://api.franzinfra.com'; | 14 | export const LIVE_API = 'https://api.franzinfra.com'; |
15 | 15 | ||
16 | export const LOCAL_WS_API = 'ws://localhost:3000'; | ||
17 | export const DEV_WS_API = 'wss://dev.franzinfra.com'; | ||
18 | export const LIVE_WS_API = 'wss://api.franzinfra.com'; | ||
19 | |||
16 | export const LOCAL_API_WEBSITE = 'http://localhost:3333'; | 20 | export const LOCAL_API_WEBSITE = 'http://localhost:3333'; |
17 | export const DEV_API_WEBSITE = 'https://meetfranz.com'; | 21 | export const DEV_API_WEBSITE = 'https://meetfranz.com'; |
18 | export const LIVE_API_WEBSITE = 'https://meetfranz.com'; | 22 | 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 { | |||
7 | LOCAL_API_WEBSITE, | 7 | LOCAL_API_WEBSITE, |
8 | DEV_API_WEBSITE, | 8 | DEV_API_WEBSITE, |
9 | LIVE_API_WEBSITE, | 9 | LIVE_API_WEBSITE, |
10 | LIVE_WS_API, | ||
11 | LOCAL_WS_API, | ||
12 | DEV_WS_API, | ||
10 | } from './config'; | 13 | } from './config'; |
11 | 14 | ||
12 | export const isDevMode = isDev; | 15 | export const isDevMode = isDev; |
@@ -26,17 +29,22 @@ export const ctrlKey = isMac ? '⌘' : 'Ctrl'; | |||
26 | export const cmdKey = isMac ? 'Cmd' : 'Ctrl'; | 29 | export const cmdKey = isMac ? 'Cmd' : 'Ctrl'; |
27 | 30 | ||
28 | let api; | 31 | let api; |
32 | let wsApi; | ||
29 | let web; | 33 | let web; |
30 | if (!isDevMode || (isDevMode && useLiveAPI)) { | 34 | if (!isDevMode || (isDevMode && useLiveAPI)) { |
31 | api = LIVE_API; | 35 | api = LIVE_API; |
36 | wsApi = LIVE_WS_API; | ||
32 | web = LIVE_API_WEBSITE; | 37 | web = LIVE_API_WEBSITE; |
33 | } else if (isDevMode && useLocalAPI) { | 38 | } else if (isDevMode && useLocalAPI) { |
34 | api = LOCAL_API; | 39 | api = LOCAL_API; |
40 | wsApi = LOCAL_WS_API; | ||
35 | web = LOCAL_API_WEBSITE; | 41 | web = LOCAL_API_WEBSITE; |
36 | } else { | 42 | } else { |
37 | api = DEV_API; | 43 | api = DEV_API; |
44 | wsApi = DEV_WS_API; | ||
38 | web = DEV_API_WEBSITE; | 45 | web = DEV_API_WEBSITE; |
39 | } | 46 | } |
40 | 47 | ||
41 | export const API = api; | 48 | export const API = api; |
49 | export const WS_API = wsApi; | ||
42 | export const WEBSITE = web; | 50 | 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 @@ | |||
1 | import PropTypes from 'prop-types'; | ||
2 | import { createActionsFromDefinitions } from '../../actions/lib/actions'; | ||
3 | |||
4 | export const settingsWSActions = createActionsFromDefinitions({ | ||
5 | greet: { | ||
6 | name: PropTypes.string.isRequired, | ||
7 | }, | ||
8 | }, PropTypes.checkPropTypes); | ||
9 | |||
10 | 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 @@ | |||
1 | import { reaction, runInAction } from 'mobx'; | ||
2 | import { SettingsWSStore } from './store'; | ||
3 | import state, { resetState } from './state'; | ||
4 | |||
5 | const debug = require('debug')('Franz:feature:settingsWS'); | ||
6 | |||
7 | let store = null; | ||
8 | |||
9 | export default function initAnnouncements(stores, actions) { | ||
10 | const { features } = stores; | ||
11 | |||
12 | // Toggle workspace feature | ||
13 | reaction( | ||
14 | () => ( | ||
15 | features.features.isSettingsWSEnabled | ||
16 | ), | ||
17 | (isEnabled) => { | ||
18 | if (isEnabled) { | ||
19 | debug('Initializing `settingsWS` feature'); | ||
20 | store = new SettingsWSStore(stores, null, actions, state); | ||
21 | store.initialize(); | ||
22 | runInAction(() => { state.isFeatureActive = true; }); | ||
23 | } else if (store) { | ||
24 | debug('Disabling `settingsWS` feature'); | ||
25 | runInAction(() => { state.isFeatureActive = false; }); | ||
26 | store.teardown(); | ||
27 | store = null; | ||
28 | resetState(); // Reset state to default | ||
29 | } | ||
30 | }, | ||
31 | { | ||
32 | fireImmediately: true, | ||
33 | }, | ||
34 | ); | ||
35 | } | ||
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 @@ | |||
1 | import { observable } from 'mobx'; | ||
2 | |||
3 | const defaultState = { | ||
4 | isFeatureActive: false, | ||
5 | }; | ||
6 | |||
7 | export const settingsWSState = observable(defaultState); | ||
8 | |||
9 | export function resetState() { | ||
10 | Object.assign(settingsWSState, defaultState); | ||
11 | } | ||
12 | |||
13 | 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 @@ | |||
1 | import { observable, reaction } from 'mobx'; | ||
2 | import WebSocket from 'ws'; | ||
3 | import ms from 'ms'; | ||
4 | |||
5 | import Store from '../../stores/lib/Store'; | ||
6 | import { WS_API } from '../../environment'; | ||
7 | |||
8 | const debug = require('debug')('Franz:feature:settingsWS:store'); | ||
9 | |||
10 | export class SettingsWSStore extends Store { | ||
11 | ws = null; | ||
12 | |||
13 | @observable connected = false; | ||
14 | |||
15 | pingTimeout = null; | ||
16 | |||
17 | reconnectTimeout = null; | ||
18 | |||
19 | constructor(stores, api, actions, state) { | ||
20 | super(stores, api, actions); | ||
21 | this.state = state; | ||
22 | } | ||
23 | |||
24 | setup() { | ||
25 | this.connect(); | ||
26 | |||
27 | reaction(() => !this.connected, this.reconnect.bind(this)); | ||
28 | } | ||
29 | |||
30 | connect() { | ||
31 | try { | ||
32 | const wsURL = `${WS_API}/ws/${this.stores.user.data.id}`; | ||
33 | debug('Setting up WebSocket to', wsURL); | ||
34 | |||
35 | this.ws = new WebSocket(wsURL); | ||
36 | |||
37 | this.ws.on('open', () => { | ||
38 | debug('Opened WebSocket'); | ||
39 | this.send({ | ||
40 | action: 'authorize', | ||
41 | token: this.stores.user.authToken, | ||
42 | }); | ||
43 | |||
44 | this.connected = true; | ||
45 | |||
46 | this.heartbeat(); | ||
47 | }); | ||
48 | |||
49 | this.ws.on('message', (data) => { | ||
50 | const resp = JSON.parse(data); | ||
51 | debug('Received message', resp); | ||
52 | |||
53 | if (resp.id) { | ||
54 | this.stores.user.getUserInfoRequest.patch((result) => { | ||
55 | if (!result) return; | ||
56 | |||
57 | debug('Patching user object with new values'); | ||
58 | Object.assign(result, resp); | ||
59 | }); | ||
60 | } | ||
61 | }); | ||
62 | |||
63 | this.ws.on('ping', this.heartbeat.bind(this)); | ||
64 | } catch (err) { | ||
65 | console.err(err); | ||
66 | } | ||
67 | } | ||
68 | |||
69 | heartbeat() { | ||
70 | debug('Heartbeat'); | ||
71 | clearTimeout(this.pingTimeout); | ||
72 | |||
73 | // Use `WebSocket#terminate()` and not `WebSocket#close()`. Delay should be | ||
74 | // equal to the interval at which your server sends out pings plus a | ||
75 | // conservative assumption of the latency. | ||
76 | this.pingTimeout = setTimeout(() => { | ||
77 | debug('Terminating connection reconnecting in 35'); | ||
78 | this.ws.terminate(); | ||
79 | |||
80 | this.connected = false; | ||
81 | }, ms('35s')); | ||
82 | } | ||
83 | |||
84 | send(data) { | ||
85 | if (this.ws) { | ||
86 | this.ws.send(JSON.stringify(data)); | ||
87 | debug('Sending data', data); | ||
88 | } else { | ||
89 | debug('WebSocket is not initialized'); | ||
90 | } | ||
91 | } | ||
92 | |||
93 | reconnect() { | ||
94 | if (!this.connected) { | ||
95 | debug('Trying to reconnect in 30s'); | ||
96 | this.reconnectTimeout = setInterval(() => { | ||
97 | debug('Trying to reconnect'); | ||
98 | this.connect(); | ||
99 | }, ms('30s')); | ||
100 | } else { | ||
101 | debug('Clearing reconnect interval'); | ||
102 | clearInterval(this.reconnectTimeout); | ||
103 | } | ||
104 | } | ||
105 | } | ||
106 | |||
107 | 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'; | |||
8 | import serviceProxy from '../features/serviceProxy'; | 8 | import serviceProxy from '../features/serviceProxy'; |
9 | import basicAuth from '../features/basicAuth'; | 9 | import basicAuth from '../features/basicAuth'; |
10 | import shareFranz from '../features/shareFranz'; | 10 | import shareFranz from '../features/shareFranz'; |
11 | import settingsWS from '../features/settingsWS'; | ||
11 | 12 | ||
12 | import { DEFAULT_FEATURES_CONFIG } from '../config'; | 13 | import { DEFAULT_FEATURES_CONFIG } from '../config'; |
13 | 14 | ||
@@ -58,5 +59,6 @@ export default class FeaturesStore extends Store { | |||
58 | serviceProxy(this.stores, this.actions); | 59 | serviceProxy(this.stores, this.actions); |
59 | basicAuth(this.stores, this.actions); | 60 | basicAuth(this.stores, this.actions); |
60 | shareFranz(this.stores, this.actions); | 61 | shareFranz(this.stores, this.actions); |
62 | settingsWS(this.stores, this.actions); | ||
61 | } | 63 | } |
62 | } | 64 | } |