open-standards/aac/aac-rs/third_party/onnxruntime-web/vendor.sh
Transaction Science 66218e2b83 fix(aac listener): its worker owns its GPU device, and gives it back before it ends
In WebKit, ending a worker whose WebGPU device onnxruntime-web's JSPI build
still holds ends the page with it (found by the First letters work, 215f65a4;
seen here with Playwright's WebKit, 2026-10-02). On an iPad that is the end of
every listen: the listener lets go as listening ends (the models take turns),
as the sentence helper closes, after a failed run, and when it sits unused.

The worker now asks the adapter for a device of its own (every limit and
feature it offers) and hands it to the runtime for all three sessions.
Unloading closes any listening, releases the sessions and destroys the
device, which gives back all of its graphics memory; a load that fails
destroys it too. listen.js ends a worker only once it has answered that
unload (retire, used by dropWorker, through which every path that lets the
listener go passes); one that never answers is left rather than ended.

Its bytes changed, so the runtime directory is onnxruntime-web-1.30.0-aac4,
with the worker re-pinned in RUNTIME_FILES and the directory in vendor.sh.
The bundle and the WebAssembly are unchanged.

Checked in Playwright's WebKit with the app's own code loading three
sessions on WebGPU: closing the row (twice) and a failed run leave the page
alive; the same steps with the previous worker end the page.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-02 00:47:11 -04:00

171 lines
9.2 KiB
Shell
Executable file

#!/usr/bin/env bash
# ============================================================================
# vendor.sh — put the listener's runtime (Microsoft's onnxruntime-web) on the
# app's own site.
#
# third_party/onnxruntime-web/vendor.sh <site-dir> [cache-dir]
#
# Writes <site-dir>/vendor/onnxruntime-web-<version>-<revision>/ (and removes
# any other vendor/onnxruntime-web-* there):
# asr-worker.js this directory's worker.js, unchanged
# ort.jspi.bundle.min.mjs and ort-wasm-simd-threaded.jspi.wasm
# the WebGPU runtime for browsers with JavaScript
# Promise Integration (WebAssembly.Suspending), from
# the npm package, unchanged
# LICENSE.txt, NOTICE.txt this directory's licence and notice
# ThirdPartyNotices.txt ONNX Runtime's notices for the code it includes,
# at the same release, fetched and pinned
#
# Only this build: the listener is offered only where JSPI is (it reads the
# model one tensor at a time; without JSPI the runtime would hold the whole
# model in memory first). The package's other builds (asyncify, the
# processor-only WebAssembly one, WebGL, JSEP) are not shipped.
#
# The app never imports these URLs directly. When someone downloads the
# listener, src/listen.rs stores the worker and the one build their browser
# runs in the model store (SHA-256 checked, kept across app updates), and
# src/bindings/listen.js runs them from there, in a worker, offline.
#
# The version and revision come from ORT_WEB_VERSION and ORT_WEB_VENDOR_REV
# in crates/aac-web/src/listen.rs, and each runtime file's size and SHA-256
# from RUNTIME_FILES there. This script fails loudly on any drift: another
# version asked for than pinned here, an npm tarball that is not the pinned
# one, a vendored file that differs from the Rust pin, or a directory whose
# bytes differ from VENDOR_DIGEST. Changed bytes need a new revision, because
# browsers and the model store treat the directory's URLs as immutable.
#
# Idempotent; the download is cached in cache-dir (default ~/.cache/aac-rs).
# Needs node (npm).
# ============================================================================
set -euo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
AAC="$(cd "$HERE/../.." && pwd)"
SITE="${1:?usage: vendor.sh <site-dir> [cache-dir]}"
CACHE="${2:-${XDG_CACHE_HOME:-$HOME/.cache}/aac-rs}"
LS="$AAC/crates/aac-web/src/listen.rs"
ORT_VERSION="$(sed -n 's/^pub const ORT_WEB_VERSION: &str = "\([^"]*\)";$/\1/p' "$LS")"
ORT_REV="$(sed -n 's/^pub const ORT_WEB_VENDOR_REV: &str = "\([^"]*\)";$/\1/p' "$LS")"
[ -n "$ORT_VERSION" ] || { echo "✗ ORT_WEB_VERSION not found in $LS" >&2; exit 1; }
[ -n "$ORT_REV" ] || { echo "✗ ORT_WEB_VENDOR_REV not found in $LS" >&2; exit 1; }
DIR="onnxruntime-web-$ORT_VERSION-$ORT_REV"
# ─── Pins ────────────────────────────────────────────────────────────────────
# The npm release, by its registry integrity (npm view [email protected]
# dist.integrity, read 2026-10-01; 1.30.0 is the latest stable, only
# 1.31.0-dev builds are newer).
PINNED_VERSION="1.30.0"
NPM_INTEGRITY="sha512-q0y+JrrtukXSzsBWEMccVfqX25LRmosXHF+CaRJmg8pZClzcV7svNc4rKY3jL02Vb7QmRMDs1SigqR4CXAfKYQ=="
# ONNX Runtime's LICENSE at tag v1.30.0 (commit f2c39fe2f838cf35ce7da92824f5a5e3ee6e88a7).
LICENSE_SHA256="2f07c72751aed99790b8a4869cf2311df85a860b22ded05fa22803587a48922c"
# ONNX Runtime's ThirdPartyNotices.txt at the same commit.
NOTICES_URL="https://raw.githubusercontent.com/microsoft/onnxruntime/f2c39fe2f838cf35ce7da92824f5a5e3ee6e88a7/ThirdPartyNotices.txt"
NOTICES_SHA256="143764b952fdb1a7c69ce653bfba74a7744d6a8a573bfb73e235fba356c83de3"
# What vendor/$VENDOR_PINNED holds, as a sha256 over its sorted file list and
# each file's sha256. Re-pin only together with a new ORT_WEB_VENDOR_REV.
VENDOR_PINNED="onnxruntime-web-1.30.0-aac4"
VENDOR_DIGEST="29e86994b4eb7224033963e085a7cb15673142db9db8ad7ac8bfae66df6bcfca"
# The files shipped from the package's dist/.
DIST_FILES=(ort.jspi.bundle.min.mjs ort-wasm-simd-threaded.jspi.wasm)
# The one link in the bundle: a page cited in an error message about
# cross-origin isolation, never fetched.
DOC_LINK="https://web.dev/cross-origin-isolation-guide/"
[ "$ORT_VERSION" = "$PINNED_VERSION" ] || {
echo "✗ listen.rs asks for onnxruntime-web $ORT_VERSION, but vendor.sh is pinned to $PINNED_VERSION —" >&2
echo " check the new release (licence, no network use, the graphs still run on WebGPU only), then re-pin PINNED_VERSION and NPM_INTEGRITY" >&2
exit 1
}
[ "$DIR" = "$VENDOR_PINNED" ] || {
echo "✗ listen.rs asks for $DIR, but vendor.sh is pinned to $VENDOR_PINNED — vendor it, check it, and re-pin VENDOR_PINNED and VENDOR_DIGEST" >&2
exit 1
}
sha256() { shasum -a 256 "$1" | cut -c1-64; }
fsize() { wc -c < "$1" | tr -d ' '; }
echo "▸ vendoring onnxruntime-web $ORT_VERSION ($DIR)"
mkdir -p "$CACHE"
# Tell (but do not fail) when npm has a newer stable release to review.
LATEST="$(npm view onnxruntime-web dist-tags.latest 2>/dev/null || true)"
if [ -n "$LATEST" ] && [ "$LATEST" != "$ORT_VERSION" ]; then
echo " note: npm's latest onnxruntime-web is $LATEST (pinned: $ORT_VERSION)"
fi
# The package, fetched and checked against its pinned integrity beside the
# cache entry, then moved in whole, so an interrupted run never leaves half a
# package that looks complete.
ORT_CACHE="$CACHE/onnxruntime-web-$ORT_VERSION"
if [ ! -f "$ORT_CACHE/package/package.json" ]; then
tmp="$(mktemp -d "$CACHE/npm.XXXXXX")"
(cd "$tmp" && npm pack --silent "onnxruntime-web@$ORT_VERSION" >/dev/null)
tgz="$(ls "$tmp"/*.tgz)"
got="sha512-$(openssl dgst -sha512 -binary "$tgz" | base64 | tr -d '\n')"
[ "$got" = "$NPM_INTEGRITY" ] || {
rm -rf "$tmp"
echo "✗ onnxruntime-web@$ORT_VERSION from npm is not the pinned tarball (got $got)" >&2
exit 1
}
(cd "$tmp" && tar xzf "$tgz")
rm -f "$tgz"
rm -rf "$ORT_CACHE"
mv "$tmp" "$ORT_CACHE"
fi
grep -q "\"version\": \"$ORT_VERSION\"" "$ORT_CACHE/package/package.json" || { echo "✗ cached onnxruntime-web is not $ORT_VERSION" >&2; exit 1; }
grep -q '"license": "MIT"' "$ORT_CACHE/package/package.json" || { echo "✗ onnxruntime-web $ORT_VERSION no longer declares MIT — review it" >&2; exit 1; }
[ "$(sha256 "$HERE/LICENSE.txt")" = "$LICENSE_SHA256" ] || { echo "✗ $HERE/LICENSE.txt is not ONNX Runtime's LICENSE at v1.30.0" >&2; exit 1; }
# ONNX Runtime's third-party notices: fetched to a temporary name and moved in
# only once their hash matches.
NOTICES="$CACHE/onnxruntime-$ORT_VERSION-ThirdPartyNotices.txt"
if [ ! -f "$NOTICES" ] || [ "$(sha256 "$NOTICES")" != "$NOTICES_SHA256" ]; then
rm -f "$NOTICES"
curl -fsSL "$NOTICES_URL" -o "$NOTICES.tmp"
[ "$(sha256 "$NOTICES.tmp")" = "$NOTICES_SHA256" ] || { rm -f "$NOTICES.tmp"; echo "✗ ThirdPartyNotices.txt ($NOTICES_URL) does not match its pinned sha256" >&2; exit 1; }
mv "$NOTICES.tmp" "$NOTICES"
fi
rm -rf "$SITE"/vendor/onnxruntime-web-*
OUT="$SITE/vendor/$DIR"
mkdir -p "$OUT"
cp "$HERE/worker.js" "$OUT/asr-worker.js"
cp "$HERE/LICENSE.txt" "$HERE/NOTICE.txt" "$OUT/"
cp "$NOTICES" "$OUT/ThirdPartyNotices.txt"
for f in "${DIST_FILES[@]}"; do cp "$ORT_CACHE/package/dist/$f" "$OUT/"; done
chmod 644 "$OUT"/*
# The runtime must never reach another site: no URL in the worker at all, and
# in the bundle none but the documentation link in one error message.
if grep -nE 'https?://' "$OUT/asr-worker.js"; then
echo "✗ the listener's worker names another site — review it" >&2; exit 1
fi
other="$(grep -oE "https?://[^\"'\` )]*" "$OUT/ort.jspi.bundle.min.mjs" | grep -vxF "$DOC_LINK" || true)"
[ -z "$other" ] || { echo "✗ ort.jspi.bundle.min.mjs names another site: $other — review it" >&2; exit 1; }
# Each runtime file must be exactly what listen.rs pins (RUNTIME_FILES): the
# app downloads it by that size and SHA-256.
PINS="$(sed -n 's/^ *RuntimeFile { name: "\([^"]*\)", bytes: \([0-9_]*\), sha256: "\([0-9a-f]*\)" },$/\1 \2 \3/p' "$LS" | awk '{ gsub(/_/, "", $2); print }')"
[ "$(printf '%s\n' "$PINS" | grep -c .)" = 3 ] || { echo "✗ RUNTIME_FILES in $LS should pin 3 files" >&2; exit 1; }
bad=0
while read -r name bytes want; do
f="$OUT/$name"
[ -s "$f" ] || { echo "✗ vendored runtime is missing $name" >&2; exit 1; }
got_bytes="$(fsize "$f")"; got="$(sha256 "$f")"
if [ "$got_bytes" != "$bytes" ] || [ "$got" != "$want" ]; then
echo "✗ vendor/$DIR/$name is $got_bytes bytes, sha256 $got;" >&2
echo " listen.rs RUNTIME_FILES pins $bytes bytes, sha256 $want." >&2
bad=1
fi
done <<< "$PINS"
[ "$bad" = 0 ] || { echo " Changed bytes need a new ORT_WEB_VENDOR_REV (a new URL), then new pins there and here." >&2; exit 1; }
DIGEST="$(cd "$OUT" && find . -type f | LC_ALL=C sort | while IFS= read -r f; do printf '%s %s\n' "$f" "$(sha256 "$f")"; done | shasum -a 256 | cut -c1-64)"
[ "$DIGEST" = "$VENDOR_DIGEST" ] || {
echo "✗ vendor/$DIR differs from what was pinned (got $DIGEST)." >&2
echo " Its URLs are cached as immutable: bump ORT_WEB_VENDOR_REV in listen.rs," >&2
echo " then set VENDOR_PINNED and VENDOR_DIGEST in $HERE/vendor.sh." >&2
exit 1
}
echo "✓ vendor/$DIR ($(du -sh "$OUT" | cut -f1))"