@knowlearning/agents 0.4.14 → 0.5.1
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/embedded.js +0 -3
- package/agents/browser/firebase-auth.js +2 -2
- package/agents/generic.js +151 -102
- package/package.json +30 -30
- package/persistence/vuex.js +1 -4
- package/vue/3/component.js +1 -1
|
@@ -117,13 +117,10 @@ export default function EmbeddedAgent() {
|
|
|
117
117
|
|
|
118
118
|
if (data === undefined) return url
|
|
119
119
|
else {
|
|
120
|
-
console.log('UPLOADING TO URL!!!!!!!!!!!!!!', id, url)
|
|
121
120
|
const headers = { 'Content-Type': type }
|
|
122
121
|
const response = await fetch(url, {method: 'PUT', headers, body: data})
|
|
123
122
|
const { ok, statusText } = response
|
|
124
123
|
|
|
125
|
-
console.log(ok, statusText)
|
|
126
|
-
|
|
127
124
|
if (ok) return id
|
|
128
125
|
else throw new Error(statusText)
|
|
129
126
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { initializeApp } from 'firebase/app'
|
|
1
|
+
import { initializeApp } from '@firebase/app'
|
|
2
2
|
import {
|
|
3
3
|
getAuth,
|
|
4
4
|
signOut as firebaseSignOut,
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
signInWithRedirect,
|
|
9
9
|
signInWithEmailAndPassword,
|
|
10
10
|
signInAnonymously
|
|
11
|
-
} from 'firebase/auth'
|
|
11
|
+
} from '@firebase/auth'
|
|
12
12
|
|
|
13
13
|
initializeApp({
|
|
14
14
|
"apiKey": "AIzaSyAxjYuF-2JmXxlXnGgNu2CO4Q41EAtUgrY",
|
package/agents/generic.js
CHANGED
|
@@ -26,7 +26,7 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
|
|
|
26
26
|
const responses = {}
|
|
27
27
|
const watchers = {}
|
|
28
28
|
const keyToSubscriptionId = {}
|
|
29
|
-
const
|
|
29
|
+
const lastInteractionResponse = {}
|
|
30
30
|
const messageQueue = []
|
|
31
31
|
let resolveEnvironment
|
|
32
32
|
let disconnected = false
|
|
@@ -35,23 +35,24 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
|
|
|
35
35
|
const environmentPromise = new Promise(r => resolveEnvironment = r)
|
|
36
36
|
let lastSentSI = -1
|
|
37
37
|
let lastHeartbeat
|
|
38
|
+
const syncedPromiseResolutions = []
|
|
38
39
|
|
|
39
|
-
const subscriptions =
|
|
40
|
-
const
|
|
41
|
-
const downloads = mutate('downloads', false)
|
|
42
|
-
const patches = mutate('patches', false)
|
|
40
|
+
const subscriptions = {}
|
|
41
|
+
const patches = state('patches')
|
|
43
42
|
|
|
44
43
|
log('INITIALIZING AGENT CONNECTION')
|
|
45
44
|
initWS()
|
|
46
45
|
|
|
47
|
-
function subscription(key) {
|
|
48
|
-
return subscriptions[keyToSubscriptionId[key]]
|
|
49
|
-
}
|
|
50
|
-
|
|
51
46
|
function log() {
|
|
52
47
|
if (mode === 'debug') console.log(...arguments)
|
|
53
48
|
}
|
|
54
49
|
|
|
50
|
+
function resolveSyncPromises() {
|
|
51
|
+
while (syncedPromiseResolutions.length) {
|
|
52
|
+
syncedPromiseResolutions.shift()()
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
55
56
|
function removeWatcher(key, fn) {
|
|
56
57
|
const watcherIndex = watchers[key].findIndex(x => x === fn)
|
|
57
58
|
if (watcherIndex > -1) watchers[key].splice(watcherIndex, 1)
|
|
@@ -67,14 +68,15 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
|
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
function lastMessageResponse() { // TODO: handle error responses
|
|
70
|
-
return new Promise(
|
|
71
|
+
return new Promise((resolve, reject) => responses[si].push([resolve, reject]))
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
function queueMessage(message) {
|
|
74
75
|
si += 1
|
|
75
|
-
responses[si] = []
|
|
76
|
+
const promise = new Promise((resolve, reject) => responses[si] = [[resolve, reject]])
|
|
76
77
|
messageQueue.push({...message, si, ts: Date.now()})
|
|
77
78
|
flushMessageQueue()
|
|
79
|
+
return promise
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
function checkHeartbeat() {
|
|
@@ -144,9 +146,16 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
|
|
|
144
146
|
if (message.si !== undefined) {
|
|
145
147
|
if (responses[message.si]) {
|
|
146
148
|
// TODO: remove "acknowledged" messages fromt queue and do accounting with si
|
|
147
|
-
responses[message.si]
|
|
149
|
+
responses[message.si]
|
|
150
|
+
.forEach(([resolve, reject]) => {
|
|
151
|
+
message.error ? reject(message) : resolve(message)
|
|
152
|
+
})
|
|
148
153
|
delete responses[message.si]
|
|
149
154
|
ws.send(JSON.stringify({ack: message.si})) // acknowledgement that we have received the response for this message
|
|
155
|
+
|
|
156
|
+
if (Object.keys(responses).length === 0) {
|
|
157
|
+
resolveSyncPromises()
|
|
158
|
+
}
|
|
150
159
|
}
|
|
151
160
|
else {
|
|
152
161
|
// TODO: consider what to do here... probably want to throw error if in dev env
|
|
@@ -154,22 +163,22 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
|
|
|
154
163
|
}
|
|
155
164
|
}
|
|
156
165
|
else {
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
if (watchers[key]) {
|
|
166
|
+
const sub = subscriptions[keyToSubscriptionId[message.scope]]
|
|
167
|
+
if (watchers[message.scope]) {
|
|
160
168
|
// TODO: debug case where sub is not defined
|
|
161
169
|
if (sub && sub.ii + 1 !== message.ii) {
|
|
162
170
|
// TODO: this seems to be an error that happens with decent regularity (an answer with a given si was skipped/failed)
|
|
163
171
|
// we should be wary of out-of-order ii being passed down (maybe need to wait for older ones???)
|
|
164
172
|
console.warn('UNEXPECTED UPDATE INTERACTION INDEX!!!!!!!!!!! last index in session', sub, ' passed index ', message.ii)
|
|
165
173
|
}
|
|
166
|
-
states[
|
|
174
|
+
states[message.scope] = await states[message.scope]
|
|
167
175
|
|
|
168
176
|
const lastResetPatchIndex = message.patch.findLastIndex(p => p.path.length === 0)
|
|
169
|
-
if (lastResetPatchIndex > -1) states[
|
|
177
|
+
if (lastResetPatchIndex > -1) states[message.scope] = message.patch[lastResetPatchIndex].value
|
|
170
178
|
|
|
171
|
-
|
|
172
|
-
|
|
179
|
+
if (states[message.scope].active === undefined) states[message.scope].active = {}
|
|
180
|
+
applyPatch(states[message.scope], standardJSONPatch(message.patch.slice(lastResetPatchIndex + 1)))
|
|
181
|
+
watchers[message.scope].forEach(fn => fn({ ...message, state: states[message.scope] }))
|
|
173
182
|
if (sub) sub.ii = message.ii
|
|
174
183
|
}
|
|
175
184
|
}
|
|
@@ -192,78 +201,95 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
|
|
|
192
201
|
checkHeartbeat()
|
|
193
202
|
}
|
|
194
203
|
|
|
204
|
+
function initialize(id, type, value) {
|
|
205
|
+
// TODO: collapse into 1 patch and 1 interact call
|
|
206
|
+
interact(id, [{
|
|
207
|
+
op: 'add',
|
|
208
|
+
path: ['active_type'],
|
|
209
|
+
value: type
|
|
210
|
+
}])
|
|
211
|
+
interact(id, [{
|
|
212
|
+
op: 'add',
|
|
213
|
+
path: ['active'],
|
|
214
|
+
value
|
|
215
|
+
}])
|
|
216
|
+
}
|
|
217
|
+
|
|
195
218
|
function environment() { return environmentPromise }
|
|
196
219
|
|
|
197
|
-
function state(scope
|
|
220
|
+
function state(scope) {
|
|
198
221
|
let watchFn
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const promise = new Promise(async resolveState => {
|
|
203
|
-
await environmentPromise // environment not set until first connection
|
|
204
|
-
|
|
205
|
-
if (!u) u = user
|
|
206
|
-
if (!d) d = domain
|
|
207
|
-
|
|
208
|
-
const k = `${d || domain}/${u || user}/${scope}`
|
|
209
|
-
resolveKey(k)
|
|
210
|
-
|
|
211
|
-
if (!keyToSubscriptionId[k]) {
|
|
222
|
+
const promise = new Promise(async (resolveState, rejectState) => {
|
|
223
|
+
if (!keyToSubscriptionId[scope]) {
|
|
212
224
|
const id = uuid()
|
|
213
225
|
|
|
214
|
-
keyToSubscriptionId[
|
|
215
|
-
watchers[
|
|
216
|
-
states[
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
scope,
|
|
223
|
-
|
|
226
|
+
keyToSubscriptionId[scope] = id
|
|
227
|
+
watchers[scope] = []
|
|
228
|
+
states[scope] = new Promise(async (resolve, reject) => {
|
|
229
|
+
|
|
230
|
+
const subscriptionId = uuid()
|
|
231
|
+
initialize(
|
|
232
|
+
subscriptionId,
|
|
233
|
+
'application/json;type=subscription',
|
|
234
|
+
{ session, scope, ii: null }
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
const state = await lastMessageResponse()
|
|
239
|
+
// TODO: replace with editing scope of type
|
|
240
|
+
interact(subscriptionId, [{
|
|
241
|
+
op: 'add',
|
|
242
|
+
path: ['active', 'ii'],
|
|
243
|
+
value: 1 // TODO: use state.ii when is coming down properly...
|
|
244
|
+
}])
|
|
245
|
+
|
|
246
|
+
resolve(state)
|
|
247
|
+
|
|
248
|
+
if (state.ii === -1) {
|
|
249
|
+
// -1 indicates the result is a computed scope, so
|
|
250
|
+
// ii does not apply (we clear out the subscription to not cache value)
|
|
251
|
+
delete states[scope]
|
|
252
|
+
delete keyToSubscriptionId[scope]
|
|
253
|
+
delete subscriptions[id]
|
|
254
|
+
}
|
|
224
255
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
subscriptions[id].ii = ii
|
|
228
|
-
resolve(state)
|
|
229
|
-
if (ii === -1) {
|
|
230
|
-
// -1 indicates the result is a computed scope, so
|
|
231
|
-
// ii does not apply (we clear out the subscription to not cache value)
|
|
232
|
-
delete states[k]
|
|
233
|
-
delete keyToSubscriptionId[k]
|
|
234
|
-
delete subscriptions[id]
|
|
256
|
+
catch (error) {
|
|
257
|
+
reject(error)
|
|
235
258
|
}
|
|
236
259
|
})
|
|
237
260
|
}
|
|
238
261
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
262
|
+
await lastInteractionResponse[scope]
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
const state = structuredClone(await states[scope])
|
|
266
|
+
|
|
267
|
+
resolveState(new MutableProxy(state.active || {}, patch => {
|
|
268
|
+
const activePatch = structuredClone(patch)
|
|
269
|
+
activePatch.forEach(entry => entry.path.unshift('active'))
|
|
270
|
+
interact(scope, activePatch)
|
|
271
|
+
}))
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
rejectState(error)
|
|
275
|
+
}
|
|
247
276
|
})
|
|
248
277
|
|
|
249
278
|
promise.watch = fn => {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
watchFn = fn
|
|
256
|
-
})
|
|
279
|
+
if (watchFn) throw new Error('Only one watcher allowed')
|
|
280
|
+
if (!watchers[scope]) watchers[scope] = []
|
|
281
|
+
watchers[scope].push(fn)
|
|
282
|
+
watchFn = fn
|
|
283
|
+
|
|
257
284
|
return promise
|
|
258
285
|
}
|
|
259
286
|
|
|
260
287
|
promise.unwatch = async () => {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
delete
|
|
265
|
-
delete
|
|
266
|
-
delete watchers[k]
|
|
288
|
+
removeWatcher(scope, watchFn)
|
|
289
|
+
if (watchers[scope].length === 0) {
|
|
290
|
+
delete subscriptions[keyToSubscriptionId[scope]]
|
|
291
|
+
delete keyToSubscriptionId[scope]
|
|
292
|
+
delete watchers[scope]
|
|
267
293
|
}
|
|
268
294
|
}
|
|
269
295
|
|
|
@@ -273,9 +299,16 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
|
|
|
273
299
|
// TODO: if no data, set up streaming upload
|
|
274
300
|
async function upload(name, type, data, id=uuid()) {
|
|
275
301
|
// TODO: include data size info...
|
|
276
|
-
|
|
302
|
+
const uploadId = uuid()
|
|
303
|
+
initialize(
|
|
304
|
+
uploadId,
|
|
305
|
+
'application/json;type=upload',
|
|
306
|
+
{
|
|
307
|
+
id,
|
|
308
|
+
type
|
|
309
|
+
}
|
|
310
|
+
)
|
|
277
311
|
const { url } = await lastMessageResponse()
|
|
278
|
-
uploads[id].url = url
|
|
279
312
|
|
|
280
313
|
if (data === undefined) return url
|
|
281
314
|
else {
|
|
@@ -289,9 +322,14 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
|
|
|
289
322
|
}
|
|
290
323
|
|
|
291
324
|
async function download(id, passthrough=false) {
|
|
292
|
-
|
|
325
|
+
// TODO: initialize size info
|
|
326
|
+
const downloadId = uuid()
|
|
327
|
+
initialize(
|
|
328
|
+
downloadId,
|
|
329
|
+
'application/json;type=download',
|
|
330
|
+
{ id }
|
|
331
|
+
)
|
|
293
332
|
const { url } = await lastMessageResponse()
|
|
294
|
-
downloads[id].url = url
|
|
295
333
|
|
|
296
334
|
if (passthrough) return url
|
|
297
335
|
else {
|
|
@@ -310,30 +348,25 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
|
|
|
310
348
|
}
|
|
311
349
|
|
|
312
350
|
async function interact(scope, patch) {
|
|
313
|
-
|
|
314
|
-
const response =
|
|
315
|
-
|
|
316
|
-
await environmentPromise
|
|
317
|
-
|
|
318
|
-
const key = `${domain}/${user}/${scope}`
|
|
351
|
+
// TODO: ensure user is owner of scope
|
|
352
|
+
const response = queueMessage({scope, patch})
|
|
319
353
|
|
|
320
|
-
// if we are watching this
|
|
321
|
-
if (states[
|
|
354
|
+
// if we are watching this scope, we want to keep track of last interaction we fired
|
|
355
|
+
if (states[scope] !== undefined) {
|
|
322
356
|
let resolve
|
|
323
|
-
|
|
357
|
+
lastInteractionResponse[scope] = new Promise(r => resolve = r)
|
|
324
358
|
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
const resolveAndUnwatch = (update) => {
|
|
359
|
+
const resolveAndUnwatch = async (update) => {
|
|
360
|
+
const { ii } = await response
|
|
328
361
|
if (update.ii === ii) {
|
|
329
362
|
resolve(ii)
|
|
330
|
-
removeWatcher(
|
|
363
|
+
removeWatcher(scope, resolveAndUnwatch)
|
|
331
364
|
}
|
|
332
365
|
}
|
|
333
366
|
|
|
334
|
-
watchers[
|
|
367
|
+
watchers[scope].push(resolveAndUnwatch)
|
|
335
368
|
|
|
336
|
-
return
|
|
369
|
+
return response
|
|
337
370
|
}
|
|
338
371
|
else {
|
|
339
372
|
const { ii } = await response
|
|
@@ -342,20 +375,36 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
|
|
|
342
375
|
}
|
|
343
376
|
|
|
344
377
|
async function claim(domain) {
|
|
345
|
-
return interact('claims', [{ op: 'add', path: [domain], value: null }])
|
|
378
|
+
return interact('claims', [{ op: 'add', path: ['active', domain], value: null }])
|
|
346
379
|
}
|
|
347
380
|
|
|
348
|
-
function
|
|
349
|
-
|
|
350
|
-
return initialize ? state(scope).then(mp) : mp({})
|
|
381
|
+
function reset(scope) {
|
|
382
|
+
return interact(scope, [{ op: 'remove', path:['active'] }])
|
|
351
383
|
}
|
|
352
384
|
|
|
353
|
-
function
|
|
354
|
-
return
|
|
385
|
+
function isValidMetadataMutation({ path, op, value }) {
|
|
386
|
+
return (
|
|
387
|
+
['active_type', 'name'].includes(path[0])
|
|
388
|
+
&& path.length === 1
|
|
389
|
+
&& typeof value === 'string' || op === 'remove'
|
|
390
|
+
)
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
async function metadata(id) {
|
|
394
|
+
await state(id)
|
|
395
|
+
const md = structuredClone(await states[id])
|
|
396
|
+
delete md.active
|
|
397
|
+
return new MutableProxy(md, patch => {
|
|
398
|
+
const activePatch = structuredClone(patch)
|
|
399
|
+
activePatch.forEach(entry => {
|
|
400
|
+
if (!isValidMetadataMutation(entry)) throw new Error('You may only modify the type or name for a scope\'s metadata')
|
|
401
|
+
})
|
|
402
|
+
interact(id, activePatch)
|
|
403
|
+
})
|
|
355
404
|
}
|
|
356
405
|
|
|
357
|
-
function
|
|
358
|
-
return
|
|
406
|
+
async function synced() {
|
|
407
|
+
return new Promise(resolve => syncedPromiseResolutions.push(resolve))
|
|
359
408
|
}
|
|
360
409
|
|
|
361
410
|
function disconnect() {
|
|
@@ -384,9 +433,9 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
|
|
|
384
433
|
interact,
|
|
385
434
|
patch,
|
|
386
435
|
claim,
|
|
387
|
-
mutate,
|
|
388
436
|
reset,
|
|
389
437
|
metadata,
|
|
438
|
+
synced,
|
|
390
439
|
disconnect,
|
|
391
440
|
reconnect,
|
|
392
441
|
debug
|
package/package.json
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
2
|
+
"name": "@knowlearning/agents",
|
|
3
|
+
"version": "0.5.1",
|
|
4
|
+
"description": "API for embedding applications in KnowLearning systems.",
|
|
5
|
+
"main": "node.js",
|
|
6
|
+
"browser": "browser.js",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"directories": {
|
|
9
|
+
"example": "examples",
|
|
10
|
+
"test": "test"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/knowlearning/core.git"
|
|
18
|
+
},
|
|
19
|
+
"author": "KnowLearning",
|
|
20
|
+
"license": "MPL-2.0",
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/knowlearning/core/issues"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/knowlearning/core#readme",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@firebase/app": "^0.9.14",
|
|
27
|
+
"@firebase/auth": "^1.0.0",
|
|
28
|
+
"fast-json-patch": "^3.1.1",
|
|
29
|
+
"node-fetch": "^3.3.1",
|
|
30
|
+
"uuid": "^8.3.2"
|
|
31
31
|
}
|
|
32
|
-
|
|
32
|
+
}
|
package/persistence/vuex.js
CHANGED
|
@@ -13,10 +13,7 @@ export default async function (storeDefinition) {
|
|
|
13
13
|
const stateAttachedStore = await attachModuleState(savedState, storeDefinition, scopedPaths)
|
|
14
14
|
const s = stateAttachedStore.state
|
|
15
15
|
const originalState = s instanceof Function ? s() : s
|
|
16
|
-
const handlePatch = patch =>
|
|
17
|
-
console.log('PATCHING STATE', patch)
|
|
18
|
-
Agent.interact(scope, patch)
|
|
19
|
-
}
|
|
16
|
+
const handlePatch = patch => Agent.interact(scope, patch)
|
|
20
17
|
|
|
21
18
|
if (savedState === null) handlePatch([{ op: 'add', path: [], value: copy(originalState) }])
|
|
22
19
|
|
package/vue/3/component.js
CHANGED
|
@@ -12,7 +12,7 @@ export default function (module, scope) {
|
|
|
12
12
|
const component = module.default ? { ...module.default } : { ...module } // copy component since we will mess with mounted and data functions
|
|
13
13
|
|
|
14
14
|
if (!component.ephemeral) {
|
|
15
|
-
const state = await Agent.
|
|
15
|
+
const state = await Agent.state(scope)
|
|
16
16
|
|
|
17
17
|
if (component.data) {
|
|
18
18
|
const origDataFn = component.data
|