@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.
- package/index.js +273 -0
- 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
|
+
}
|