@live-change/online-service 0.2.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/index.js +273 -0
  2. package/package.json +14 -0
package/index.js ADDED
@@ -0,0 +1,273 @@
1
+ const http = require('http')
2
+ const ReactiveDao = require("@live-change/dao")
3
+ const ReactiveDaoWebsocket = require("@live-change/dao-websocket")
4
+ const ReactiveServer = ReactiveDao.ReactiveServer
5
+ const WebSocketServer = require('websocket').server
6
+
7
+ const App = require("@live-change/framework")
8
+ const app = App.app()
9
+
10
+ const definition = app.createServiceDefinition({
11
+ name: 'online'
12
+ })
13
+
14
+ const config = definition.config
15
+ const onlinePort = config.port || process.env.ONLINE_PORT || 8007
16
+ const onlineHost = config.host || process.env.ONLINE_HOST || 'localhost'
17
+ const onlineUrl = `ws://${onlineHost}:${onlinePort}/ws`
18
+
19
+ const eventDelay = 2000
20
+ const cacheDelay = 5000
21
+
22
+ async function sendOnlineEvent(path) {
23
+ const type = path[0]
24
+ const params = path[1]
25
+ try {
26
+ if(type == 'object') {
27
+ const { group } = params
28
+ const triggerName = `${group}Online`
29
+ console.log("TRIGGER", triggerName)
30
+ await app.trigger({
31
+ ...params,
32
+ type: triggerName
33
+ })
34
+ } else if(type == 'user') {
35
+ const { group, user } = params
36
+ const triggerName = `user${group ? group.slice(0, 1).toUpperCase() + group.slice(1) : ''}Online`
37
+ console.log("TRIGGER", triggerName)
38
+ await app.trigger({
39
+ ...params,
40
+ type: triggerName
41
+ })
42
+ } else if(type == 'session') {
43
+ const { group, session } = params
44
+ const triggerName = `session${group ? group.slice(0, 1).toUpperCase() + group.slice(1) : ''}Online`
45
+ console.log("TRIGGER", triggerName)
46
+ await app.trigger({
47
+ ...params,
48
+ type: triggerName
49
+ })
50
+ }
51
+ } catch(error) {
52
+ console.error("ONLINE EVENT ERROR")
53
+ }
54
+ }
55
+
56
+ async function sendOfflineEvent(path) {
57
+ const type = path[0]
58
+ const params = path[1]
59
+ try {
60
+ if(type == 'object') {
61
+ const { group } = params
62
+ const triggerName = `${group}Offline`
63
+ console.log("TRIGGER", triggerName)
64
+ await app.trigger({
65
+ ...params,
66
+ type: triggerName
67
+ })
68
+ } else if(type == 'user') {
69
+ const { group, user } = params
70
+ const triggerName = `user${group ? group.slice(0, 1).toUpperCase() + group.slice(1) : ''}Offline`
71
+ console.log("TRIGGER", triggerName)
72
+ await app.trigger({
73
+ ...params,
74
+ type: triggerName
75
+ })
76
+ } else if(type == 'session') {
77
+ const { group, session } = params
78
+ const triggerName = `session${group ? group.slice(0, 1).toUpperCase() + group.slice(1) : ''}Offline`
79
+ console.log("TRIGGER", triggerName)
80
+ await app.trigger({
81
+ ...params,
82
+ type: triggerName
83
+ })
84
+ }
85
+ } catch(error) {
86
+ console.error("OFFLINE EVENT ERROR")
87
+ }
88
+ }
89
+
90
+ async function sendAllOfflineEvent() {
91
+ console.log("SEND ALL OFFLINE EVENT")
92
+ await app.trigger({
93
+ type: `allOffline`
94
+ })
95
+ }
96
+
97
+ const selfObservables = new Map()
98
+
99
+ class SelfObservable extends ReactiveDao.Observable {
100
+ constructor(path) {
101
+ super()
102
+ this.path = path
103
+ this.disposeTimeout = null
104
+ this.onlineEventTimeout = null
105
+ this.offlineEventTimeout = null
106
+ this.lastEvent = null
107
+
108
+ console.log("PATH", this.path, "IS ONLINE")
109
+ this.setOnlineEventTimeout()
110
+ }
111
+ setOnlineEventTimeout() {
112
+ if(this.lastEvent == 'online') return
113
+ this.onlineEventTimeout = setTimeout(() => {
114
+ sendOnlineEvent(this.path)
115
+ this.lastEvent = 'online'
116
+ this.onlineEventTimeout = null
117
+ }, eventDelay)
118
+ }
119
+ setOfflineEventTimeout() {
120
+ if(this.lastEvent == 'offline') return
121
+ this.offlineEventTimeout = setTimeout(() => {
122
+ sendOfflineEvent(this.path)
123
+ this.lastEvent = 'offline'
124
+ this.offlineEventTimeout = null
125
+ }, eventDelay)
126
+ }
127
+ clearOnlineEventTimeout() {
128
+ if(this.onlineEventTimeout) {
129
+ clearTimeout(this.onlineEventTimeout)
130
+ this.onlineEventTimeout = null
131
+ }
132
+ }
133
+ clearOfflineEventTimeout() {
134
+ if(this.offlineEventTimeout) {
135
+ clearTimeout(this.offlineEventTimeout)
136
+ this.offlineEventTimeout = null
137
+ }
138
+ }
139
+ observe(observer) {
140
+ if(this.isDisposed()) this.respawn()
141
+ this.observers.push(observer)
142
+ this.fireObserver(observer, 'set', this.observers.length)
143
+ }
144
+ unobserve(observer) {
145
+ console.log("ONLINE UNOBSERVED")
146
+ this.observers.splice(this.observers.indexOf(observer), 1)
147
+ this.fireObservers('set', this.observers.length)
148
+ if(this.isUseless()) this.dispose()
149
+ }
150
+ dispose() {
151
+ this.disposed = true
152
+ console.log("PATH", this.path, "IS OFFLINE")
153
+ this.disposeTimeout = setTimeout(() => {
154
+ if(this.disposed) {
155
+ selfObservables.delete(JSON.stringify(this.path))
156
+ }
157
+ }, cacheDelay)
158
+ this.clearOnlineEventTimeout()
159
+ this.setOfflineEventTimeout()
160
+ }
161
+ respawn() {
162
+ if(this.disposeTimeout) {
163
+ clearTimeout(this.disposeTimeout)
164
+ this.disposeTimeout = null
165
+ }
166
+ this.disposed = false
167
+ this.clearOfflineEventTimeout()
168
+ this.setOnlineEventTimeout()
169
+ console.log("PATH", this.path, "IS ONLINE AGAIN")
170
+ }
171
+ }
172
+
173
+ function getSelfObservable(path) {
174
+ let observable = selfObservables.get(JSON.stringify(path))
175
+ if(observable) return observable
176
+ observable = new SelfObservable(path)
177
+ selfObservables.set(JSON.stringify(path), observable)
178
+ return observable
179
+ }
180
+
181
+ const onlineDao = {
182
+ observable([type, ...path]) {
183
+ console.log("OBSERVABLE", type, path)
184
+ if(type!='online') throw new Error("not found")
185
+ return getSelfObservable(path)
186
+ },
187
+ get([type, ...path]) {
188
+ console.log("GET", type, path)
189
+ if(type!='online') throw new Error("not found")
190
+ let observable = selfObservables.get(path)
191
+ return observable ? observable.observers.length : 0
192
+ },
193
+ dispose() {
194
+ }
195
+ }
196
+
197
+ const createDao = (clientSessionId) => {
198
+ console.log("ONLINE SERVICE DAO")
199
+ return onlineDao
200
+ }
201
+
202
+ definition.beforeStart(async service => {
203
+ await sendAllOfflineEvent()
204
+
205
+ const reactiveServer = new ReactiveServer(createDao)
206
+
207
+ const httpServer = http.createServer() // TODO: pure HTTP API
208
+ httpServer.listen(onlinePort)
209
+
210
+ let wsServer = new WebSocketServer({httpServer, autoAcceptConnections: false})
211
+ wsServer.on("request",(request) => {
212
+ let serverConnection = new ReactiveDaoWebsocket.server(request)
213
+ reactiveServer.handleConnection(serverConnection)
214
+ })
215
+
216
+ console.log(`server started at localhost:${onlinePort}`)
217
+ })
218
+
219
+ const onlineClient = new ReactiveDaoWebsocket.client("api-server-"+process.pid, onlineUrl)
220
+
221
+ definition.view({
222
+ name: "session",
223
+ properties: {},
224
+ async get(params, { client, service }) {
225
+ const { session } = client
226
+ return onlineClient.get(['online', 'session', client.session, { ...params, session }])
227
+ },
228
+ async observable(params, { client, service }) {
229
+ const { session } = client
230
+ return onlineClient.observable(['online', 'session', { ...params, session }], ReactiveDao.ObservableValue)
231
+ }
232
+ })
233
+
234
+ definition.view({
235
+ name: "user",
236
+ properties: {},
237
+ async get(params, { client, service }) {
238
+ const { user } = client
239
+ return onlineClient.get(['online', 'user', { ...params, user }])
240
+ },
241
+ async observable(params, { client, service }) {
242
+ const { user } = client
243
+ return onlineClient.observable(['online', 'user', { ...params, user }], ReactiveDao.ObservableValue)
244
+ }
245
+ })
246
+
247
+ definition.view({
248
+ name: "self",
249
+ properties: {},
250
+ async get(params, { client, service }) {
251
+ return onlineClient.get(client.user
252
+ ? ['online', 'user', { ...params, user: client.user }]
253
+ : ['online', 'session', { ...params, session: client.session }])
254
+ },
255
+ async observable(params, { client, service }) {
256
+ return onlineClient.observable(client.user
257
+ ? ['online', 'user', { ...params, user: client.user }]
258
+ : ['online', 'session', { ...params, session: client.session }], ReactiveDao.ObservableValue)
259
+ }
260
+ })
261
+
262
+ definition.view({
263
+ name: "object",
264
+ properties: {},
265
+ async get(params, { client, service }) {
266
+ return onlineClient.get(['online', 'object', params])
267
+ },
268
+ async observable(params, { client, service }) {
269
+ return onlineClient.observable(['online', 'object', params], ReactiveDao.ObservableValue)
270
+ }
271
+ })
272
+
273
+ module.exports = definition
package/package.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "@live-change/online-service",
3
+ "version": "0.2.29",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "start": "node index.js",
8
+ "setup": "INSTALL=true node index.js",
9
+ "test": "NODE_ENV=test blue-tape tests/*"
10
+ },
11
+ "author": "Michał Łaszczewski <michal@emikse.com>",
12
+ "license": "BSD-3-Clause",
13
+ "gitHead": "37d229ac05adf5e045ae3dcc826c6945d5dc3670"
14
+ }