@knowlearning/agents 0.9.179 → 0.9.181

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.
@@ -48,6 +48,7 @@ function embed(environment, iframe) {
48
48
  const watchers = {}
49
49
  const postMessageQueue = []
50
50
  const listeners = {}
51
+ const responses = {}
51
52
  let frameLoaded = false
52
53
  let embeddedAgentInitialized = false
53
54
 
@@ -70,7 +71,10 @@ function embed(environment, iframe) {
70
71
  const handleMessage = async message => {
71
72
  const { requestId, type } = message
72
73
 
73
- const sendDown = (response, error) => postMessage({ requestId, response, error })
74
+ const sendDown = (response, error) => {
75
+ if (responses[requestId]) responses[requestId](response, error)
76
+ postMessage({ requestId, response, error })
77
+ }
74
78
 
75
79
  if (type === 'error') {
76
80
  console.error(message)
@@ -103,7 +107,7 @@ function embed(environment, iframe) {
103
107
  const namespacedScope = getNamespacedScope(environment.namespace, scope)
104
108
  let before, after
105
109
  if (listeners.mutate) before = copy(await Agent.state(namespacedScope))
106
- await Agent.interact(namespacedScope, patch, true, [environment.id, ...context])
110
+ const response = await Agent.interact(namespacedScope, patch, true, [environment.id, ...context])
107
111
  if (listeners.mutate) {
108
112
  const patchCopy = copy(patch)
109
113
  patchCopy.forEach(op => op.path.shift()) // remove "active" path prefix
@@ -115,7 +119,7 @@ function embed(environment, iframe) {
115
119
  patch: patchCopy
116
120
  })
117
121
  }
118
- sendDown({}) // TODO: might want to send down the interaction index
122
+ sendDown(response)
119
123
  }
120
124
  else if (type === 'metadata') {
121
125
  const { scope, user, domain } = message
@@ -146,7 +150,7 @@ function embed(environment, iframe) {
146
150
  Agent
147
151
  .query(query, params, domain, [environment.id, ...context])
148
152
  .then(sendDown)
149
- .catch(error => sendDown(null, error.error))
153
+ .catch(error => sendDown(null, error))
150
154
  }
151
155
  else if (type === 'upload') {
152
156
  const { info } = message
@@ -163,8 +167,19 @@ function embed(environment, iframe) {
163
167
  else if (type === 'disconnect') sendDown(await Agent.disconnect())
164
168
  else if (type === 'reconnect') sendDown(await Agent.reconnect())
165
169
  else if (type === 'synced') sendDown(await Agent.synced())
170
+ else if (type === 'guarantee') {
171
+ const { script, namespaces=[], context=[] } = message
172
+ sendDown(await Agent.guarantee(script, [environment.namespace || '', ...namespaces], [environment.id, ...context]))
173
+ }
174
+ else if (type === 'response') {
175
+ const { id, requestId } = message
176
+ responses[id] = (response, error) => {
177
+ delete responses[id]
178
+ sendDown(response, error)
179
+ }
180
+ }
166
181
  else {
167
- console.log('Unknown message type passed up...', message)
182
+ console.warn('Unknown message type passed up...', message)
168
183
  sendDown({})
169
184
  }
170
185
  }
@@ -2,19 +2,22 @@ import { v1 as uuid } from 'uuid'
2
2
  import { applyPatch } from 'fast-json-patch'
3
3
  import { getToken, login, logout } from './auth.js'
4
4
  import GenericAgent from '../generic/index.js'
5
+ import { io } from 'socket.io-client'
5
6
 
6
7
  const TEST_DOMAIN = 'tests.knowlearning.systems'
7
8
  const LANGUAGES = [...navigator.languages]
8
9
 
9
10
  const API_HOST = localStorage.getItem('API_HOST') || 'api.knowlearning.systems'
10
- // const API_HOST = 'api-test.knowlearning.systems'
11
- //const API_HOST = 'localhost:8765'
11
+ // const API_HOST = 'api-test.knowlearning.systems'
12
+ // const API_HOST = 'localhost:8765'
12
13
 
13
- // TODO: remove this hack when we can set partitioned sid cookie through websocket handshake
14
- // deno is partly in the way on teh set side, and browser support is in the way for
15
- // the client side.
14
+ // TODO: remove sid hack when partitioned cookies via WS handshakes are supported
16
15
  async function ensureSidEstablished() {
17
- const response = await fetch(`https://${API_HOST}/_sid-check`, { method: 'GET', credentials: 'include' })
16
+ const response = await fetch(`https://${API_HOST}/_sid-check`, {
17
+ method: 'GET',
18
+ credentials: 'include'
19
+ })
20
+
18
21
  const hasLocalStorageSID = !!localStorage.getItem('sid')
19
22
  if (response.status === 201) {
20
23
  if (!hasLocalStorageSID) {
@@ -24,7 +27,6 @@ async function ensureSidEstablished() {
24
27
  }
25
28
  }
26
29
  else if (response.status === 200) {
27
- // if we reach here, assumably the server has seen an sid cookie
28
30
  if (hasLocalStorageSID) {
29
31
  localStorage.removeItem('sid')
30
32
  location.reload()
@@ -37,20 +39,44 @@ async function ensureSidEstablished() {
37
39
 
38
40
  export default options => {
39
41
  ensureSidEstablished()
42
+
40
43
  const Connection = function () {
44
+ const sid = localStorage.getItem('sid')
45
+
46
+ // socket.io client connection
47
+ const socket = io(`https://${API_HOST}`, {
48
+ withCredentials: true,
49
+ extraHeaders: sid ? { sid } : {}
50
+ })
41
51
 
42
- const ws = new WebSocket(`wss://${API_HOST}`)
52
+ this.send = message => {
53
+ try {
54
+ socket.emit('message', message)
55
+ } catch (err) {
56
+ console.warn('Error sending via socket.io', err)
57
+ }
58
+ }
43
59
 
44
- this.send = message => ws.send(JSON.stringify(message))
45
60
  this.close = info => {
46
61
  this.send({ type: 'close', info })
47
- ws.close()
62
+ socket.disconnect()
48
63
  }
49
64
 
50
- ws.onopen = () => this.onopen()
51
- ws.onmessage = ({ data }) => this.onmessage(data.length === 0 ? null : JSON.parse(data))
52
- ws.onerror = error => this.onerror && this.onerror(error)
53
- ws.onclose = error => this.onclose && this.onclose(error)
65
+ socket.on('connect', () => {
66
+ if (this.onopen) this.onopen()
67
+ })
68
+
69
+ socket.on('message', (data) => {
70
+ if (this.onmessage) this.onmessage(data)
71
+ })
72
+
73
+ socket.on('error', (err) => {
74
+ if (this.onerror) this.onerror(err)
75
+ })
76
+
77
+ socket.on('disconnect', (reason) => {
78
+ if (this.onclose) this.onclose(reason)
79
+ })
54
80
 
55
81
  return this
56
82
  }
@@ -12,8 +12,10 @@ export default function EmbeddedAgent(postMessage) {
12
12
 
13
13
  const [ watch, removeWatcher ] = watchImplementation({ metadata, state, watchers, synced, sentUpdates, environment })
14
14
 
15
+ let lastRequestId
16
+
15
17
  async function send(message) {
16
- const requestId = message.requestId || uuid()
18
+ const requestId = lastRequestId = message.requestId || uuid()
17
19
 
18
20
  messageIndex += 1
19
21
  try {
@@ -202,6 +204,8 @@ export default function EmbeddedAgent(postMessage) {
202
204
  function reconnect() { return send({ type: 'reconnect' }) }
203
205
  function synced() { return send({ type: 'synced' }) }
204
206
  function close(info) { return send({ type: 'close', info }) }
207
+ function guarantee(script, namespaces, context) { return send({ type: 'guarantee', script, namespaces, context }) }
208
+ function response(id=lastRequestId) { return send({ type: 'response', id }) }
205
209
 
206
210
  return {
207
211
  embedded: true,
@@ -222,6 +226,7 @@ export default function EmbeddedAgent(postMessage) {
222
226
  reconnect,
223
227
  synced,
224
228
  close,
229
+ response,
225
230
  query
226
231
  }
227
232
  }
@@ -28,9 +28,10 @@ export default function Agent({ Connection, domain, token, sid, uuid, fetch, app
28
28
  ] = messageQueue({ token, sid, domain, Connection, watchers, states, applyPatch, log, login, interact, reboot, trigger, handleDomainMessage, variables })
29
29
 
30
30
  // initialize session
31
+ const initialSessionData = { queries: {}, subscriptions: {}, guarantees: {} }
31
32
  environment()
32
33
  .then(({ session }) => {
33
- interact('sessions', [{ op: 'add', path: ['active', session], value: { queries: {}, subscriptions: {} } }], false)
34
+ interact('sessions', [{ op: 'add', path: ['active', session], value: initialSessionData }], false)
34
35
  })
35
36
 
36
37
  const internalReferences = {
@@ -157,23 +158,10 @@ export default function Agent({ Connection, domain, token, sid, uuid, fetch, app
157
158
  ], false)
158
159
  try {
159
160
  const response = await lastMessageResponse()
160
- const { rows } = response
161
-
162
- interact('sessions', [
163
- {
164
- op: 'add',
165
- path: ['active', session, 'queries', id, 'agent_latency'],
166
- value: Date.now() - requested
167
- },
168
- {
169
- op: 'remove',
170
- path: ['active', session, 'queries', id]
171
- }
172
- ], false)
173
- return rows
161
+ return response.rows
174
162
  }
175
163
  catch (error) {
176
- throw error
164
+ throw error.error
177
165
  }
178
166
  }
179
167
 
@@ -188,6 +176,37 @@ export default function Agent({ Connection, domain, token, sid, uuid, fetch, app
188
176
  reactions[event].forEach(f => f(data))
189
177
  }
190
178
 
179
+ async function guarantee(script, namespaces=[], context=[]) {
180
+ const { session } = await environment()
181
+ const id = uuid()
182
+
183
+ interact('sessions', [
184
+ {
185
+ op: 'add',
186
+ path: ['active', session, 'guarantees', id],
187
+ value: { script, namespaces, context }
188
+ }
189
+ ], false)
190
+
191
+ return {
192
+ execute() {
193
+ // TODO: use simplified worker wrapper to execute script in restricted environment
194
+ },
195
+ cancel() {
196
+ interact('sessions', [
197
+ {
198
+ op: 'remove',
199
+ path: ['active', session, 'guarantees', id]
200
+ }
201
+ ], false)
202
+ }
203
+ }
204
+ }
205
+
206
+ function response() {
207
+ return lastMessageResponse()
208
+ }
209
+
191
210
  return {
192
211
  uuid,
193
212
  environment,
@@ -208,6 +227,8 @@ export default function Agent({ Connection, domain, token, sid, uuid, fetch, app
208
227
  disconnect,
209
228
  reconnect,
210
229
  debug,
230
+ guarantee,
231
+ response,
211
232
  on
212
233
  }
213
234
  }
@@ -227,7 +227,11 @@ export default function messageQueue({ token, sid, domain, Connection, watchers,
227
227
  return syncPromise
228
228
  }
229
229
 
230
- function lastMessageResponse() { return new Promise((res, rej) => responses[si].push([res, rej])) }
230
+ function lastMessageResponse() {
231
+ if (!responses[si]) throw new Error('A response must be requested before all outstanding responses have already returned')
232
+
233
+ return new Promise((res, rej) => responses[si].push([res, rej]))
234
+ }
231
235
 
232
236
  function disconnect() {
233
237
  log('DISCONNECTED AGENT!!!!!!!!!!!!!!!')
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@knowlearning/agents",
3
- "version": "0.9.179",
3
+ "version": "0.9.181",
4
4
  "description": "API for embedding applications in KnowLearning systems.",
5
5
  "main": "node.js",
6
6
  "browser": "browser.js",
7
7
  "type": "module",
8
+ "types": "types.d.ts",
8
9
  "directories": {
9
10
  "example": "examples",
10
11
  "test": "test"
@@ -25,6 +26,7 @@
25
26
  "dependencies": {
26
27
  "@knowlearning/patch-proxy": "^1.3.4",
27
28
  "fast-json-patch": "^3.1.1",
29
+ "socket.io-client": "^4.8.1",
28
30
  "uuid": "^8.3.2"
29
31
  },
30
32
  "devDependencies": {
@@ -19,12 +19,12 @@ export interface AgentEnvironment {
19
19
  }
20
20
 
21
21
  export interface AgentUploadInfo {
22
- name?: string,
23
- type?: string,
24
- data?: string | ArrayBuffer,
25
- id?: string,
26
- browser?: boolean,
27
- accept?: string,
22
+ name?: string;
23
+ type?: string;
24
+ data?: string | ArrayBuffer;
25
+ id?: string;
26
+ browser?: boolean;
27
+ accept?: string;
28
28
  }
29
29
 
30
30
  export interface Agent {
@@ -39,6 +39,7 @@ export interface Agent {
39
39
  environment(userId?: string): Promise<AgentEnvironment>;
40
40
  close(): void;
41
41
  reset(ns: string): Promise<void>;
42
+ // TODO: add query function
42
43
  synced(): Promise<void>;
43
44
  }
44
45