@knowlearning/agents 0.9.27 → 0.9.28

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.
@@ -7,6 +7,7 @@ export default function EmbeddedAgent() {
7
7
  const session = new Promise(r => resolveSession = r)
8
8
  const responses = {}
9
9
  const watchers = {}
10
+ const sentUpdates = {}
10
11
 
11
12
  function removeWatcher(key, fn) {
12
13
  const watcherIndex = watchers[key].findIndex(x => x === fn)
@@ -37,8 +38,13 @@ export default function EmbeddedAgent() {
37
38
  }
38
39
  }
39
40
 
41
+ let sessionResolved = false
40
42
  addEventListener('message', async ({ data }) => {
41
- if (data.type === 'setup') resolveSession(data.session)
43
+ if (data.type === 'setup' && !sessionResolved) {
44
+ sessionResolved = true
45
+ resolveSession(data.session)
46
+ }
47
+ else if (!sessionResolved || data.session !== await session) return
42
48
  else if (responses[data.requestId]) {
43
49
  const { resolve, reject } = responses[data.requestId]
44
50
  if (data.error) reject(data.error)
@@ -50,7 +56,15 @@ export default function EmbeddedAgent() {
50
56
  const d = !domain || domain === rootDomain ? '' : domain
51
57
  const u = !user || auth.user === user ? '' : user
52
58
  const key = isUUID(scope) ? scope : `${d}/${u}/${scope}`
53
- if (watchers[key]) watchers[key].forEach(fn => fn(data))
59
+ if (watchers[key]) {
60
+ if (sentUpdates[key] + 1 === data.ii) {
61
+ sentUpdates[key] = data.ii
62
+ watchers[key].forEach(fn => fn(data))
63
+ }
64
+ else if (data.ii !== sentUpdates[key]) {
65
+ console.warn('Out of order or repeated update for', key, data.ii, sentUpdates[key])
66
+ }
67
+ }
54
68
  }
55
69
  })
56
70
 
@@ -84,11 +98,24 @@ export default function EmbeddedAgent() {
84
98
 
85
99
  function watch(scope, fn, user, domain) {
86
100
  tagIfNotYetTaggedInSession('subscribed', scope)
87
- const key = isUUID(scope) ? scope : `${ domain || ''}/${ user || ''}/${scope}`
88
- if (!watchers[key]) watchers[key] = []
89
- watchers[key].push(fn)
90
- send({ type: 'state', scope, user, domain })
91
- return () => removeWatcher(key, fn)
101
+
102
+ const key = (
103
+ environment()
104
+ .then(async ({ auth, domain:rootDomain }) => {
105
+ const d = !domain || domain === rootDomain ? '' : domain
106
+ const u = !user || auth.user === user ? '' : user
107
+ const key = isUUID(scope) ? scope : `${d}/${u}/${scope}`
108
+
109
+ const state = await send({ type: 'state', scope, user, domain })
110
+ const metadata = await send({ type: 'metadata', scope, user, domain })
111
+ fn({ state, patch: null, ii: metadata.ii })
112
+ sentUpdates[key] = metadata.ii
113
+ if (!watchers[key]) watchers[key] = []
114
+ watchers[key].push(fn)
115
+ return key
116
+ })
117
+ )
118
+ return async () => removeWatcher(await key, fn)
92
119
  }
93
120
 
94
121
  async function patch(root, scopes) {
@@ -12,10 +12,12 @@ export default function browserAgent(options={}) {
12
12
  try { embedded = window.self !== window.top }
13
13
  catch (e) { embedded = true }
14
14
 
15
- Agent = embedded && !options.root ? EmbeddedAgent() : RootAgent(options)
16
- Agent.embed = embed
15
+ const newAgent = embedded && !options.root ? EmbeddedAgent() : RootAgent(options)
16
+ newAgent.embed = embed
17
17
 
18
- return Agent
18
+ if (!Agent) Agent = newAgent
19
+
20
+ return newAgent
19
21
  }
20
22
 
21
23
  const copy = x => JSON.parse(JSON.stringify(x))
@@ -57,10 +59,9 @@ function embed(environment, iframe) {
57
59
  if (listeners.close) listeners.close(message.info)
58
60
  }
59
61
  else if (type === 'environment') {
62
+ const { context } = message
60
63
  const env = await Agent.environment()
61
- const context = [...env.context, environment.id]
62
- Object.assign(env, { ...env, context })
63
- sendDown(env)
64
+ sendDown({ ...env, context: [...env.context, environment.id] })
64
65
  }
65
66
  else if (type === 'interact') {
66
67
  const { scope, patch } = message
@@ -154,6 +155,7 @@ function embed(environment, iframe) {
154
155
  else iframe.src = id // TODO: ensure is url
155
156
 
156
157
  while(!embeddedAgentInitialized) {
158
+ // TODO: wait for any other agents that are initializing from other potential root agents
157
159
  postMessage({ type: 'setup', session })
158
160
  await new Promise(r => setTimeout(r, 100))
159
161
  }
@@ -44,6 +44,7 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
44
44
  tagIfNotYetTaggedInSession,
45
45
  interact,
46
46
  fetch,
47
+ synced,
47
48
  metadata
48
49
  }
49
50
 
@@ -18,7 +18,6 @@ export default function messageQueue(setEnvironment, { token, protocol, host, We
18
18
  let ws
19
19
  let user
20
20
  let authed = false
21
- let isSynced = false
22
21
  let session
23
22
  let server
24
23
  let si = -1
@@ -30,7 +29,7 @@ export default function messageQueue(setEnvironment, { token, protocol, host, We
30
29
  let lastSynchronousScopePatchPromise = null
31
30
  let restarting = false
32
31
  let disconnected = false
33
- const syncedPromiseResolutions = []
32
+ const outstandingSyncPromises = []
34
33
  const responses = {}
35
34
 
36
35
 
@@ -41,7 +40,6 @@ export default function messageQueue(setEnvironment, { token, protocol, host, We
41
40
  }
42
41
 
43
42
  function queueMessage({ scope, patch }) {
44
- isSynced = false
45
43
  if (lastSynchronousScopePatched === scope) {
46
44
  const i = messageQueue.length - 1
47
45
  messageQueue[i].patch = [...messageQueue[i].patch, ...patch]
@@ -74,8 +72,10 @@ export default function messageQueue(setEnvironment, { token, protocol, host, We
74
72
  }
75
73
 
76
74
  function resolveSyncPromises() {
77
- while (syncedPromiseResolutions.length) syncedPromiseResolutions.shift()()
78
- isSynced = true
75
+ const lowestOutstandingResponseIndex = Object.keys(responses).map(parseInt).sort()[0] || Infinity
76
+ while (outstandingSyncPromises[0] && outstandingSyncPromises[0].si < lowestOutstandingResponseIndex) {
77
+ outstandingSyncPromises.shift().resolve()
78
+ }
79
79
  }
80
80
 
81
81
  function checkHeartbeat() {
@@ -162,7 +162,7 @@ export default function messageQueue(setEnvironment, { token, protocol, host, We
162
162
 
163
163
  delete responses[message.si]
164
164
  ws.send(JSON.stringify({ack: message.si})) // acknowledgement that we have received the response for this message
165
- if (Object.keys(responses).length === 0) resolveSyncPromises()
165
+ resolveSyncPromises()
166
166
  }
167
167
  else {
168
168
  // TODO: consider what to do here... probably want to throw error if in dev env
@@ -208,7 +208,11 @@ export default function messageQueue(setEnvironment, { token, protocol, host, We
208
208
  checkHeartbeat()
209
209
  }
210
210
 
211
- async function synced() { return isSynced ? null : new Promise(res => syncedPromiseResolutions.push(res)) }
211
+ async function synced() {
212
+ const syncPromise = new Promise(resolve => outstandingSyncPromises.push({ si: lastSentSI, resolve }))
213
+ resolveSyncPromises()
214
+ return syncPromise
215
+ }
212
216
 
213
217
  function lastMessageResponse() { return new Promise((res, rej) => responses[si].push([res, rej])) }
214
218
 
@@ -1,23 +1,13 @@
1
1
  import { validate as isUUID } from 'uuid'
2
2
 
3
- export default function({ metadata, state, watchers }) {
3
+ export default function({ metadata, state, watchers, synced }) {
4
4
 
5
5
  function watch(scope=DEFAULT_SCOPE_NAME, fn, user, domain) {
6
6
  if (Array.isArray(scope)) return watchResolution(scope, fn, user, domain)
7
7
 
8
- let initialSent = false
9
- const queue = []
10
- function cb(update) {
11
- if (initialSent) fn(update)
12
- else queue.push(update)
13
- }
14
-
15
8
  const statePromise = state(scope, user, domain)
16
9
  const qualifiedScope = isUUID(scope) ? scope : `${domain || ''}/${user || ''}/${scope}`
17
10
 
18
- if (!watchers[qualifiedScope]) watchers[qualifiedScope] = []
19
- watchers[qualifiedScope].push(cb)
20
-
21
11
  metadata(scope, user, domain)
22
12
  .then(async ({ ii }) => {
23
13
  fn({
@@ -28,11 +18,11 @@ export default function({ metadata, state, watchers }) {
28
18
  patch: null,
29
19
  ii
30
20
  })
31
- initialSent = true
32
- queue.forEach(fn)
21
+ if (!watchers[qualifiedScope]) watchers[qualifiedScope] = []
22
+ watchers[qualifiedScope].push(fn)
33
23
  })
34
24
 
35
- return () => removeWatcher(qualifiedScope, cb)
25
+ return () => removeWatcher(qualifiedScope, fn)
36
26
  }
37
27
 
38
28
  function watchResolution(path, callback, user, domain) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knowlearning/agents",
3
- "version": "0.9.27",
3
+ "version": "0.9.28",
4
4
  "description": "API for embedding applications in KnowLearning systems.",
5
5
  "main": "node.js",
6
6
  "browser": "browser.js",