# Recombine the HEIC codec

The notice page links the source kit, `application-code.tar.gz`, and `application-assets.tar.gz`. Download those files over HTTP. Check each archive is at most 26214400 bytes, and check every SHA-256 in `releases/<releaseId>/manifest.json`.

`applicationArchives` lists both archives. `applicationFiles` is their union, and each path appears once. Extract both into the same directory, and make a receipts directory before you run the tools:

```sh
mkdir -p app receipts
tar -xzf application-code.tar.gz -C app
tar -xzf application-assets.tar.gz -C app
chmod +x 1.6.5/scripts/reproduce.sh 1.6.5/scripts/rebuild-one.sh
```

The parent worker is the file named by `workerPath` in that manifest. It handles only `{ kind: "convert", id, session, blob, mode }` and answers `{ kind: "result", id, session, jpeg }` or `{ kind: "error", id, session, message }`. `mode` is `preview` or `upload`. Upload scales a longest edge that is already over 2560 down to 2560 and does not enlarge a smaller image. JPEG quality is 0.82. The parent ignores a request whose kind is not `convert`, including a request with no id. It still runs a `convert` request when `id` is absent. The application queue ignores an unrelated reply, and it ignores a diagnostic reply that has no id. A reply whose id matches an in-flight job but whose kind is unknown fails that job.

Use your own HEIC file. Preview keeps the image's pixel size. Upload output keeps a longest edge of at most 2560, without upscaling, and uses JPEG quality 0.82.

The codec chunk is `codecPath`. The worker loads it with one dynamic import. Leave the page Content-Security-Policy as the server sent it.

## Tools

Use the tools named in `1.6.5/source-manifest.json`:

- Emscripten 3.1.64, commit `a1fe3902bf73a3802eae0357d273d0e37ea79898`
- CMake 3.31.8
- esbuild 0.25.12

`generatedLibrarySha256` is `8c11f36a8d03c9b08f79fc11cef01585a54d0699da3ddb4bb6dac9c68920a557`. The unmodified derivative CSP is `caaedd6dc268e8cc4c53a2131e78b68cc23563b3522f0346cbeef803af18e2b7`. The preferred wrapper archive is `1.6.5/sources/heic-to-1.6.5-eastbook-rebuild-1.tgz`. Its published SHA-256 stays `aa3c5d7282df746690ebcf5b025b9610d6067e04dfa55b485d56242daee7b060`. Do not replace that file. A changed wrapper is a new archive with a new SHA-256.

To rebuild the C++ library and the original CSP from the downloaded archives:

```sh
1.6.5/scripts/reproduce.sh \
  --libheif 1.6.5/sources/libheif-1.23.5.tar.gz \
  --libheif-sha256 fd9036064c4432f0550d15072ddf34956a248279ee9aeaff0fba3fa0f77d8f1a \
  --libde265 1.6.5/sources/libde265-1.0.16.tar.gz \
  --libde265-sha256 b92beb6b53c346db9a8fae968d686ab706240099cdd5aff87777362d668b0de7 \
  --wrapper 1.6.5/sources/heic-to-1.6.5-eastbook-rebuild-1.tgz \
  --wrapper-sha256 aa3c5d7282df746690ebcf5b025b9610d6067e04dfa55b485d56242daee7b060 \
  --expect-lib-sha256 8c11f36a8d03c9b08f79fc11cef01585a54d0699da3ddb4bb6dac9c68920a557 \
  --expect-csp-sha256 caaedd6dc268e8cc4c53a2131e78b68cc23563b3522f0346cbeef803af18e2b7 \
  --work /path/to/fresh-work \
  --emsdk /path/to/emsdk \
  --cmake /path/to/cmake \
  --esbuild /path/to/node_modules/esbuild \
  --receipt-dir receipts
```

`--work` must be new. A bad checksum exits 2. A library or CSP hash miss exits 3.

If `libheif.js` already matches `generatedLibrarySha256`, bundle the CSP without repeating the C++ build:

```sh
node 1.6.5/scripts/bundle-csp.mjs \
  --lib /path/to/libheif.js \
  --lib-sha256 8c11f36a8d03c9b08f79fc11cef01585a54d0699da3ddb4bb6dac9c68920a557 \
  --wrapper 1.6.5/sources/heic-to-1.6.5-eastbook-rebuild-1.tgz \
  --wrapper-sha256 aa3c5d7282df746690ebcf5b025b9610d6067e04dfa55b485d56242daee7b060 \
  --esbuild /path/to/node_modules/esbuild \
  --work /path/to/fresh-bundle \
  --out /path/to/heic-to-csp.js
```

