@knowlearning/agents 0.9.177 → 0.9.178
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 +7 -227
- package/agents/embedded.js +228 -0
- package/package.json +1 -1
|
@@ -1,230 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import PatchProxy from '@knowlearning/patch-proxy'
|
|
3
|
-
import watchImplementation from '../watch.js'
|
|
1
|
+
import EmbeddedAgent from '../embedded.js'
|
|
4
2
|
|
|
5
|
-
export default
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
const responses = {}
|
|
10
|
-
const watchers = {}
|
|
11
|
-
const sentUpdates = {}
|
|
3
|
+
export default () => {
|
|
4
|
+
// default to window opener if present
|
|
5
|
+
const parent = window.opener ? window.opener : window.parent
|
|
6
|
+
const agent = EmbeddedAgent(parent)
|
|
12
7
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
async function send(message) {
|
|
16
|
-
const requestId = message.requestId || uuid()
|
|
17
|
-
|
|
18
|
-
messageIndex += 1
|
|
19
|
-
// default to window opener if present
|
|
20
|
-
const upstream = window.opener ? window.opener : window.parent
|
|
21
|
-
try {
|
|
22
|
-
upstream
|
|
23
|
-
.postMessage({
|
|
24
|
-
...message,
|
|
25
|
-
session: await session,
|
|
26
|
-
requestId,
|
|
27
|
-
index: messageIndex
|
|
28
|
-
}, '*')
|
|
29
|
-
return new Promise((resolve, reject) => {
|
|
30
|
-
responses[requestId] = { resolve, reject }
|
|
31
|
-
})
|
|
32
|
-
}
|
|
33
|
-
catch (error) {
|
|
34
|
-
console.log('ERROR POSTING MESSAGE UP', message, error)
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
let sessionResolved = false
|
|
39
|
-
addEventListener('message', async ({ data }) => {
|
|
40
|
-
if (data.type === 'setup' && !sessionResolved) {
|
|
41
|
-
sessionResolved = true
|
|
42
|
-
resolveSession(data.session)
|
|
43
|
-
}
|
|
44
|
-
else if (!sessionResolved || data.session !== await session) return
|
|
45
|
-
else if (responses[data.requestId]) {
|
|
46
|
-
const { resolve, reject } = responses[data.requestId]
|
|
47
|
-
if (data.error) reject(data.error)
|
|
48
|
-
else resolve(data.response)
|
|
49
|
-
}
|
|
50
|
-
else if (data.ii !== undefined) {
|
|
51
|
-
const { scope, user, domain } = data
|
|
52
|
-
const { auth, domain:rootDomain } = await environment()
|
|
53
|
-
const d = !domain || domain === rootDomain ? '' : domain
|
|
54
|
-
const u = !user || auth.user === user ? '' : user
|
|
55
|
-
const key = isUUID(scope) ? scope : `${d}/${u}/${scope}`
|
|
56
|
-
|
|
57
|
-
const sendUpdate = () => {
|
|
58
|
-
sentUpdates[key] = data.ii
|
|
59
|
-
watchers[key].forEach(fn => fn(data))
|
|
60
|
-
}
|
|
61
|
-
if (watchers[key]) {
|
|
62
|
-
if (sentUpdates[key] === undefined || sentUpdates[key] + 1 === data.ii) {
|
|
63
|
-
sendUpdate()
|
|
64
|
-
} else if (data.ii === sentUpdates[key]) {
|
|
65
|
-
//console.warn('Repeated update for', key, data, sentUpdates[key])
|
|
66
|
-
} else if (data.ii < sentUpdates[key]) {
|
|
67
|
-
//console.warn('Out of order update, from past', key, JSON.stringify(data, null, 4), sentUpdates[key])
|
|
68
|
-
} else {
|
|
69
|
-
//console.warn('Out of order update, fast forward', key, JSON.stringify(data, null, 4), sentUpdates[key])
|
|
70
|
-
sendUpdate()
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
let variables
|
|
77
|
-
|
|
78
|
-
async function environment(user) {
|
|
79
|
-
const response = await send({ type: 'environment', user })
|
|
80
|
-
// keep copy on initialize symantics for environment variables
|
|
81
|
-
if (!variables && !user) variables = response.variables
|
|
82
|
-
return { ...response, variables }
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function create({ id=uuid(), active_type, active }) {
|
|
86
|
-
if (!active_type) active_type = 'application/json'
|
|
87
|
-
interact(id, [
|
|
88
|
-
{ op: 'add', path: ['active_type'], value: active_type },
|
|
89
|
-
{ op: 'add', path: ['active'], value: active }
|
|
90
|
-
])
|
|
91
|
-
return id
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async function patch(root, scopes) {
|
|
95
|
-
// TODO: consider watch function added to return to receive progress
|
|
96
|
-
return send({ type: 'patch', root, scopes })
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
async function state(scope, user, domain) {
|
|
100
|
-
if (scope === undefined) {
|
|
101
|
-
const { context } = await environment()
|
|
102
|
-
scope = JSON.stringify(context)
|
|
103
|
-
}
|
|
104
|
-
const startState = await send({ type: 'state', scope, user, domain })
|
|
105
|
-
return new PatchProxy(startState, patch => {
|
|
106
|
-
// TODO: reject updates if user is not owner
|
|
107
|
-
const activePatch = structuredClone(patch)
|
|
108
|
-
activePatch.forEach(entry => entry.path.unshift('active'))
|
|
109
|
-
interact(scope, activePatch)
|
|
110
|
-
})
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function reset(scope) {
|
|
114
|
-
return interact(scope, [{ op: 'add', path:['active'], value: null }])
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function interact(scope, patch, _, context) {
|
|
118
|
-
return send({ type: 'interact', scope, patch, context })
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
async function upload(info) {
|
|
122
|
-
let { name, type, data, id=uuid() } = info || {}
|
|
123
|
-
|
|
124
|
-
const url = await send({ type: 'upload', info: { name, type, id } })
|
|
125
|
-
|
|
126
|
-
if (data === undefined) return url
|
|
127
|
-
else {
|
|
128
|
-
const headers = { 'Content-Type': type }
|
|
129
|
-
const response = await fetch(url, {method: 'PUT', headers, body: data})
|
|
130
|
-
const { ok, statusText } = response
|
|
131
|
-
|
|
132
|
-
if (ok) return id
|
|
133
|
-
else throw new Error(statusText)
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function download(id) {
|
|
138
|
-
let mode = 'fetch'
|
|
139
|
-
const promise = new Promise(async (resolve, reject) => {
|
|
140
|
-
const url = await send({ type: 'download', id })
|
|
141
|
-
|
|
142
|
-
await new Promise(r => setTimeout(r))
|
|
143
|
-
if (mode === 'url') resolve(url)
|
|
144
|
-
else if (mode === 'fetch') {
|
|
145
|
-
const response = await fetch(url)
|
|
146
|
-
const { ok, statusText } = response
|
|
147
|
-
|
|
148
|
-
if (ok) resolve(response)
|
|
149
|
-
else reject(statusText)
|
|
150
|
-
}
|
|
151
|
-
else if (mode === 'direct') {
|
|
152
|
-
// TODO: use browser progress UX instead of downloading all into memory first
|
|
153
|
-
const res = await download(id)
|
|
154
|
-
const { name } = await metadata(id)
|
|
155
|
-
const type = res.headers.get('Content-Type')
|
|
156
|
-
const blob = new Blob([ await res.blob() ], { type })
|
|
157
|
-
const url = window.URL.createObjectURL(blob)
|
|
158
|
-
const a = document.createElement('a')
|
|
159
|
-
a.style.display = 'none'
|
|
160
|
-
a.href = url
|
|
161
|
-
a.download = name
|
|
162
|
-
document.body.appendChild(a)
|
|
163
|
-
a.click()
|
|
164
|
-
window.URL.revokeObjectURL(url)
|
|
165
|
-
resolve()
|
|
166
|
-
}
|
|
167
|
-
})
|
|
168
|
-
promise.direct = () => {
|
|
169
|
-
mode = 'direct'
|
|
170
|
-
return promise
|
|
171
|
-
}
|
|
172
|
-
promise.url = () => {
|
|
173
|
-
mode = 'url'
|
|
174
|
-
return promise
|
|
175
|
-
}
|
|
176
|
-
return promise
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function isValidMetadataMutation({ path, op, value }) {
|
|
180
|
-
return (
|
|
181
|
-
['active_type', 'name'].includes(path[0])
|
|
182
|
-
&& path.length === 1
|
|
183
|
-
&& typeof value === 'string' || op === 'remove'
|
|
184
|
-
)
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
async function metadata(scope, user, domain) {
|
|
188
|
-
const md = await send({ type: 'metadata', scope, user, domain })
|
|
189
|
-
return new PatchProxy(md, patch => {
|
|
190
|
-
const activePatch = structuredClone(patch)
|
|
191
|
-
activePatch.forEach(entry => {
|
|
192
|
-
if (!isValidMetadataMutation(entry)) throw new Error('You may only modify the type or name for a scope\'s metadata')
|
|
193
|
-
})
|
|
194
|
-
interact(scope, activePatch)
|
|
195
|
-
})
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function login(provider, username, password) {
|
|
199
|
-
return send({ type: 'login', provider, username, password })
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function query(query, params, domain, context=[]) { return send({ type: 'query', query, params, domain, context }) }
|
|
203
|
-
function logout() { return send({ type: 'logout' }) }
|
|
204
|
-
function disconnect() { return send({ type: 'disconnect' }) }
|
|
205
|
-
function reconnect() { return send({ type: 'reconnect' }) }
|
|
206
|
-
function synced() { return send({ type: 'synced' }) }
|
|
207
|
-
function close(info) { return send({ type: 'close', info }) }
|
|
208
|
-
|
|
209
|
-
return {
|
|
210
|
-
embedded: true,
|
|
211
|
-
uuid,
|
|
212
|
-
environment,
|
|
213
|
-
login,
|
|
214
|
-
logout,
|
|
215
|
-
create,
|
|
216
|
-
state,
|
|
217
|
-
watch,
|
|
218
|
-
upload,
|
|
219
|
-
download,
|
|
220
|
-
interact,
|
|
221
|
-
patch,
|
|
222
|
-
reset,
|
|
223
|
-
metadata,
|
|
224
|
-
disconnect,
|
|
225
|
-
reconnect,
|
|
226
|
-
synced,
|
|
227
|
-
close,
|
|
228
|
-
query
|
|
229
|
-
}
|
|
8
|
+
return agent
|
|
230
9
|
}
|
|
10
|
+
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { validate as isUUID, v1 as uuid } from 'uuid'
|
|
2
|
+
import PatchProxy from '@knowlearning/patch-proxy'
|
|
3
|
+
import watchImplementation from './watch.js'
|
|
4
|
+
|
|
5
|
+
export default function EmbeddedAgent(parent) {
|
|
6
|
+
let messageIndex = 0
|
|
7
|
+
let resolveSession
|
|
8
|
+
const session = new Promise(r => resolveSession = r)
|
|
9
|
+
const responses = {}
|
|
10
|
+
const watchers = {}
|
|
11
|
+
const sentUpdates = {}
|
|
12
|
+
|
|
13
|
+
const [ watch, removeWatcher ] = watchImplementation({ metadata, state, watchers, synced, sentUpdates, environment })
|
|
14
|
+
|
|
15
|
+
async function send(message) {
|
|
16
|
+
const requestId = message.requestId || uuid()
|
|
17
|
+
|
|
18
|
+
messageIndex += 1
|
|
19
|
+
try {
|
|
20
|
+
parent
|
|
21
|
+
.postMessage({
|
|
22
|
+
...message,
|
|
23
|
+
session: await session,
|
|
24
|
+
requestId,
|
|
25
|
+
index: messageIndex
|
|
26
|
+
}, '*')
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
responses[requestId] = { resolve, reject }
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
console.log('ERROR POSTING MESSAGE UP', message, error)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let sessionResolved = false
|
|
37
|
+
addEventListener('message', async ({ data }) => {
|
|
38
|
+
if (data.type === 'setup' && !sessionResolved) {
|
|
39
|
+
sessionResolved = true
|
|
40
|
+
resolveSession(data.session)
|
|
41
|
+
}
|
|
42
|
+
else if (!sessionResolved || data.session !== await session) return
|
|
43
|
+
else if (responses[data.requestId]) {
|
|
44
|
+
const { resolve, reject } = responses[data.requestId]
|
|
45
|
+
if (data.error) reject(data.error)
|
|
46
|
+
else resolve(data.response)
|
|
47
|
+
}
|
|
48
|
+
else if (data.ii !== undefined) {
|
|
49
|
+
const { scope, user, domain } = data
|
|
50
|
+
const { auth, domain:rootDomain } = await environment()
|
|
51
|
+
const d = !domain || domain === rootDomain ? '' : domain
|
|
52
|
+
const u = !user || auth.user === user ? '' : user
|
|
53
|
+
const key = isUUID(scope) ? scope : `${d}/${u}/${scope}`
|
|
54
|
+
|
|
55
|
+
const sendUpdate = () => {
|
|
56
|
+
sentUpdates[key] = data.ii
|
|
57
|
+
watchers[key].forEach(fn => fn(data))
|
|
58
|
+
}
|
|
59
|
+
if (watchers[key]) {
|
|
60
|
+
if (sentUpdates[key] === undefined || sentUpdates[key] + 1 === data.ii) {
|
|
61
|
+
sendUpdate()
|
|
62
|
+
} else if (data.ii === sentUpdates[key]) {
|
|
63
|
+
//console.warn('Repeated update for', key, data, sentUpdates[key])
|
|
64
|
+
} else if (data.ii < sentUpdates[key]) {
|
|
65
|
+
//console.warn('Out of order update, from past', key, JSON.stringify(data, null, 4), sentUpdates[key])
|
|
66
|
+
} else {
|
|
67
|
+
//console.warn('Out of order update, fast forward', key, JSON.stringify(data, null, 4), sentUpdates[key])
|
|
68
|
+
sendUpdate()
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
let variables
|
|
75
|
+
|
|
76
|
+
async function environment(user) {
|
|
77
|
+
const response = await send({ type: 'environment', user })
|
|
78
|
+
// keep copy on initialize symantics for environment variables
|
|
79
|
+
if (!variables && !user) variables = response.variables
|
|
80
|
+
return { ...response, variables }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function create({ id=uuid(), active_type, active }) {
|
|
84
|
+
if (!active_type) active_type = 'application/json'
|
|
85
|
+
interact(id, [
|
|
86
|
+
{ op: 'add', path: ['active_type'], value: active_type },
|
|
87
|
+
{ op: 'add', path: ['active'], value: active }
|
|
88
|
+
])
|
|
89
|
+
return id
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function patch(root, scopes) {
|
|
93
|
+
// TODO: consider watch function added to return to receive progress
|
|
94
|
+
return send({ type: 'patch', root, scopes })
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function state(scope, user, domain) {
|
|
98
|
+
if (scope === undefined) {
|
|
99
|
+
const { context } = await environment()
|
|
100
|
+
scope = JSON.stringify(context)
|
|
101
|
+
}
|
|
102
|
+
const startState = await send({ type: 'state', scope, user, domain })
|
|
103
|
+
return new PatchProxy(startState, patch => {
|
|
104
|
+
// TODO: reject updates if user is not owner
|
|
105
|
+
const activePatch = structuredClone(patch)
|
|
106
|
+
activePatch.forEach(entry => entry.path.unshift('active'))
|
|
107
|
+
interact(scope, activePatch)
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function reset(scope) {
|
|
112
|
+
return interact(scope, [{ op: 'add', path:['active'], value: null }])
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function interact(scope, patch, _, context) {
|
|
116
|
+
return send({ type: 'interact', scope, patch, context })
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function upload(info) {
|
|
120
|
+
let { name, type, data, id=uuid() } = info || {}
|
|
121
|
+
|
|
122
|
+
const url = await send({ type: 'upload', info: { name, type, id } })
|
|
123
|
+
|
|
124
|
+
if (data === undefined) return url
|
|
125
|
+
else {
|
|
126
|
+
const headers = { 'Content-Type': type }
|
|
127
|
+
const response = await fetch(url, {method: 'PUT', headers, body: data})
|
|
128
|
+
const { ok, statusText } = response
|
|
129
|
+
|
|
130
|
+
if (ok) return id
|
|
131
|
+
else throw new Error(statusText)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function download(id) {
|
|
136
|
+
let mode = 'fetch'
|
|
137
|
+
const promise = new Promise(async (resolve, reject) => {
|
|
138
|
+
const url = await send({ type: 'download', id })
|
|
139
|
+
|
|
140
|
+
await new Promise(r => setTimeout(r))
|
|
141
|
+
if (mode === 'url') resolve(url)
|
|
142
|
+
else if (mode === 'fetch') {
|
|
143
|
+
const response = await fetch(url)
|
|
144
|
+
const { ok, statusText } = response
|
|
145
|
+
|
|
146
|
+
if (ok) resolve(response)
|
|
147
|
+
else reject(statusText)
|
|
148
|
+
}
|
|
149
|
+
else if (mode === 'direct') {
|
|
150
|
+
// TODO: use browser progress UX instead of downloading all into memory first
|
|
151
|
+
const res = await download(id)
|
|
152
|
+
const { name } = await metadata(id)
|
|
153
|
+
const type = res.headers.get('Content-Type')
|
|
154
|
+
const blob = new Blob([ await res.blob() ], { type })
|
|
155
|
+
const url = window.URL.createObjectURL(blob)
|
|
156
|
+
const a = document.createElement('a')
|
|
157
|
+
a.style.display = 'none'
|
|
158
|
+
a.href = url
|
|
159
|
+
a.download = name
|
|
160
|
+
document.body.appendChild(a)
|
|
161
|
+
a.click()
|
|
162
|
+
window.URL.revokeObjectURL(url)
|
|
163
|
+
resolve()
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
promise.direct = () => {
|
|
167
|
+
mode = 'direct'
|
|
168
|
+
return promise
|
|
169
|
+
}
|
|
170
|
+
promise.url = () => {
|
|
171
|
+
mode = 'url'
|
|
172
|
+
return promise
|
|
173
|
+
}
|
|
174
|
+
return promise
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function isValidMetadataMutation({ path, op, value }) {
|
|
178
|
+
return (
|
|
179
|
+
['active_type', 'name'].includes(path[0])
|
|
180
|
+
&& path.length === 1
|
|
181
|
+
&& typeof value === 'string' || op === 'remove'
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function metadata(scope, user, domain) {
|
|
186
|
+
const md = await send({ type: 'metadata', scope, user, domain })
|
|
187
|
+
return new PatchProxy(md, patch => {
|
|
188
|
+
const activePatch = structuredClone(patch)
|
|
189
|
+
activePatch.forEach(entry => {
|
|
190
|
+
if (!isValidMetadataMutation(entry)) throw new Error('You may only modify the type or name for a scope\'s metadata')
|
|
191
|
+
})
|
|
192
|
+
interact(scope, activePatch)
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function login(provider, username, password) {
|
|
197
|
+
return send({ type: 'login', provider, username, password })
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function query(query, params, domain, context=[]) { return send({ type: 'query', query, params, domain, context }) }
|
|
201
|
+
function logout() { return send({ type: 'logout' }) }
|
|
202
|
+
function disconnect() { return send({ type: 'disconnect' }) }
|
|
203
|
+
function reconnect() { return send({ type: 'reconnect' }) }
|
|
204
|
+
function synced() { return send({ type: 'synced' }) }
|
|
205
|
+
function close(info) { return send({ type: 'close', info }) }
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
embedded: true,
|
|
209
|
+
uuid,
|
|
210
|
+
environment,
|
|
211
|
+
login,
|
|
212
|
+
logout,
|
|
213
|
+
create,
|
|
214
|
+
state,
|
|
215
|
+
watch,
|
|
216
|
+
upload,
|
|
217
|
+
download,
|
|
218
|
+
interact,
|
|
219
|
+
patch,
|
|
220
|
+
reset,
|
|
221
|
+
metadata,
|
|
222
|
+
disconnect,
|
|
223
|
+
reconnect,
|
|
224
|
+
synced,
|
|
225
|
+
close,
|
|
226
|
+
query
|
|
227
|
+
}
|
|
228
|
+
}
|