@knowlearning/agents 0.9.26 → 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.
- package/agents/browser/embedded.js +49 -17
- package/agents/browser/initialize.js +14 -12
- package/agents/browser/root.js +2 -2
- package/agents/generic/index.js +4 -2
- package/agents/generic/message-queue.js +15 -8
- package/agents/generic/state.js +3 -3
- package/agents/generic/watch.js +14 -22
- package/package.json +1 -1
|
@@ -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,18 +38,33 @@ 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')
|
|
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)
|
|
45
51
|
else resolve(data.response)
|
|
46
52
|
}
|
|
47
53
|
else if (data.ii !== undefined) {
|
|
48
|
-
const { scope, user } = data
|
|
49
|
-
const { auth } = await environment()
|
|
50
|
-
const
|
|
51
|
-
|
|
54
|
+
const { scope, user, domain } = data
|
|
55
|
+
const { auth, domain:rootDomain } = await environment()
|
|
56
|
+
const d = !domain || domain === rootDomain ? '' : domain
|
|
57
|
+
const u = !user || auth.user === user ? '' : user
|
|
58
|
+
const key = isUUID(scope) ? scope : `${d}/${u}/${scope}`
|
|
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
|
+
}
|
|
52
68
|
}
|
|
53
69
|
})
|
|
54
70
|
|
|
@@ -57,9 +73,11 @@ export default function EmbeddedAgent() {
|
|
|
57
73
|
}
|
|
58
74
|
|
|
59
75
|
function create({ id=uuid(), active_type, active }) {
|
|
60
|
-
|
|
61
|
-
interact(id, [
|
|
62
|
-
|
|
76
|
+
if (!active_type) active_type = 'application/json'
|
|
77
|
+
interact(id, [
|
|
78
|
+
{ op: 'add', path: ['active_type'], value: active_type },
|
|
79
|
+
{ op: 'add', path: ['active'], value: active }
|
|
80
|
+
])
|
|
63
81
|
return id
|
|
64
82
|
}
|
|
65
83
|
|
|
@@ -78,13 +96,26 @@ export default function EmbeddedAgent() {
|
|
|
78
96
|
await tag(tag_type, target)
|
|
79
97
|
}
|
|
80
98
|
|
|
81
|
-
function watch(
|
|
82
|
-
tagIfNotYetTaggedInSession('subscribed',
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
99
|
+
function watch(scope, fn, user, domain) {
|
|
100
|
+
tagIfNotYetTaggedInSession('subscribed', scope)
|
|
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)
|
|
88
119
|
}
|
|
89
120
|
|
|
90
121
|
async function patch(root, scopes) {
|
|
@@ -92,14 +123,15 @@ export default function EmbeddedAgent() {
|
|
|
92
123
|
return send({ type: 'patch', root, scopes })
|
|
93
124
|
}
|
|
94
125
|
|
|
95
|
-
async function state(scope) {
|
|
126
|
+
async function state(scope, user, domain) {
|
|
96
127
|
if (scope === undefined) {
|
|
97
128
|
const { context } = await environment()
|
|
98
129
|
scope = JSON.stringify(context)
|
|
99
130
|
}
|
|
100
131
|
tagIfNotYetTaggedInSession('subscribed', scope)
|
|
101
|
-
const startState = await send({ type: 'state', scope })
|
|
132
|
+
const startState = await send({ type: 'state', scope, user, domain })
|
|
102
133
|
return new MutableProxy(startState, patch => {
|
|
134
|
+
// TODO: reject updates if user is not owner
|
|
103
135
|
const activePatch = structuredClone(patch)
|
|
104
136
|
activePatch.forEach(entry => entry.path.unshift('active'))
|
|
105
137
|
interact(scope, activePatch)
|
|
@@ -4,18 +4,20 @@ import { v1 as uuid, validate as validateUUID } from 'uuid'
|
|
|
4
4
|
|
|
5
5
|
let Agent
|
|
6
6
|
|
|
7
|
-
export default function browserAgent() {
|
|
8
|
-
if (Agent) return Agent
|
|
7
|
+
export default function browserAgent(options={}) {
|
|
8
|
+
if (Agent && !options.unique) return Agent
|
|
9
9
|
|
|
10
10
|
let embedded
|
|
11
11
|
|
|
12
12
|
try { embedded = window.self !== window.top }
|
|
13
13
|
catch (e) { embedded = true }
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
const newAgent = embedded && !options.root ? EmbeddedAgent() : RootAgent(options)
|
|
16
|
+
newAgent.embed = embed
|
|
17
17
|
|
|
18
|
-
|
|
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
|
-
|
|
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
|
|
@@ -80,13 +81,13 @@ function embed(environment, iframe) {
|
|
|
80
81
|
sendDown(await Agent.tag(tag_type, target, prependedContext))
|
|
81
82
|
}
|
|
82
83
|
else if (type === 'state') {
|
|
83
|
-
const { scope, user } = message
|
|
84
|
+
const { scope, user, domain } = message
|
|
84
85
|
|
|
85
|
-
const statePromise = Agent.state(scope, user)
|
|
86
|
+
const statePromise = Agent.state(scope, user, domain)
|
|
86
87
|
|
|
87
|
-
const key = `${user || ''}/${scope}`
|
|
88
|
+
const key = `${ domain || ''}/${user || ''}/${scope}`
|
|
88
89
|
if (!watchers[key]) {
|
|
89
|
-
watchers[key] = Agent.watch(scope, postMessage, user)
|
|
90
|
+
watchers[key] = Agent.watch(scope, postMessage, user, domain)
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
if (listeners.state) listeners.state({ scope })
|
|
@@ -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
|
}
|
package/agents/browser/root.js
CHANGED
|
@@ -8,13 +8,13 @@ const REMOTE_HOST = 'api.knowlearning.systems'
|
|
|
8
8
|
|
|
9
9
|
function isLocal() { return localStorage.getItem('api') === 'local' }
|
|
10
10
|
|
|
11
|
-
export default
|
|
11
|
+
export default options => {
|
|
12
12
|
const { host, protocol } = window.location
|
|
13
13
|
|
|
14
14
|
const agent = GenericAgent({
|
|
15
15
|
host: isLocal() ? DEVELOPMENT_HOST : REMOTE_HOST,
|
|
16
16
|
protocol: protocol === 'https:' ? 'wss' : 'ws',
|
|
17
|
-
token: getToken,
|
|
17
|
+
token: options.getToken || getToken,
|
|
18
18
|
WebSocket,
|
|
19
19
|
uuid,
|
|
20
20
|
fetch,
|
package/agents/generic/index.js
CHANGED
|
@@ -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
|
|
|
@@ -51,7 +52,7 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
|
|
|
51
52
|
|
|
52
53
|
async function environment() { return { ...(await environmentPromise), context: [] } }
|
|
53
54
|
|
|
54
|
-
function state(scope, user) { return stateImplementation(scope, user, internalReferences) }
|
|
55
|
+
function state(scope, user, domain) { return stateImplementation(scope, user, domain, internalReferences) }
|
|
55
56
|
|
|
56
57
|
function download(id) { return downloadImplementation(id, internalReferences) }
|
|
57
58
|
|
|
@@ -60,6 +61,7 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
|
|
|
60
61
|
function log() { if (mode === 'debug') console.log(...arguments) }
|
|
61
62
|
|
|
62
63
|
function create({ id=uuid(), active_type, active, name }) {
|
|
64
|
+
if (!active_type) active_type = 'application/json'
|
|
63
65
|
const patch = [
|
|
64
66
|
{ op: 'add', path: ['active_type'], value: active_type },
|
|
65
67
|
{ op: 'add', path: ['active'], value: active }
|
|
@@ -110,7 +112,7 @@ export default function Agent({ host, token, WebSocket, protocol='ws', uuid, fet
|
|
|
110
112
|
const response = queueMessage({scope, patch})
|
|
111
113
|
|
|
112
114
|
// if we are watching this scope, we want to keep track of last interaction we fired
|
|
113
|
-
const qualifiedScope = isUUID(scope) ? scope :
|
|
115
|
+
const qualifiedScope = isUUID(scope) ? scope : `//${scope}`
|
|
114
116
|
if (states[qualifiedScope] !== undefined) {
|
|
115
117
|
let resolve
|
|
116
118
|
lastInteractionResponse[qualifiedScope] = new Promise(r => resolve = r)
|
|
@@ -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
|
|
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
|
-
|
|
78
|
-
|
|
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
|
-
|
|
165
|
+
resolveSyncPromises()
|
|
166
166
|
}
|
|
167
167
|
else {
|
|
168
168
|
// TODO: consider what to do here... probably want to throw error if in dev env
|
|
@@ -170,7 +170,10 @@ export default function messageQueue(setEnvironment, { token, protocol, host, We
|
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
172
|
else {
|
|
173
|
-
const
|
|
173
|
+
const d = message.domain === window.location.host ? '' : message.domain
|
|
174
|
+
const u = message.user === user ? '' : message.user
|
|
175
|
+
const s = message.scope
|
|
176
|
+
const qualifiedScope = isUUID(s) ? s : `${d}/${u}/${s}`
|
|
174
177
|
if (watchers[qualifiedScope]) {
|
|
175
178
|
states[qualifiedScope] = await states[qualifiedScope]
|
|
176
179
|
|
|
@@ -205,7 +208,11 @@ export default function messageQueue(setEnvironment, { token, protocol, host, We
|
|
|
205
208
|
checkHeartbeat()
|
|
206
209
|
}
|
|
207
210
|
|
|
208
|
-
async function synced() {
|
|
211
|
+
async function synced() {
|
|
212
|
+
const syncPromise = new Promise(resolve => outstandingSyncPromises.push({ si: lastSentSI, resolve }))
|
|
213
|
+
resolveSyncPromises()
|
|
214
|
+
return syncPromise
|
|
215
|
+
}
|
|
209
216
|
|
|
210
217
|
function lastMessageResponse() { return new Promise((res, rej) => responses[si].push([res, rej])) }
|
|
211
218
|
|
package/agents/generic/state.js
CHANGED
|
@@ -3,12 +3,12 @@ import MutableProxy from '../../persistence/json.js'
|
|
|
3
3
|
|
|
4
4
|
const SUBSCRIPTION_TYPE = 'application/json;type=subscription'
|
|
5
5
|
|
|
6
|
-
export default function(scope='[]', user, { keyToSubscriptionId, watchers, states, create, environment, lastMessageResponse, lastInteractionResponse, tagIfNotYetTaggedInSession, interact }) {
|
|
6
|
+
export default function(scope='[]', user, domain, { keyToSubscriptionId, watchers, states, create, environment, lastMessageResponse, lastInteractionResponse, tagIfNotYetTaggedInSession, interact }) {
|
|
7
7
|
let resolveMetadataPromise
|
|
8
8
|
let metadataPromise = new Promise(resolve => resolveMetadataPromise = resolve)
|
|
9
9
|
|
|
10
10
|
const statePromise = new Promise(async (resolveState, rejectState) => {
|
|
11
|
-
const qualifiedScope = isUUID(scope) ? scope : `${user || ''}/${scope}`
|
|
11
|
+
const qualifiedScope = isUUID(scope) ? scope : `${domain || ''}/${user || ''}/${scope}`
|
|
12
12
|
if (!keyToSubscriptionId[qualifiedScope]) {
|
|
13
13
|
const id = uuid()
|
|
14
14
|
|
|
@@ -19,7 +19,7 @@ export default function(scope='[]', user, { keyToSubscriptionId, watchers, state
|
|
|
19
19
|
create({
|
|
20
20
|
id,
|
|
21
21
|
active_type: SUBSCRIPTION_TYPE,
|
|
22
|
-
active: { session, scope, user, ii: null, initialized: Date.now() },
|
|
22
|
+
active: { session, scope, user, domain, ii: null, initialized: Date.now() },
|
|
23
23
|
})
|
|
24
24
|
|
|
25
25
|
try {
|
package/agents/generic/watch.js
CHANGED
|
@@ -1,39 +1,31 @@
|
|
|
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
|
-
function watch(scope=DEFAULT_SCOPE_NAME, fn, user) {
|
|
6
|
-
if (Array.isArray(scope)) return watchResolution(scope, fn, user)
|
|
5
|
+
function watch(scope=DEFAULT_SCOPE_NAME, fn, user, domain) {
|
|
6
|
+
if (Array.isArray(scope)) return watchResolution(scope, fn, user, domain)
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
function cb(update) {
|
|
11
|
-
if (initialSent) fn(update)
|
|
12
|
-
else queue.push(update)
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const statePromise = state(scope, user)
|
|
16
|
-
const qualifiedScope = isUUID(scope) ? scope : `${user || ''}/${scope}`
|
|
17
|
-
|
|
18
|
-
if (!watchers[qualifiedScope]) watchers[qualifiedScope] = []
|
|
19
|
-
watchers[qualifiedScope].push(cb)
|
|
8
|
+
const statePromise = state(scope, user, domain)
|
|
9
|
+
const qualifiedScope = isUUID(scope) ? scope : `${domain || ''}/${user || ''}/${scope}`
|
|
20
10
|
|
|
21
|
-
metadata(scope, user)
|
|
11
|
+
metadata(scope, user, domain)
|
|
22
12
|
.then(async ({ ii }) => {
|
|
23
13
|
fn({
|
|
24
14
|
scope,
|
|
15
|
+
user,
|
|
16
|
+
domain,
|
|
25
17
|
state: await statePromise,
|
|
26
18
|
patch: null,
|
|
27
19
|
ii
|
|
28
20
|
})
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
if (!watchers[qualifiedScope]) watchers[qualifiedScope] = []
|
|
22
|
+
watchers[qualifiedScope].push(fn)
|
|
31
23
|
})
|
|
32
24
|
|
|
33
|
-
return () => removeWatcher(qualifiedScope,
|
|
25
|
+
return () => removeWatcher(qualifiedScope, fn)
|
|
34
26
|
}
|
|
35
27
|
|
|
36
|
-
function watchResolution(path, callback, user) {
|
|
28
|
+
function watchResolution(path, callback, user, domain) {
|
|
37
29
|
const id = path[0]
|
|
38
30
|
const references = path.slice(1)
|
|
39
31
|
let unwatchDeeper = () => {}
|
|
@@ -57,13 +49,13 @@ export default function({ metadata, state, watchers }) {
|
|
|
57
49
|
index === references.length - 1
|
|
58
50
|
) callback(value)
|
|
59
51
|
else if (isUUID(value)) {
|
|
60
|
-
unwatchDeeper = watchResolution([value, ...references.slice(index + 1)], callback, user)
|
|
52
|
+
unwatchDeeper = watchResolution([value, ...references.slice(index + 1)], callback, user, domain)
|
|
61
53
|
return
|
|
62
54
|
}
|
|
63
55
|
}
|
|
64
56
|
}
|
|
65
57
|
|
|
66
|
-
const unwatch = watch(id, watchCallback, user)
|
|
58
|
+
const unwatch = watch(id, watchCallback, user, domain)
|
|
67
59
|
|
|
68
60
|
return () => {
|
|
69
61
|
unwatch()
|