## Change the preferred source

Extract the published wrapper, edit only `src/index.js` inside `heicTo`, then pack a new archive. Do not append code to a generated CSP. Keep `export { isHeic, heicTo }` and `heicTo({ blob, type: "bitmap" })`.

```sh
mkdir -p source-edit
tar -xzf 1.6.5/sources/heic-to-1.6.5-eastbook-rebuild-1.tgz -C source-edit
```

A diagnostic posted from inside `heicTo` must not include `id` or `session`:

```js
if (typeof postMessage === "function") postMessage({ kind: "eastbook-source-diagnostic" });
```

Repack the same top-level directory name and record the new SHA-256. The published archive hash above does not change. The one-line diagnostic archive built from that edit is SHA-256 `5ffde4936b13d172bb62eaf74023e97d13ed427f26dafaacf7d838e808505f36`. Any other edit has a different hash. Pass that new hash to `bundle-csp.mjs`.

```sh
COPYFILE_DISABLE=1 tar -czf heic-to-modified.tgz -C source-edit heic-to-1.6.5-eastbook-rebuild-1
shasum -a 256 heic-to-modified.tgz
node 1.6.5/scripts/bundle-csp.mjs \
  --lib /path/to/libheif.js \
  --lib-sha256 8c11f36a8d03c9b08f79fc11cef01585a54d0699da3ddb4bb6dac9c68920a557 \
  --wrapper heic-to-modified.tgz \
  --wrapper-sha256 <sha256 printed above> \
  --esbuild /path/to/node_modules/esbuild \
  --work /path/to/fresh-modified-bundle \
  --out /path/to/heic-to-csp.js
```

## Build the parent override

Save this program as `generate-override.mjs`. It reads the manifest-selected parent worker from the extracted application directory and writes `override-worker.js`. The rebuilt CSP is placed in a Blob module inside that worker. The Blob URL is revoked after `import()` settles. The convert/result protocol stays the parent's.

```js
import { readFileSync, writeFileSync } from "node:fs";
import path from "node:path";

const manifestPath = process.argv[2];
const appDir = process.argv[3];
const cspPath = process.argv[4];
const outPath = process.argv[5];
if (!manifestPath || !appDir || !cspPath || !outPath) {
  process.stderr.write("usage: node generate-override.mjs manifest.json appDir csp.js override-worker.js\n");
  process.exit(2);
}
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
const parentPath = path.join(appDir, manifest.workerPath);
const parent = readFileSync(parentPath, "utf8");
const codecName = path.posix.basename(manifest.codecPath);
const needle = "import(\"./" + codecName + "\")";
if (!parent.includes(needle)) {
  process.stderr.write("parent import not found\n");
  process.exit(2);
}
const csp = JSON.stringify(readFileSync(cspPath, "utf8"));
const replacement = "(async()=>{const u=URL.createObjectURL(new Blob([" + csp + "],{type:\"text/javascript\"}));try{return await import(u)}finally{URL.revokeObjectURL(u)}})()";
const next = parent.replace(needle, () => replacement);
if (next === parent) {
  process.stderr.write("parent import not replaced\n");
  process.exit(2);
}
writeFileSync(outPath, next);
```

```sh
node generate-override.mjs releases/<releaseId>/manifest.json app /path/to/heic-to-csp.js override-worker.js
```

## Install it with DevTools Local Overrides

Use Chrome DevTools Local Overrides on the parent worker response. Do not change the page Content-Security-Policy, and do not replace `window.Worker`.

1. Open DevTools, then Sources, then Overrides. Choose an empty folder and allow access.
2. Reload the app. In Network, select the request whose path ends with the manifest `workerPath`.
3. Right-click that request and choose Override content.
4. Replace the whole editor buffer with `override-worker.js` and save (Command-S or Ctrl-S).
5. Reload the page. Open your own HEIC file. The diagnostic message has no `id` and no `session`. Preview keeps the source pixel size. Upload mode scales a longest edge down only when it is over 2560, does not upscale, and encodes JPEG at quality 0.82.
6. Clear Enable Local Overrides, then reload. The page uses the original worker again.
7. Delete the override file in the Overrides pane, then reload once more and confirm the original worker response is back.

Use a new worker after `terminate()`. The library keeps an internal worker, and a terminated module hangs if it is reused.

The 30000 ms limit, FIFO queue, abort, and blob cleanup stay in the app. Do not deploy the experiment. Keep each immutable Pages release while its objects are still distributed. Before deleting one, mirror the complete source, licenses, recipes, manifests, and both Application Code archives to the existing durable location, and keep the same correspondence and public access. Do not add hosting or deletion automation.
