@knowlearning/agents 0.3.16 → 0.3.18

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/agents/generic.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import MutableProxy from '../persistence/json.js'
2
2
 
3
+ const HEARTBEAT_TIMEOUT = 10000
4
+
3
5
  // transform our custom path implementation to the standard JSONPatch path
4
6
  function standardJSONPatch(patch) {
5
7
  return patch.map(p => {
@@ -34,6 +36,8 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
34
36
  let failedConnections = 0
35
37
  let mode = 'normal'
36
38
  const environmentPromise = new Promise(r => resolveEnvironment = r)
39
+ let lastSentSI = -1
40
+ let lastHeartbeat
37
41
 
38
42
  const sessionData = new MutableProxy({}, patch => queueMessage({scope: 'sessions', patch}))
39
43
 
@@ -44,6 +48,9 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
44
48
  patches: {}
45
49
  }
46
50
 
51
+ log('INITIALIZING AGENT CONNECTION')
52
+ initWS()
53
+
47
54
  function log() {
48
55
  if (mode === 'debug') console.log(...arguments)
49
56
  }
@@ -53,11 +60,6 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
53
60
  if (watcherIndex > -1) watchers[key].splice(watcherIndex, 1)
54
61
  else console.warn('TRIED TO REMOVE WATCHER THAT DOES NOT EXIST')
55
62
  }
56
-
57
- log('INITIALIZING AGENT CONNECTION')
58
- initWS()
59
-
60
- let lastSentSI = -1
61
63
  function flushMessageQueue() {
62
64
  // TODO: probably want to make this loop async so we don't try and push more to
63
65
  // a closed connection
@@ -78,16 +80,43 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
78
80
  flushMessageQueue()
79
81
  }
80
82
 
83
+ function checkHeartbeat() {
84
+ clearTimeout(lastHeartbeat)
85
+ lastHeartbeat = setTimeout(
86
+ () => {
87
+ log('CLOSING DUE TO HEARTBEAT TIMEOUT')
88
+ restartConnection()
89
+ },
90
+ HEARTBEAT_TIMEOUT
91
+ )
92
+ }
93
+
94
+ async function restartConnection() {
95
+ if (authed) log(`CLOSED DOMAIN ${domain} USER ${user} SESSION ${session} CONNECTION TO SERVER ${server}`)
96
+ authed = false
97
+ ws.onmessage = () => {} // needs to be a no-op since a closing ws can still get messages
98
+ if (!disconnected) {
99
+ await new Promise(r => setTimeout(r, Math.min(1000, failedConnections * 100)))
100
+ failedConnections += 1
101
+ initWS() // TODO: don't do this if we are purposefully unloading...
102
+ }
103
+ }
104
+
81
105
  function initWS() {
82
106
  ws = new WebSocket(`${protocol}://${host}`)
83
107
 
108
+ let opened = false
84
109
  ws.onopen = async () => {
110
+ opened = true
85
111
  log('AUTHORIZING NEWLY OPENED WS FOR SESSION:', session)
86
112
  failedConnections = 0
87
113
  ws.send(JSON.stringify({ token: await token(), session }))
88
114
  }
89
115
 
90
116
  ws.onmessage = async ({ data }) => {
117
+ checkHeartbeat()
118
+ if (data.length === 0) return // heartbeat
119
+
91
120
  try {
92
121
  log('handling message', disconnected, authed)
93
122
  const message = JSON.parse(data)
@@ -151,19 +180,16 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
151
180
  }
152
181
 
153
182
  ws.onerror = async error => {
154
- log('WS CONNECTION ERROR', error.message)
183
+ log('WS CONNECTION ERROR', error.message, opened)
184
+ if (!opened) restartConnection() // onclose won't trigger if never opened
155
185
  }
156
186
 
157
- ws.onclose = async () => {
158
- if (authed) log(`CLOSED DOMAIN ${domain} USER ${user} SESSION ${session} CONNECTION TO SERVER ${server}`)
159
- authed = false
160
- ws.onmessage = () => {} // needs to be a no-op since a closing ws can still get messages
161
- if (!disconnected) {
162
- await new Promise(r => setTimeout(r, Math.min(1000, failedConnections * 100)))
163
- failedConnections += 1
164
- initWS() // TODO: don't do this if we are purposefully unloading...
165
- }
187
+ ws.onclose = async error => {
188
+ log('WS CLOSURE', error.message, opened)
189
+ restartConnection()
166
190
  }
191
+
192
+ checkHeartbeat()
167
193
  }
168
194
 
169
195
  function environment() { return environmentPromise }
@@ -205,7 +231,7 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
205
231
  // POSSIBLE: wait for most recent interaction update response for key k that we sent...
206
232
  await lastInteractionUpdateForWatchedScope[k]
207
233
  // TODO: probably something like await updateMessageReceivedForLastInteractionWeSentForKey[k]
208
- resolveState(states[k])
234
+ resolveState(structuredClone(await states[k]))
209
235
  })
210
236
 
211
237
  promise.watch = fn => {
package/agents/node.js CHANGED
@@ -14,5 +14,5 @@ export default () => new Agent({
14
14
  uuid,
15
15
  fetch,
16
16
  applyPatch: fastJSONPatch.applyPatch,
17
- reboot: () => process.exit()
17
+ reboot: () => process.exit(1)
18
18
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knowlearning/agents",
3
- "version": "0.3.16",
3
+ "version": "0.3.18",
4
4
  "description": "API for embedding applications in KnowLearning systems.",
5
5
  "main": "node.js",
6
6
  "browser": "browser.js",