@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.
- package/agents/browser/embed.js +20 -21
- package/agents/generic.js +44 -38
- package/package.json +1 -1
- package/vue/3/content.vue +2 -6
package/agents/browser/embed.js
CHANGED
|
@@ -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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
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
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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',
|
|
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
|
-
|
|
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
|
-
|
|
177
|
-
|
|
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
|
-
|
|
211
|
+
subscriptions[k] = null
|
|
207
212
|
const { ii, state } = await lastMessageResponse()
|
|
208
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
260
|
+
uploads[id] = { url: null, sent: 0, name, type }
|
|
256
261
|
const { url } = await lastMessageResponse()
|
|
257
|
-
|
|
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
|
-
|
|
276
|
+
downloads[id] = { url: null, size: null, sent: 0 }
|
|
272
277
|
const { url } = await lastMessageResponse()
|
|
273
|
-
|
|
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
|
-
|
|
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
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
|
|
24
|
+
const { id } = this
|
|
29
25
|
this.iframe = iframe
|
|
30
|
-
this.embedding = Agent.embed({
|
|
26
|
+
this.embedding = Agent.embed({ id }, iframe)
|
|
31
27
|
|
|
32
28
|
/*
|
|
33
29
|
const { handle } = this.embedding
|