コンテンツにスキップ

組込み

showcase の端末は @aletheia-works/formicarium の公開 export だけを使います: /browser の createSession と、package の root の createCommandSession と createInstaller です(installer は、nano のようにどの page にも入れるツールを、 何も表示せずに導入します)。自分の page でも同じことができます。

この guide は formicarium 0.9.0 の pack(候補の版)に合わせて書いています。npm から入れるには:

        npm install @aletheia-works/formicarium@0.9.0
      

npm の page

  • 同じ origin。 Worker(runtime/web/package-worker.bundle.js)と assets/paludarium/ のコア(loader の index.mjs、その隣のモジュール、 paludarium.wasm、build-info.json)を page と同じ origin から配ります。 Worker・loader・wasm・ビルド情報の絶対 URL を assets に渡し、ほかのモジュールは loader の隣から読み込みます。
  • クロスオリジン分離。 COOP same-origin と COEP require-corp を送るか、 静的な hosting では coi-serviceworker などの service worker を使います。
  • CSP。 同じ origin の script と Worker、WebAssembly の compile を許します。 たとえば script-src 'self' 'wasm-unsafe-eval'; worker-src 'self'; connect-src 'self' です。blob: は要りません。コアは SHA-256 を照合した後に 資産の URL から読み込むので、信頼できる origin から配ります。
  • guest。 同じ origin の guest の file と、その SHA256 と build-info.json を書いた catalog(GuestCatalog)を使います。install <tool> はすべての bytes を確かめてから guest を登録します。

この code は、このサイトを作った pack の型で型検査しています。

embedding.ts
// The embedding guide shows this file as it is; the build type-checks it
// against the types of the candidate's pack.
import {
createCommandSession,
decodeUtf8,
type GuestCatalog,
} from '@aletheia-works/formicarium';
import { createSession } from '@aletheia-works/formicarium/browser';
/**
* Installs one tool from a catalog and prints its version. `packageBase` is
* the same-origin URL of the package files; the page must be cross-origin
* isolated (COOP/COEP headers or a service worker such as coi-serviceworker).
*/
export async function toolVersion(
packageBase: URL,
catalog: GuestCatalog,
tool: string,
): Promise<string> {
const session = await createSession({
cwd: '/work/app',
entries: [
{ path: '/work', type: 'dir', mode: 0o755 },
{ path: '/work/app', type: 'dir', mode: 0o755 },
],
assets: {
loaderURL: new URL('assets/paludarium/index.mjs', packageBase),
wasmURL: new URL('assets/paludarium/paludarium.wasm', packageBase),
workerURL: new URL('runtime/web/package-worker.bundle.js', packageBase),
buildInfoURL: new URL('assets/paludarium/build-info.json', packageBase),
},
});
try {
const shell = createCommandSession(session, { catalog });
await shell.execute(`install ${tool}`, {
onProgress: (progress) =>
console.log(progress.phase, progress.processedBytes),
});
const result = await shell.execute(`${tool} --version`);
return decodeUtf8(result.stdout);
} finally {
await session.dispose();
}
}

ツールを導入済みの端末を開くには、command session を作る前に createInstaller でツールを導入します。install <tool> と同じ確かめをしますが、何も書き出さない ので、利用者には導入の行が見えません。導入したツールは、command session で名前を 打てば動き、どの guest の実行からも /usr/bin/<tool> として見えるので、ツールから 別のツールを起動できます(たとえば version control のツールが editor を開く)。 catalog の代わりに手元にある guest の bytes を登録するには、validateGuest で 確かめてから、その結果を registerValidatedGuest に渡します。

preinstall.ts
// The embedding guide shows this file as it is; the build type-checks it
// against the types of the candidate's pack.
import {
type CommandSession,
createCommandSession,
createInstaller,
type GuestCatalog,
type Session,
} from '@aletheia-works/formicarium';
/**
* Installs `tools` before the user types anything, without writing to the
* terminal, then returns the command session for the terminal. Each tool runs
* by name there, and other guests find it as `/usr/bin/<tool>` (for example a
* version control tool that opens an editor).
*/
export async function terminalWithTools(
session: Session,
catalog: GuestCatalog,
tools: readonly string[],
): Promise<CommandSession> {
const installer = createInstaller(session, catalog);
for (const tool of tools) await installer.install(tool);
return createCommandSession(session, { catalog });
}

CommandSession.execute は command ごとに元の stdout と stderr の bytes を 返します。guest の実行では同じ bytes が onOutput でも少しずつ届くので、どちらか 一方だけを書きます。表示のときは bytes を文字に decode し、HTML として扱いません。 onProgress は全体の大きさが分からないとき totalBytes: null を返し、0 とは 区別します。取り消すには AbortSignal を渡し、page を離れるときは session を dispose します。