@standardagents/code-web 0.14.1-issue.663.fd4c3f → 0.14.4-branch.9e48e914.ee06ef
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/README.md +9 -1
- package/dist/build.json +1 -1
- package/dist/licenses/jco-transpile.txt +220 -0
- package/dist/manifest.json +1 -1
- package/dist/manifest.sig +1 -1
- package/dist/pkg/standard_web.d.ts +44 -6
- package/dist/pkg/standard_web.js +292 -20
- package/dist/pkg/standard_web_bg.wasm +0 -0
- package/dist/pkg/standard_web_bg.wasm.d.ts +11 -6
- package/dist/plugins/clock-cells/clock_cells.core.wasm +0 -0
- package/dist/plugins/clock-cells/clock_cells.wasm +0 -0
- package/dist/plugins/clock-cells/standard-plugin.json +11 -0
- package/dist/plugins/host/transpile.js +51 -0
- package/dist/plugins/host/ui-jspi.js +14851 -0
- package/dist/plugins/host/ui.core2.wasm +0 -0
- package/dist/plugins/host/ui.core3.wasm +0 -0
- package/dist/plugins/host/ui.js +14675 -0
- package/dist/plugins/host/world.js +172 -0
- package/dist/plugins/jco/cli.js +141 -0
- package/dist/plugins/jco/common.js +16 -0
- package/dist/plugins/jco/config.js +7 -0
- package/dist/plugins/jco/environment.js +24 -0
- package/dist/plugins/jco/filesystem.js +339 -0
- package/dist/plugins/jco/in-memory-filesystem.js +620 -0
- package/dist/plugins/jco/io.js +366 -0
- package/dist/plugins/jco/js-component-bindgen-component.core.wasm +0 -0
- package/dist/plugins/jco/js-component-bindgen-component.core2.wasm +0 -0
- package/dist/plugins/jco/js-component-bindgen-component.js +14272 -0
- package/dist/plugins/jco/opfs-filesystem.js +245 -0
- package/dist/plugins/jco/random.js +51 -0
- package/dist/plugins/plasma-pixels/plasma_pixels.core.wasm +0 -0
- package/dist/plugins/plasma-pixels/plasma_pixels.wasm +0 -0
- package/dist/plugins/plasma-pixels/standard-plugin.json +11 -0
- package/dist/plugins/runtime.js +1044 -0
- package/dist/plugins/samples.json +81 -0
- package/dist/plugins/worker.js +411 -0
- package/package.json +8 -2
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": 2,
|
|
3
|
+
"transpiler": "@bytecodealliance/jco-transpile@0.15.0",
|
|
4
|
+
"world": {
|
|
5
|
+
"glue": {
|
|
6
|
+
"file": "ui.js",
|
|
7
|
+
"sha256": "24c4ba57a14d27d8fda334210c4b5225f1b196ed43f8680442954f07080294ba"
|
|
8
|
+
},
|
|
9
|
+
"jspi": {
|
|
10
|
+
"file": "ui-jspi.js",
|
|
11
|
+
"sha256": "24e6e96752a139da58b703f6b2ae7551ae011783ed6ca895cf343e1a355da8e9"
|
|
12
|
+
},
|
|
13
|
+
"shims": [
|
|
14
|
+
{
|
|
15
|
+
"file": "ui.core2.wasm",
|
|
16
|
+
"sha256": "f3b18532f5fa5f5e31522891929d243dd383df225120106933cce6b7b31960c0"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"file": "ui.core3.wasm",
|
|
20
|
+
"sha256": "7ec25df2026c03fdd7c51abe4522e357d5888c0b2ba4b45cf8bf502275da1c60"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
"plugins": [
|
|
25
|
+
{
|
|
26
|
+
"id": "clock-cells",
|
|
27
|
+
"dir": "clock-cells",
|
|
28
|
+
"manifest": {
|
|
29
|
+
"apiVersion": 2,
|
|
30
|
+
"kind": "ui",
|
|
31
|
+
"id": "clock-cells",
|
|
32
|
+
"version": "0.1.0",
|
|
33
|
+
"module": "clock_cells.wasm",
|
|
34
|
+
"surfaces": [
|
|
35
|
+
{
|
|
36
|
+
"id": "card",
|
|
37
|
+
"anchor": "sidebar.card",
|
|
38
|
+
"model": "cells",
|
|
39
|
+
"height": 2
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
"grants": {}
|
|
43
|
+
},
|
|
44
|
+
"component": {
|
|
45
|
+
"file": "clock_cells.wasm",
|
|
46
|
+
"sha256": "80d8a92f866f3bd906dfdde535e5b6489613ebffa4b04b0029d71182f2af28b9"
|
|
47
|
+
},
|
|
48
|
+
"core": {
|
|
49
|
+
"file": "clock_cells.core.wasm",
|
|
50
|
+
"sha256": "df640416b7ff87c0bd7dc9982f29baa6fdf8c856e13f28484e13727cfd3823ac"
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"id": "plasma-pixels",
|
|
55
|
+
"dir": "plasma-pixels",
|
|
56
|
+
"manifest": {
|
|
57
|
+
"apiVersion": 2,
|
|
58
|
+
"kind": "ui",
|
|
59
|
+
"id": "plasma-pixels",
|
|
60
|
+
"version": "0.1.0",
|
|
61
|
+
"module": "plasma_pixels.wasm",
|
|
62
|
+
"surfaces": [
|
|
63
|
+
{
|
|
64
|
+
"id": "stage",
|
|
65
|
+
"anchor": "stage",
|
|
66
|
+
"model": "pixels"
|
|
67
|
+
}
|
|
68
|
+
],
|
|
69
|
+
"grants": {}
|
|
70
|
+
},
|
|
71
|
+
"component": {
|
|
72
|
+
"file": "plasma_pixels.wasm",
|
|
73
|
+
"sha256": "cc80f9e6fdeb7433cf3f1ab7787f7192082a2a86a38d8aa535f7d6c934c2819d"
|
|
74
|
+
},
|
|
75
|
+
"core": {
|
|
76
|
+
"file": "plasma_pixels.core.wasm",
|
|
77
|
+
"sha256": "0cf0a23eceb1acc34db76b6e20f773229c52e0fc453b4324ea302347c899e6d0"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
}
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
// One plugin's Worker (docs/plugin-runtime.md, "The browser runtime").
|
|
2
|
+
//
|
|
3
|
+
// The viewer starts one module Worker per plugin from its own verified
|
|
4
|
+
// bundle path and sends it plain messages: `{ m: <json>, b: [buffers] }`.
|
|
5
|
+
// The Worker gets the plugin's world core module (a sample's from the
|
|
6
|
+
// bundle, checked against the index; an account package's by transpiling
|
|
7
|
+
// its verified component in the viewer, cached per component hash),
|
|
8
|
+
// instantiates it through the viewer's own reference glue (host/world.js)
|
|
9
|
+
// with the host imports in runtime.js, and calls `activate`, `frame()` and
|
|
10
|
+
// `event()` only when the viewer asks. It never wakes on its own: an idle
|
|
11
|
+
// plugin costs nothing here.
|
|
12
|
+
//
|
|
13
|
+
// The same script, started by a plugin Worker with a `transpile` message,
|
|
14
|
+
// is the transpile Worker: it loads jco's transpiler, extracts the world
|
|
15
|
+
// core module, answers, and is terminated. It runs under the Worker policy
|
|
16
|
+
// the site gives this path, and only the plugin Worker that needs a
|
|
17
|
+
// transpile pays for loading jco.
|
|
18
|
+
//
|
|
19
|
+
// Calls into the plugin run one at a time, in the order the viewer sent
|
|
20
|
+
// them. Under JSPI an account call suspends the plugin until the viewer
|
|
21
|
+
// answers; what arrives meanwhile waits its turn, except the answers
|
|
22
|
+
// themselves, released buffers and view state.
|
|
23
|
+
//
|
|
24
|
+
// The viewer owns the budgets: this Worker reports how long each `frame()`
|
|
25
|
+
// took, less the time it waited for the account, and the viewer terminates
|
|
26
|
+
// a Worker that overruns or hangs.
|
|
27
|
+
import { Mirror, PluginHost, witEvent } from './runtime.js'
|
|
28
|
+
import { instantiatePlugin } from './host/world.js'
|
|
29
|
+
import { extractMain } from './host/transpile.js'
|
|
30
|
+
|
|
31
|
+
const CACHE = 'standard-plugin-core-v1'
|
|
32
|
+
|
|
33
|
+
let epoch = 0
|
|
34
|
+
let host = null
|
|
35
|
+
let plugin = null
|
|
36
|
+
let memory = () => null
|
|
37
|
+
let memoryLimit = 64 * 1024 * 1024
|
|
38
|
+
let dead = false
|
|
39
|
+
/** Work for the plugin, in order; `running` while a call is suspended. */
|
|
40
|
+
const queue = []
|
|
41
|
+
let running = false
|
|
42
|
+
/** A call into the plugin is on the stack or suspended. */
|
|
43
|
+
let calling = false
|
|
44
|
+
/** What arrived before the plugin was instantiated. */
|
|
45
|
+
let early = []
|
|
46
|
+
/** Account calls in flight, by id, and the time spent waiting on them. */
|
|
47
|
+
const pending = new Map()
|
|
48
|
+
let nextCall = 1
|
|
49
|
+
let suspendedSince = null
|
|
50
|
+
let suspendedMs = 0
|
|
51
|
+
|
|
52
|
+
function post(message, buffer) {
|
|
53
|
+
const data = { m: JSON.stringify({ ...message, epoch }) }
|
|
54
|
+
if (buffer) {
|
|
55
|
+
data.b = [buffer]
|
|
56
|
+
self.postMessage(data, [buffer])
|
|
57
|
+
} else {
|
|
58
|
+
self.postMessage(data)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function describe(error) {
|
|
63
|
+
return error instanceof Error ? `${error.name}: ${error.message}` : String(error)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** A trap, or a call that grew memory past the limit, ends this instance. */
|
|
67
|
+
function trapped(error) {
|
|
68
|
+
if (dead) return
|
|
69
|
+
dead = true
|
|
70
|
+
queue.length = 0
|
|
71
|
+
post({ t: 'trap', error: describe(error) })
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function checkMemory() {
|
|
75
|
+
const current = memory()
|
|
76
|
+
if (current && current.buffer.byteLength > memoryLimit) {
|
|
77
|
+
trapped(new Error(`linear memory grew to ${current.buffer.byteLength} bytes, over the ${memoryLimit} limit`))
|
|
78
|
+
return false
|
|
79
|
+
}
|
|
80
|
+
return true
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function sha256(bytes) {
|
|
84
|
+
const digest = await crypto.subtle.digest('SHA-256', bytes)
|
|
85
|
+
return [...new Uint8Array(digest)].map(byte => byte.toString(16).padStart(2, '0')).join('')
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** A file of this bundle, bound to the SHA-256 the index names. The site's
|
|
89
|
+
* verified route checked the whole bundle; this binds each file to the
|
|
90
|
+
* index the viewer read. A non-secure development origin has no WebCrypto
|
|
91
|
+
* and relies on the bundle check alone. */
|
|
92
|
+
async function bundleFile(url, expected) {
|
|
93
|
+
const response = await fetch(url)
|
|
94
|
+
if (!response.ok) throw new Error(`${url}: HTTP ${response.status}`)
|
|
95
|
+
const bytes = new Uint8Array(await response.arrayBuffer())
|
|
96
|
+
if (globalThis.crypto?.subtle) {
|
|
97
|
+
const actual = await sha256(bytes)
|
|
98
|
+
if (actual !== expected) throw new Error(`${url}: SHA-256 ${actual} does not match the index`)
|
|
99
|
+
}
|
|
100
|
+
return bytes
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ── the account, through the viewer ─────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
function ask(call) {
|
|
106
|
+
const id = nextCall++
|
|
107
|
+
return new Promise((resolve, reject) => {
|
|
108
|
+
pending.set(id, { resolve, reject })
|
|
109
|
+
if (calling && pending.size === 1 && suspendedSince === null) {
|
|
110
|
+
suspendedSince = performance.now()
|
|
111
|
+
post({ t: 'suspend', on: true })
|
|
112
|
+
}
|
|
113
|
+
post({ t: 'account', id, call })
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function answered(id, reply) {
|
|
118
|
+
const waiter = pending.get(id)
|
|
119
|
+
if (!waiter) return
|
|
120
|
+
pending.delete(id)
|
|
121
|
+
if (pending.size === 0 && suspendedSince !== null) {
|
|
122
|
+
suspendedMs += performance.now() - suspendedSince
|
|
123
|
+
suspendedSince = null
|
|
124
|
+
post({ t: 'suspend', on: false })
|
|
125
|
+
}
|
|
126
|
+
waiter.resolve(reply)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ── the world core module ───────────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
/** Transpiles in a Worker of its own, so jco's memory goes with it. */
|
|
132
|
+
function transpileElsewhere(component) {
|
|
133
|
+
return new Promise((resolve, reject) => {
|
|
134
|
+
const worker = new Worker(import.meta.url, { type: 'module' })
|
|
135
|
+
worker.onmessage = (event) => {
|
|
136
|
+
worker.terminate()
|
|
137
|
+
const message = JSON.parse(event.data.m)
|
|
138
|
+
if (message.t === 'transpiled') resolve({ bytes: new Uint8Array(event.data.b[0]), ms: message.ms })
|
|
139
|
+
else reject(new Error(message.error))
|
|
140
|
+
}
|
|
141
|
+
worker.onerror = (event) => {
|
|
142
|
+
worker.terminate()
|
|
143
|
+
reject(new Error(event.message || 'the transpile Worker failed'))
|
|
144
|
+
}
|
|
145
|
+
const copy = component.slice().buffer
|
|
146
|
+
worker.postMessage({ m: JSON.stringify({ t: 'transpile' }), b: [copy] }, [copy])
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function transpileHere(component) {
|
|
151
|
+
const started = performance.now()
|
|
152
|
+
const jco = await import(new URL('jco/js-component-bindgen-component.js', import.meta.url).href)
|
|
153
|
+
await jco.$init
|
|
154
|
+
const loadedAt = performance.now()
|
|
155
|
+
const bytes = await extractMain(component, jco.generate)
|
|
156
|
+
return { bytes, ms: { load: loadedAt - started, transpile: performance.now() - loadedAt } }
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function cacheKey(base, entry, world) {
|
|
160
|
+
return new Request(new URL(`plugins/cache/${entry.component.sha256}-${world.glue.sha256.slice(0, 16)}.wasm`, base))
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function cached(key) {
|
|
164
|
+
try {
|
|
165
|
+
if (!globalThis.caches) return null
|
|
166
|
+
const response = await (await caches.open(CACHE)).match(key)
|
|
167
|
+
if (!response) return null
|
|
168
|
+
const bytes = new Uint8Array(await response.arrayBuffer())
|
|
169
|
+
const expected = response.headers.get('x-core-sha256')
|
|
170
|
+
if (globalThis.crypto?.subtle && (!expected || (await sha256(bytes)) !== expected)) return null
|
|
171
|
+
return bytes
|
|
172
|
+
} catch {
|
|
173
|
+
return null
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function remember(key, bytes) {
|
|
178
|
+
try {
|
|
179
|
+
if (!globalThis.caches) return
|
|
180
|
+
const headers = { 'content-type': 'application/wasm' }
|
|
181
|
+
if (globalThis.crypto?.subtle) headers['x-core-sha256'] = await sha256(bytes)
|
|
182
|
+
await (await caches.open(CACHE)).put(key, new Response(bytes, { headers }))
|
|
183
|
+
} catch {
|
|
184
|
+
// A full or unavailable cache only costs the next start a transpile.
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** The plugin's world core module and how it was obtained. */
|
|
189
|
+
async function mainModule(init, component) {
|
|
190
|
+
const entry = init.plugin
|
|
191
|
+
if (!component) {
|
|
192
|
+
const dir = new URL(`plugins/${entry.dir}/`, init.base)
|
|
193
|
+
return { bytes: await bundleFile(new URL(entry.core.file, dir).href, entry.core.sha256), source: 'bundle' }
|
|
194
|
+
}
|
|
195
|
+
// The viewer checked the package against its hash and hashed the
|
|
196
|
+
// component it extracted; the cache is keyed by that and by the glue.
|
|
197
|
+
const key = cacheKey(init.base, entry, init.world)
|
|
198
|
+
const hit = await cached(key)
|
|
199
|
+
if (hit) return { bytes: hit, source: 'cache' }
|
|
200
|
+
const { bytes, ms } = typeof Worker === 'function' ? await transpileElsewhere(component) : await transpileHere(component)
|
|
201
|
+
await remember(key, bytes)
|
|
202
|
+
return { bytes, source: 'transpiled', ms }
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function load(init, component) {
|
|
206
|
+
const started = performance.now()
|
|
207
|
+
memoryLimit = init.memoryLimit
|
|
208
|
+
const jspi = init.account?.mode === 'jspi'
|
|
209
|
+
const world = init.world
|
|
210
|
+
const glueFile = jspi ? world.jspi : world.glue
|
|
211
|
+
const [glue, shims, main] = await Promise.all([
|
|
212
|
+
import(new URL(`plugins/host/${glueFile.file}`, init.base).href),
|
|
213
|
+
Promise.all(world.shims.map(async shim => [shim.file, await WebAssembly.compile(
|
|
214
|
+
await bundleFile(new URL(`plugins/host/${shim.file}`, init.base).href, shim.sha256))])),
|
|
215
|
+
mainModule(init, component),
|
|
216
|
+
])
|
|
217
|
+
const account = init.account
|
|
218
|
+
? { mode: init.account.mode, ask, mirror: new Mirror(init.account.mirror ?? {}) }
|
|
219
|
+
: null
|
|
220
|
+
host = new PluginHost({
|
|
221
|
+
manifest: init.plugin.manifest,
|
|
222
|
+
grants: init.grants ?? undefined,
|
|
223
|
+
config: init.config ?? '{}',
|
|
224
|
+
view: init.view,
|
|
225
|
+
shared: init.shared,
|
|
226
|
+
post,
|
|
227
|
+
memory: () => memory(),
|
|
228
|
+
account,
|
|
229
|
+
deliver: event => enqueue({ t: 'event', event, gesture: false }),
|
|
230
|
+
})
|
|
231
|
+
if (init.accountState) host.accountState = init.accountState
|
|
232
|
+
const linked = instantiatePlugin({
|
|
233
|
+
glue,
|
|
234
|
+
shims: Object.fromEntries(shims),
|
|
235
|
+
main: await WebAssembly.compile(main.bytes),
|
|
236
|
+
imports: host.imports(),
|
|
237
|
+
// The surface's `attach` takes the region as a list<u8>; the canonical
|
|
238
|
+
// ABI hands the core import its address and length in linear memory,
|
|
239
|
+
// which the host records to copy committed slots out of later. The
|
|
240
|
+
// glue lifts only a copy of the bytes.
|
|
241
|
+
wrap: (module, name, fn) => (module === 'cm32p2|standard:plugin/surface@2' && name === 'attach'
|
|
242
|
+
? (...args) => {
|
|
243
|
+
host.attaching = { ptr: args[2] >>> 0, len: args[3] >>> 0 }
|
|
244
|
+
return fn(...args)
|
|
245
|
+
}
|
|
246
|
+
: fn),
|
|
247
|
+
})
|
|
248
|
+
memory = linked.memory
|
|
249
|
+
plugin = linked.instance
|
|
250
|
+
const timing = { ms: performance.now() - started, source: main.source, transpile: main.ms ?? null }
|
|
251
|
+
enqueue({ t: 'activate', timing })
|
|
252
|
+
const waiting = early
|
|
253
|
+
early = []
|
|
254
|
+
for (const { message, buffers } of waiting) handle(message, buffers)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ── calls into the plugin ───────────────────────────────────────────────────
|
|
258
|
+
|
|
259
|
+
function enqueue(message, buffers = []) {
|
|
260
|
+
queue.push({ message, buffers })
|
|
261
|
+
drain()
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Runs queued work until the queue is empty or a call suspends. */
|
|
265
|
+
function drain() {
|
|
266
|
+
while (!running && !dead && plugin && queue.length) {
|
|
267
|
+
const { message, buffers } = queue.shift()
|
|
268
|
+
let result
|
|
269
|
+
calling = true
|
|
270
|
+
try {
|
|
271
|
+
result = begin(message, buffers)
|
|
272
|
+
} catch (error) {
|
|
273
|
+
calling = false
|
|
274
|
+
trapped(error)
|
|
275
|
+
return
|
|
276
|
+
}
|
|
277
|
+
if (result instanceof Promise) {
|
|
278
|
+
running = true
|
|
279
|
+
result.then((value) => {
|
|
280
|
+
running = false
|
|
281
|
+
calling = false
|
|
282
|
+
finish(message, value)
|
|
283
|
+
drain()
|
|
284
|
+
}, (error) => {
|
|
285
|
+
running = false
|
|
286
|
+
calling = false
|
|
287
|
+
trapped(error)
|
|
288
|
+
})
|
|
289
|
+
return
|
|
290
|
+
}
|
|
291
|
+
calling = false
|
|
292
|
+
finish(message, result)
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** Starts one piece of work; a call into the plugin may return a promise
|
|
297
|
+
* (JSPI). */
|
|
298
|
+
function begin(message, buffers) {
|
|
299
|
+
suspendedMs = 0
|
|
300
|
+
message.started = performance.now()
|
|
301
|
+
switch (message.t) {
|
|
302
|
+
case 'activate':
|
|
303
|
+
return plugin.activate(host.config)
|
|
304
|
+
case 'resize':
|
|
305
|
+
host.resize(message.surface, message.geometry, message.gen, buffers, message.model)
|
|
306
|
+
return plugin.event(witEvent({ tag: 'resize', surface: message.surface, geometry: message.geometry }))
|
|
307
|
+
case 'visibility':
|
|
308
|
+
return plugin.event(witEvent({ tag: 'visibility', surface: message.surface, visible: message.visible }))
|
|
309
|
+
case 'event':
|
|
310
|
+
// A key, paste, press or command lets the plugin open a surface
|
|
311
|
+
// while it handles it, and only then.
|
|
312
|
+
host.gesture = Boolean(message.gesture)
|
|
313
|
+
if (host.gesture) host.noteInput()
|
|
314
|
+
return plugin.event(witEvent(message.event))
|
|
315
|
+
case 'frame':
|
|
316
|
+
return plugin.frame(BigInt(message.now), message.interval, message.power)
|
|
317
|
+
case 'deactivate':
|
|
318
|
+
return plugin.deactivate()
|
|
319
|
+
default:
|
|
320
|
+
return undefined
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function finish(message, result) {
|
|
325
|
+
host.gesture = false
|
|
326
|
+
if (dead || !checkMemory()) return
|
|
327
|
+
switch (message.t) {
|
|
328
|
+
case 'activate':
|
|
329
|
+
post({ t: 'ready', jspi: host.waits, timing: message.timing })
|
|
330
|
+
break
|
|
331
|
+
case 'frame': {
|
|
332
|
+
const elapsed = performance.now() - message.started - suspendedMs
|
|
333
|
+
post({ t: 'frame-done', next: result === undefined ? null : Number(result), elapsed })
|
|
334
|
+
break
|
|
335
|
+
}
|
|
336
|
+
case 'deactivate':
|
|
337
|
+
dead = true
|
|
338
|
+
self.close()
|
|
339
|
+
break
|
|
340
|
+
default:
|
|
341
|
+
break
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// ── the viewer's messages ───────────────────────────────────────────────────
|
|
346
|
+
|
|
347
|
+
function handle(message, buffers) {
|
|
348
|
+
switch (message.t) {
|
|
349
|
+
case 'account-reply':
|
|
350
|
+
answered(message.id, message.reply)
|
|
351
|
+
break
|
|
352
|
+
case 'release':
|
|
353
|
+
if (!dead && host) host.release(message.surface, message.gen, message.slot, buffers[0])
|
|
354
|
+
break
|
|
355
|
+
case 'view':
|
|
356
|
+
if (host) host.view = message.view
|
|
357
|
+
break
|
|
358
|
+
case 'mirror':
|
|
359
|
+
host?.account?.mirror?.apply(message.values ?? {})
|
|
360
|
+
break
|
|
361
|
+
case 'account-state':
|
|
362
|
+
if (!host) break
|
|
363
|
+
host.accountState = message.state
|
|
364
|
+
if (host.accountWatched) enqueue({ t: 'event', event: { tag: 'account-changed' }, gesture: false })
|
|
365
|
+
break
|
|
366
|
+
case 'deliver':
|
|
367
|
+
enqueue({ t: 'event', event: message.event, gesture: false })
|
|
368
|
+
break
|
|
369
|
+
default:
|
|
370
|
+
enqueue(message, buffers)
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async function transpileRole(component) {
|
|
375
|
+
try {
|
|
376
|
+
const { bytes, ms } = await transpileHere(component)
|
|
377
|
+
const buffer = bytes.slice().buffer
|
|
378
|
+
self.postMessage({ m: JSON.stringify({ t: 'transpiled', ms }), b: [buffer] }, [buffer])
|
|
379
|
+
} catch (error) {
|
|
380
|
+
self.postMessage({ m: JSON.stringify({ t: 'failed', error: describe(error) }) })
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
self.onmessage = (event) => {
|
|
385
|
+
const message = JSON.parse(event.data.m)
|
|
386
|
+
const buffers = event.data.b ?? []
|
|
387
|
+
if (message.t === 'transpile') {
|
|
388
|
+
transpileRole(new Uint8Array(buffers[0]))
|
|
389
|
+
return
|
|
390
|
+
}
|
|
391
|
+
if (message.t === 'init') {
|
|
392
|
+
epoch = message.epoch
|
|
393
|
+
// A Worker has no page address: resolve a relative bundle base against
|
|
394
|
+
// this script's own location, so callers may pass either form.
|
|
395
|
+
message.base = new URL(message.base, self.location.href).href
|
|
396
|
+
const component = buffers[0] ? new Uint8Array(buffers[0]) : null
|
|
397
|
+
load(message, component).catch((error) => {
|
|
398
|
+
dead = true
|
|
399
|
+
post({ t: 'failed', error: describe(error) })
|
|
400
|
+
})
|
|
401
|
+
return
|
|
402
|
+
}
|
|
403
|
+
// Until the plugin exists only queued work accumulates; view state and
|
|
404
|
+
// releases for it arrive only after its surfaces were sized.
|
|
405
|
+
if (!host && message.t !== 'account-reply') {
|
|
406
|
+
early.push({ message, buffers })
|
|
407
|
+
return
|
|
408
|
+
}
|
|
409
|
+
handle(message, buffers)
|
|
410
|
+
}
|
|
411
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@standardagents/code-web",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.4-branch.9e48e914.ee06ef",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The Standard viewer runtime for the browser: the TUI compiled to wasm, its JS glue, and its fonts",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"./standard_web_bg.wasm": "./dist/pkg/standard_web_bg.wasm",
|
|
28
28
|
"./fonts/*": "./dist/fonts/*",
|
|
29
29
|
"./licenses/*": "./dist/licenses/*",
|
|
30
|
+
"./plugins/*": "./dist/plugins/*",
|
|
30
31
|
"./build.json": "./dist/build.json",
|
|
31
32
|
"./manifest.json": "./dist/manifest.json",
|
|
32
33
|
"./manifest.sig": "./dist/manifest.sig",
|
|
@@ -36,6 +37,7 @@
|
|
|
36
37
|
"dist/pkg",
|
|
37
38
|
"dist/fonts",
|
|
38
39
|
"dist/licenses",
|
|
40
|
+
"dist/plugins",
|
|
39
41
|
"dist/build.json",
|
|
40
42
|
"dist/manifest.json",
|
|
41
43
|
"dist/manifest.sig",
|
|
@@ -45,6 +47,10 @@
|
|
|
45
47
|
"fonts": "node scripts/fetch-fonts.mjs",
|
|
46
48
|
"build": "node scripts/build.mjs",
|
|
47
49
|
"check": "node scripts/build.mjs --check",
|
|
48
|
-
"serve": "node scripts/serve.mjs"
|
|
50
|
+
"serve": "node scripts/serve.mjs",
|
|
51
|
+
"test": "node --test test/*.test.mjs"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@bytecodealliance/jco-transpile": "0.15.0"
|
|
49
55
|
}
|
|
50
56
|
}
|