@knowlearning/agents 0.3.17 → 0.4.0

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.
@@ -95,28 +95,27 @@ export default function embed(environment, iframe) {
95
95
 
96
96
  // TODO: make sure content security policy headers for embedded domain always restrict iframe
97
97
  // src to only self for embedded domain
98
- Agent
99
- .environment()
100
- .then(async ({ embed }) => {
101
- iframe.onload = () => {
102
- frameLoaded = true
103
- processPostMessageQueue()
104
- }
105
- const { protocol, pathname } = window.location
106
- if (validateUUID(environment.content)) {
107
- // TODO: fix development port workararounds for embed
108
- if (protocol === 'http:') embed = embed.replace(':32002', ':32020')
109
- iframe.src = `${protocol}//${embed}${pathname}?s=${session}`
110
- }
111
- else {
112
- iframe.src = environment.content
113
- }
98
+ iframe.onload = () => {
99
+ frameLoaded = true
100
+ processPostMessageQueue()
101
+ }
114
102
 
115
- while(!embeddedAgentInitialized) {
116
- postMessage({ type: 'setup', session })
117
- await new Promise(r => setTimeout(r, 100))
118
- }
119
- })
103
+ setUpEmbeddedFrame()
104
+
105
+ async function setUpEmbeddedFrame() {
106
+ const { protocol } = window.location
107
+ const { id } = environment
108
+ if (validateUUID(id)) {
109
+ const { domain } = await Agent.metadata(id)
110
+ iframe.src = `${protocol}//${domain}/${id}`
111
+ }
112
+ else iframe.src = id // TODO: ensure is url
113
+
114
+ while(!embeddedAgentInitialized) {
115
+ postMessage({ type: 'setup', session })
116
+ await new Promise(r => setTimeout(r, 100))
117
+ }
118
+ }
120
119
 
121
120
  function remove () {
122
121
  if (iframe.parentNode) iframe.parentNode.removeChild(iframe)
package/agents/generic.js CHANGED
@@ -36,15 +36,17 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
36
36
  let failedConnections = 0
37
37
  let mode = 'normal'
38
38
  const environmentPromise = new Promise(r => resolveEnvironment = r)
39
+ let lastSentSI = -1
40
+ let lastHeartbeat
39
41
 
40
- const sessionData = new MutableProxy({}, patch => queueMessage({scope: 'sessions', patch}))
42
+ // TODO: probably an use "mutate" function from below
43
+ const subscriptions = new MutableProxy({}, patch => queueMessage({scope: 'subscriptions', patch}))
44
+ const uploads = new MutableProxy({}, patch => queueMessage({scope: 'uploads', patch}))
45
+ const downloads = new MutableProxy({}, patch => queueMessage({scope: 'downloads', patch}))
46
+ const patches = new MutableProxy({}, patch => queueMessage({scope: 'patches', patch}))
41
47
 
42
- sessionData[session] = {
43
- subscriptions: {},
44
- uploads: {},
45
- downloads: {},
46
- patches: {}
47
- }
48
+ log('INITIALIZING AGENT CONNECTION')
49
+ initWS()
48
50
 
49
51
  function log() {
50
52
  if (mode === 'debug') console.log(...arguments)
@@ -55,11 +57,6 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
55
57
  if (watcherIndex > -1) watchers[key].splice(watcherIndex, 1)
56
58
  else console.warn('TRIED TO REMOVE WATCHER THAT DOES NOT EXIST')
57
59
  }
58
-
59
- log('INITIALIZING AGENT CONNECTION')
60
- initWS()
61
-
62
- let lastSentSI = -1
63
60
  function flushMessageQueue() {
64
61
  // TODO: probably want to make this loop async so we don't try and push more to
65
62
  // a closed connection
@@ -80,23 +77,34 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
80
77
  flushMessageQueue()
81
78
  }
82
79
 
83
- let lastHeartbeat
84
80
  function checkHeartbeat() {
85
81
  clearTimeout(lastHeartbeat)
86
82
  lastHeartbeat = setTimeout(
87
83
  () => {
88
84
  log('CLOSING DUE TO HEARTBEAT TIMEOUT')
89
- ws.close()
85
+ restartConnection()
90
86
  },
91
87
  HEARTBEAT_TIMEOUT
92
88
  )
93
89
  }
94
90
 
91
+ async function restartConnection() {
92
+ if (authed) log(`CLOSED DOMAIN ${domain} USER ${user} SESSION ${session} CONNECTION TO SERVER ${server}`)
93
+ authed = false
94
+ ws.onmessage = () => {} // needs to be a no-op since a closing ws can still get messages
95
+ if (!disconnected) {
96
+ await new Promise(r => setTimeout(r, Math.min(1000, failedConnections * 100)))
97
+ failedConnections += 1
98
+ initWS() // TODO: don't do this if we are purposefully unloading...
99
+ }
100
+ }
101
+
95
102
  function initWS() {
96
103
  ws = new WebSocket(`${protocol}://${host}`)
97
104
 
105
+ let opened = false
98
106
  ws.onopen = async () => {
99
- checkHeartbeat()
107
+ opened = true
100
108
  log('AUTHORIZING NEWLY OPENED WS FOR SESSION:', session)
101
109
  failedConnections = 0
102
110
  ws.send(JSON.stringify({ token: await token(), session }))
@@ -146,10 +154,10 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
146
154
  else {
147
155
  const key = `${message.domain}/${message.user}/${message.scope}`
148
156
  if (watchers[key]) {
149
- if (sessionData[session].subscriptions[key] + 1 !== message.ii) {
157
+ if (subscriptions[key] + 1 !== message.ii) {
150
158
  // TODO: this seems to be an error that happens with decent regularity (an answer with a given si was skipped/failed)
151
159
  // we should be wary of out-of-order ii being passed down (maybe need to wait for older ones???)
152
- console.warn('UNEXPECTED UPDATE INTERACTION INDEX!!!!!!!!!!! last index in session', sessionData[session].subscriptions[key], ' passed index ', message.ii)
160
+ console.warn('UNEXPECTED UPDATE INTERACTION INDEX!!!!!!!!!!! last index in session', subscriptions[key], ' passed index ', message.ii)
153
161
  }
154
162
  states[key] = await states[key] || {}
155
163
 
@@ -158,7 +166,7 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
158
166
 
159
167
  applyPatch(states[key], standardJSONPatch(message.patch.slice(lastResetPatchIndex + 1)))
160
168
  watchers[key].forEach(fn => fn({ ...message, state: states[key] }))
161
- sessionData[session].subscriptions[key] = message.ii
169
+ subscriptions[key] = message.ii
162
170
  }
163
171
  }
164
172
  }
@@ -169,19 +177,16 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
169
177
  }
170
178
 
171
179
  ws.onerror = async error => {
172
- log('WS CONNECTION ERROR', error.message)
180
+ log('WS CONNECTION ERROR', error.message, opened)
181
+ if (!opened) restartConnection() // onclose won't trigger if never opened
173
182
  }
174
183
 
175
- ws.onclose = async () => {
176
- if (authed) log(`CLOSED DOMAIN ${domain} USER ${user} SESSION ${session} CONNECTION TO SERVER ${server}`)
177
- authed = false
178
- ws.onmessage = () => {} // needs to be a no-op since a closing ws can still get messages
179
- if (!disconnected) {
180
- await new Promise(r => setTimeout(r, Math.min(1000, failedConnections * 100)))
181
- failedConnections += 1
182
- initWS() // TODO: don't do this if we are purposefully unloading...
183
- }
184
+ ws.onclose = async error => {
185
+ log('WS CLOSURE', error.message, opened)
186
+ restartConnection()
184
187
  }
188
+
189
+ checkHeartbeat()
185
190
  }
186
191
 
187
192
  function environment() { return environmentPromise }
@@ -203,15 +208,15 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
203
208
  if (states[k] === undefined) {
204
209
  watchers[k] = []
205
210
  states[k] = new Promise(async resolve => {
206
- sessionData[session].subscriptions[k] = null
211
+ subscriptions[k] = null
207
212
  const { ii, state } = await lastMessageResponse()
208
- sessionData[session].subscriptions[k] = ii
213
+ subscriptions[k] = ii
209
214
  resolve(state)
210
215
  if (ii === -1) {
211
216
  // -1 indicates the result is a computed scope, so
212
217
  // ii does not apply (we clear out the subscription to not cache value)
213
218
  delete states[k]
214
- delete sessionData[session].subscriptions[k]
219
+ delete subscriptions[k]
215
220
  }
216
221
  })
217
222
  }
@@ -223,7 +228,7 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
223
228
  // POSSIBLE: wait for most recent interaction update response for key k that we sent...
224
229
  await lastInteractionUpdateForWatchedScope[k]
225
230
  // TODO: probably something like await updateMessageReceivedForLastInteractionWeSentForKey[k]
226
- resolveState(states[k])
231
+ resolveState(structuredClone(await states[k]))
227
232
  })
228
233
 
229
234
  promise.watch = fn => {
@@ -241,7 +246,7 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
241
246
  const k = await key
242
247
  removeWatcher(k, watchFn)
243
248
  if (watchers[k].length === 0) {
244
- delete sessionData[session].subscriptions[k]
249
+ delete subscriptions[k]
245
250
  delete watchers[k]
246
251
  }
247
252
  }
@@ -252,9 +257,9 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
252
257
  // TODO: if no data, set up streaming upload
253
258
  async function upload(name, type, data, id=uuid()) {
254
259
  // TODO: include data size info...
255
- sessionData[session].uploads[id] = { url: null, sent: 0, name, type }
260
+ uploads[id] = { url: null, sent: 0, name, type }
256
261
  const { url } = await lastMessageResponse()
257
- sessionData[session].uploads[id].url = url
262
+ uploads[id].url = url
258
263
 
259
264
  if (data === undefined) return url
260
265
  else {
@@ -268,9 +273,9 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
268
273
  }
269
274
 
270
275
  async function download(id, passthrough=false) {
271
- sessionData[session].downloads[id] = { url: null, size: null, sent: 0 }
276
+ downloads[id] = { url: null, size: null, sent: 0 }
272
277
  const { url } = await lastMessageResponse()
273
- sessionData[session].downloads[id].url = url
278
+ downloads[id].url = url
274
279
 
275
280
  if (passthrough) return url
276
281
  else {
@@ -283,7 +288,7 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
283
288
  }
284
289
 
285
290
  async function patch(root, scopes) {
286
- sessionData[session].patches[uuid()] = { root, scopes }
291
+ patches[uuid()] = { root, scopes }
287
292
  const { swaps } = await lastMessageResponse()
288
293
  return { swaps }
289
294
  }
@@ -325,6 +330,7 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
325
330
  }
326
331
 
327
332
  async function mutate(scope, initialize=true) {
333
+ // TODO: probably can remove redundant copy
328
334
  const initial = initialize ? copy(await state(scope) || {}) : {}
329
335
  return new MutableProxy(initial, patch => interact(scope, patch))
330
336
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knowlearning/agents",
3
- "version": "0.3.17",
3
+ "version": "0.4.0",
4
4
  "description": "API for embedding applications in KnowLearning systems.",
5
5
  "main": "node.js",
6
6
  "browser": "browser.js",
package/vue/3/content.vue CHANGED
@@ -12,10 +12,6 @@ export default {
12
12
  id: {
13
13
  type: String,
14
14
  required: true
15
- },
16
- scope: {
17
- type: String,
18
- required: false
19
15
  }
20
16
  },
21
17
  unmounted() {
@@ -25,9 +21,9 @@ export default {
25
21
  setup(iframe) {
26
22
  if (!iframe || this.iframe === iframe) return
27
23
 
28
- const { id, scope } = this
24
+ const { id } = this
29
25
  this.iframe = iframe
30
- this.embedding = Agent.embed({ content: id, scope }, iframe)
26
+ this.embedding = Agent.embed({ id }, iframe)
31
27
 
32
28
  /*
33
29
  const { handle } = this.embedding