@knowlearning/agents 0.4.13 → 0.5.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/embedded.js +0 -3
- package/agents/browser/firebase-auth.js +2 -2
- package/agents/generic.js +151 -100
- 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,76 +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 subscriptions[id]
|
|
256
|
+
catch (error) {
|
|
257
|
+
reject(error)
|
|
234
258
|
}
|
|
235
259
|
})
|
|
236
260
|
}
|
|
237
261
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
+
}
|
|
246
276
|
})
|
|
247
277
|
|
|
248
278
|
promise.watch = fn => {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
watchFn = fn
|
|
255
|
-
})
|
|
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
|
+
|
|
256
284
|
return promise
|
|
257
285
|
}
|
|
258
286
|
|
|
259
287
|
promise.unwatch = async () => {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
delete
|
|
264
|
-
delete watchers[
|
|
288
|
+
removeWatcher(scope, watchFn)
|
|
289
|
+
if (watchers[scope].length === 0) {
|
|
290
|
+
delete subscriptions[keyToSubscriptionId[scope]]
|
|
291
|
+
delete keyToSubscriptionId[scope]
|
|
292
|
+
delete watchers[scope]
|
|
265
293
|
}
|
|
266
294
|
}
|
|
267
295
|
|
|
@@ -271,9 +299,16 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
|
|
|
271
299
|
// TODO: if no data, set up streaming upload
|
|
272
300
|
async function upload(name, type, data, id=uuid()) {
|
|
273
301
|
// TODO: include data size info...
|
|
274
|
-
|
|
302
|
+
const uploadId = uuid()
|
|
303
|
+
initialize(
|
|
304
|
+
uploadId,
|
|
305
|
+
'application/json;type=upload',
|
|
306
|
+
{
|
|
307
|
+
id,
|
|
308
|
+
type
|
|
309
|
+
}
|
|
310
|
+
)
|
|
275
311
|
const { url } = await lastMessageResponse()
|
|
276
|
-
uploads[id].url = url
|
|
277
312
|
|
|
278
313
|
if (data === undefined) return url
|
|
279
314
|
else {
|
|
@@ -287,9 +322,14 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
|
|
|
287
322
|
}
|
|
288
323
|
|
|
289
324
|
async function download(id, passthrough=false) {
|
|
290
|
-
|
|
325
|
+
// TODO: initialize size info
|
|
326
|
+
const downloadId = uuid()
|
|
327
|
+
initialize(
|
|
328
|
+
downloadId,
|
|
329
|
+
'application/json;type=download',
|
|
330
|
+
{ id }
|
|
331
|
+
)
|
|
291
332
|
const { url } = await lastMessageResponse()
|
|
292
|
-
downloads[id].url = url
|
|
293
333
|
|
|
294
334
|
if (passthrough) return url
|
|
295
335
|
else {
|
|
@@ -308,30 +348,25 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
|
|
|
308
348
|
}
|
|
309
349
|
|
|
310
350
|
async function interact(scope, patch) {
|
|
311
|
-
|
|
312
|
-
const response =
|
|
313
|
-
|
|
314
|
-
await environmentPromise
|
|
315
|
-
|
|
316
|
-
const key = `${domain}/${user}/${scope}`
|
|
351
|
+
// TODO: ensure user is owner of scope
|
|
352
|
+
const response = queueMessage({scope, patch})
|
|
317
353
|
|
|
318
|
-
// if we are watching this
|
|
319
|
-
if (states[
|
|
354
|
+
// if we are watching this scope, we want to keep track of last interaction we fired
|
|
355
|
+
if (states[scope] !== undefined) {
|
|
320
356
|
let resolve
|
|
321
|
-
|
|
357
|
+
lastInteractionResponse[scope] = new Promise(r => resolve = r)
|
|
322
358
|
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
const resolveAndUnwatch = (update) => {
|
|
359
|
+
const resolveAndUnwatch = async (update) => {
|
|
360
|
+
const { ii } = await response
|
|
326
361
|
if (update.ii === ii) {
|
|
327
362
|
resolve(ii)
|
|
328
|
-
removeWatcher(
|
|
363
|
+
removeWatcher(scope, resolveAndUnwatch)
|
|
329
364
|
}
|
|
330
365
|
}
|
|
331
366
|
|
|
332
|
-
watchers[
|
|
367
|
+
watchers[scope].push(resolveAndUnwatch)
|
|
333
368
|
|
|
334
|
-
return
|
|
369
|
+
return response
|
|
335
370
|
}
|
|
336
371
|
else {
|
|
337
372
|
const { ii } = await response
|
|
@@ -340,20 +375,36 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
|
|
|
340
375
|
}
|
|
341
376
|
|
|
342
377
|
async function claim(domain) {
|
|
343
|
-
return interact('claims', [{ op: 'add', path: [domain], value: null }])
|
|
378
|
+
return interact('claims', [{ op: 'add', path: ['active', domain], value: null }])
|
|
344
379
|
}
|
|
345
380
|
|
|
346
|
-
function
|
|
347
|
-
|
|
348
|
-
return initialize ? state(scope).then(mp) : mp({})
|
|
381
|
+
function reset(scope) {
|
|
382
|
+
return interact(scope, [{ op: 'remove', path:['active'] }])
|
|
349
383
|
}
|
|
350
384
|
|
|
351
|
-
function
|
|
352
|
-
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
|
+
})
|
|
353
404
|
}
|
|
354
405
|
|
|
355
|
-
function
|
|
356
|
-
return
|
|
406
|
+
async function synced() {
|
|
407
|
+
return new Promise(resolve => syncedPromiseResolutions.push(resolve))
|
|
357
408
|
}
|
|
358
409
|
|
|
359
410
|
function disconnect() {
|
|
@@ -382,9 +433,9 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
|
|
|
382
433
|
interact,
|
|
383
434
|
patch,
|
|
384
435
|
claim,
|
|
385
|
-
mutate,
|
|
386
436
|
reset,
|
|
387
437
|
metadata,
|
|
438
|
+
synced,
|
|
388
439
|
disconnect,
|
|
389
440
|
reconnect,
|
|
390
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.0",
|
|
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
|