diff --git a/CHANGELOG.md b/CHANGELOG.md index 14536ab..a3c140c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,58 @@ All notable changes to Shade are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [4.0.2] — 2026-05-03 — Consumer-strict reader-shape fixes + +`4.0.1` shipped the `tsc --noEmit` gate that compiles each package +internally against `lib: ["ES2022"]`. That gate did not catch types +that only fail when *consumer* code (running with `lib: ["DOM"]` + +`exactOptionalPropertyTypes`) tries to assign a native browser type +into one of our locally-defined narrower types. + +This release adds a consumer-strict smoke test to the pre-publish +gate and fixes every collision that smoke uncovered. + +### Fixed + +#### `@shade/files` +- `inline-threshold.ts`: rewrote the local `MinimalReader` interface + as an explicit disjoint union (`{ done: false; value: T } | { done: + true; value?: T | undefined }`) so it accepts every native reader + shape — `bun-types` (`value?: undefined`), `lib.dom` (`value?: T`), + and `node:stream/web`. The previous flat shape was rejected by + consumer projects with `exactOptionalPropertyTypes: true` because + the present-branch required `value: T`. **Fixes "Type + ReadableStreamReadResult is not assignable to + { value: Uint8Array | undefined; done: boolean }".** +- `client/streams-bridge.ts`, `server/streams-bridge.ts`: stash the + `setTimeout(...)` return value in a local before calling `.unref?.()` + through an explicit `{ unref?: () => void }` cast. The previous + fluent `.unref?.()` failed under `lib: ["DOM"]` because DOM types + `setTimeout` to `number`, which has no `.unref` even as an optional + property. + +#### `@shade/sdk` +- `background.ts`: same `setTimeout` / `setInterval` `.unref?.()` fix. + +### Tooling + +- New `tests/consumer-strict/` — a tiny "as if I were a downstream app" + TypeScript project with its own `tsconfig.json`: + `lib: ["ES2022", "DOM", "DOM.Iterable"]`, `types: ["bun-types"]`, + `exactOptionalPropertyTypes: true`, `strict: true`, + `paths`-mapped to the workspace's `packages/*/src/index.ts`. + Three smoke files exercise `@shade/files`, `@shade/sdk`, and + `@shade/key-transparency` against the consumer-strict tsconfig. +- `scripts/typecheck-all.ts` now runs the consumer-strict smoke after + the per-package internal type-check. Both must pass before + `prepublish:check` (and therefore `publish:dry` / `publish:all`) + succeeds. + +### Migration + +`4.0.1 → 4.0.2` is wire-compatible and source-compatible. No API shape +changed; only internal typing was tightened. + ## [4.0.1] — 2026-05-03 — Strict-TS publishability fixes `4.0.0` shipped TypeScript source files as the published `main` / diff --git a/packages/shade-cli/package.json b/packages/shade-cli/package.json index 3e1b3b7..9931e6a 100644 --- a/packages/shade-cli/package.json +++ b/packages/shade-cli/package.json @@ -1,6 +1,6 @@ { "name": "@shade/cli", - "version": "4.0.1", + "version": "4.0.2", "type": "module", "main": "src/cli.ts", "bin": { diff --git a/packages/shade-core/package.json b/packages/shade-core/package.json index 4cae89c..7c193ed 100644 --- a/packages/shade-core/package.json +++ b/packages/shade-core/package.json @@ -1,6 +1,6 @@ { "name": "@shade/core", - "version": "4.0.1", + "version": "4.0.2", "type": "module", "main": "src/index.ts", "types": "src/index.ts", diff --git a/packages/shade-crypto-web/package.json b/packages/shade-crypto-web/package.json index edb11d8..935efd0 100644 --- a/packages/shade-crypto-web/package.json +++ b/packages/shade-crypto-web/package.json @@ -1,6 +1,6 @@ { "name": "@shade/crypto-web", - "version": "4.0.1", + "version": "4.0.2", "type": "module", "main": "src/index.ts", "types": "src/index.ts", diff --git a/packages/shade-dashboard/package.json b/packages/shade-dashboard/package.json index 664bddd..f528669 100644 --- a/packages/shade-dashboard/package.json +++ b/packages/shade-dashboard/package.json @@ -1,6 +1,6 @@ { "name": "@shade/dashboard", - "version": "4.0.1", + "version": "4.0.2", "type": "module", "scripts": { "dev": "vite", diff --git a/packages/shade-files/package.json b/packages/shade-files/package.json index c37f9d3..948eda2 100644 --- a/packages/shade-files/package.json +++ b/packages/shade-files/package.json @@ -1,6 +1,6 @@ { "name": "@shade/files", - "version": "4.0.1", + "version": "4.0.2", "type": "module", "main": "src/index.ts", "types": "src/index.ts", diff --git a/packages/shade-files/src/client/inline-threshold.ts b/packages/shade-files/src/client/inline-threshold.ts index f51742d..b0a2a56 100644 --- a/packages/shade-files/src/client/inline-threshold.ts +++ b/packages/shade-files/src/client/inline-threshold.ts @@ -161,15 +161,33 @@ async function peekStream(stream: ReadableStream): Promise; +/** + * Structural mirror of WHATWG `ReadableStreamDefaultReader`. + * + * The disjoint union shape with `value?: T | undefined` is the lowest + * common denominator across every lib environment we care about: + * - `bun-types` emits `{ done: true; value?: undefined }` + * - `lib.dom` emits `{ done: true; value?: T }` + * - `node:stream/web` emits the union form + * + * `value?: T | undefined` is assignable from all three. A flat + * `{ value?: T; done: boolean }` is rejected by + * `exactOptionalPropertyTypes` because the present branches require + * `value: T`. Defining it as an explicit union avoids the trap. + */ +type MinimalReadResult = + | { done: false; value: T } + | { done: true; value?: T | undefined }; + +interface MinimalReader { + read(): Promise>; cancel(reason?: unknown): Promise; releaseLock(): void; } function reconstructStream( prefix: Uint8Array[], - reader: MinimalReader, + reader: MinimalReader, ): ReadableStream { let prefixIdx = 0; return new ReadableStream({ diff --git a/packages/shade-files/src/client/streams-bridge.ts b/packages/shade-files/src/client/streams-bridge.ts index 733e9fc..5b5a91f 100644 --- a/packages/shade-files/src/client/streams-bridge.ts +++ b/packages/shade-files/src/client/streams-bridge.ts @@ -142,13 +142,14 @@ export async function createClientStreamsBridge( } parked.set(readStreamId, arrival); - setTimeout(() => { + const t = setTimeout(() => { const stale = parked.get(readStreamId); if (stale === arrival) { parked.delete(readStreamId); void handle.abort('rpc-timeout').catch(() => undefined); } - }, parkedReadTtlMs).unref?.(); + }, parkedReadTtlMs); + (t as unknown as { unref?: () => void }).unref?.(); }); function cleanupWaiter(w: PendingReadWaiter): void { diff --git a/packages/shade-files/src/server/streams-bridge.ts b/packages/shade-files/src/server/streams-bridge.ts index ac138a7..5fda351 100644 --- a/packages/shade-files/src/server/streams-bridge.ts +++ b/packages/shade-files/src/server/streams-bridge.ts @@ -168,13 +168,14 @@ export async function createServerStreamsBridge( // No waiter yet — park. parked.set(writeId, arrived); - setTimeout(() => { + const parkTimer = setTimeout(() => { const stale = parked.get(writeId); if (stale === arrived) { parked.delete(writeId); void handle.abort('rpc-timeout').catch(() => undefined); } - }, parkedWriteTtlMs).unref?.(); + }, parkedWriteTtlMs); + (parkTimer as unknown as { unref?: () => void }).unref?.(); }); function cleanupWaiter(w: PendingWaiter): void { diff --git a/packages/shade-inbox-server/package.json b/packages/shade-inbox-server/package.json index 6612c13..3c38609 100644 --- a/packages/shade-inbox-server/package.json +++ b/packages/shade-inbox-server/package.json @@ -1,6 +1,6 @@ { "name": "@shade/inbox-server", - "version": "4.0.1", + "version": "4.0.2", "type": "module", "main": "src/index.ts", "types": "src/index.ts", diff --git a/packages/shade-inbox/package.json b/packages/shade-inbox/package.json index f1cf85f..393760e 100644 --- a/packages/shade-inbox/package.json +++ b/packages/shade-inbox/package.json @@ -1,6 +1,6 @@ { "name": "@shade/inbox", - "version": "4.0.1", + "version": "4.0.2", "type": "module", "main": "src/index.ts", "types": "src/index.ts", diff --git a/packages/shade-key-transparency/package.json b/packages/shade-key-transparency/package.json index 995f6fc..f2f95fb 100644 --- a/packages/shade-key-transparency/package.json +++ b/packages/shade-key-transparency/package.json @@ -1,6 +1,6 @@ { "name": "@shade/key-transparency", - "version": "4.0.1", + "version": "4.0.2", "type": "module", "main": "src/index.ts", "types": "src/index.ts", diff --git a/packages/shade-keychain/package.json b/packages/shade-keychain/package.json index 6706ba2..bdce624 100644 --- a/packages/shade-keychain/package.json +++ b/packages/shade-keychain/package.json @@ -1,6 +1,6 @@ { "name": "@shade/keychain", - "version": "4.0.1", + "version": "4.0.2", "type": "module", "main": "src/index.ts", "types": "src/index.ts", diff --git a/packages/shade-observability/package.json b/packages/shade-observability/package.json index e9cbde8..43aa3b3 100644 --- a/packages/shade-observability/package.json +++ b/packages/shade-observability/package.json @@ -1,6 +1,6 @@ { "name": "@shade/observability", - "version": "4.0.1", + "version": "4.0.2", "type": "module", "main": "src/index.ts", "types": "src/index.ts", diff --git a/packages/shade-observer/package.json b/packages/shade-observer/package.json index ee5c552..a770cd5 100644 --- a/packages/shade-observer/package.json +++ b/packages/shade-observer/package.json @@ -1,6 +1,6 @@ { "name": "@shade/observer", - "version": "4.0.1", + "version": "4.0.2", "type": "module", "main": "src/index.ts", "types": "src/index.ts", diff --git a/packages/shade-proto/package.json b/packages/shade-proto/package.json index 5754821..502e834 100644 --- a/packages/shade-proto/package.json +++ b/packages/shade-proto/package.json @@ -1,6 +1,6 @@ { "name": "@shade/proto", - "version": "4.0.1", + "version": "4.0.2", "type": "module", "main": "src/index.ts", "types": "src/index.ts", diff --git a/packages/shade-recovery/package.json b/packages/shade-recovery/package.json index 0691237..0c41a35 100644 --- a/packages/shade-recovery/package.json +++ b/packages/shade-recovery/package.json @@ -1,6 +1,6 @@ { "name": "@shade/recovery", - "version": "4.0.1", + "version": "4.0.2", "type": "module", "main": "src/index.ts", "types": "src/index.ts", diff --git a/packages/shade-sdk/package.json b/packages/shade-sdk/package.json index 8dd8d42..a20c293 100644 --- a/packages/shade-sdk/package.json +++ b/packages/shade-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@shade/sdk", - "version": "4.0.1", + "version": "4.0.2", "type": "module", "main": "src/index.ts", "types": "src/index.ts", diff --git a/packages/shade-sdk/src/background.ts b/packages/shade-sdk/src/background.ts index 5b84ad6..a2ecc9d 100644 --- a/packages/shade-sdk/src/background.ts +++ b/packages/shade-sdk/src/background.ts @@ -96,7 +96,7 @@ export class BackgroundTasks { this.hooks.onError?.(err as Error, 'prune-files'); } }, this.pruneFilesIntervalMs); - this.pruneFilesTimer.unref?.(); + (this.pruneFilesTimer as unknown as { unref?: () => void }).unref?.(); } stop(): void { diff --git a/packages/shade-server/package.json b/packages/shade-server/package.json index 4e658cc..9121423 100644 --- a/packages/shade-server/package.json +++ b/packages/shade-server/package.json @@ -1,6 +1,6 @@ { "name": "@shade/server", - "version": "4.0.1", + "version": "4.0.2", "type": "module", "main": "src/index.ts", "types": "src/index.ts", diff --git a/packages/shade-storage-encrypted/package.json b/packages/shade-storage-encrypted/package.json index 1572f7e..0379e3a 100644 --- a/packages/shade-storage-encrypted/package.json +++ b/packages/shade-storage-encrypted/package.json @@ -1,6 +1,6 @@ { "name": "@shade/storage-encrypted", - "version": "4.0.1", + "version": "4.0.2", "type": "module", "main": "src/index.ts", "types": "src/index.ts", diff --git a/packages/shade-storage-postgres/package.json b/packages/shade-storage-postgres/package.json index 674e550..04bb76f 100644 --- a/packages/shade-storage-postgres/package.json +++ b/packages/shade-storage-postgres/package.json @@ -1,6 +1,6 @@ { "name": "@shade/storage-postgres", - "version": "4.0.1", + "version": "4.0.2", "type": "module", "main": "src/index.ts", "types": "src/index.ts", diff --git a/packages/shade-storage-sqlite/package.json b/packages/shade-storage-sqlite/package.json index 28dd5b3..04060d0 100644 --- a/packages/shade-storage-sqlite/package.json +++ b/packages/shade-storage-sqlite/package.json @@ -1,6 +1,6 @@ { "name": "@shade/storage-sqlite", - "version": "4.0.1", + "version": "4.0.2", "type": "module", "main": "src/index.ts", "types": "src/index.ts", diff --git a/packages/shade-streams/package.json b/packages/shade-streams/package.json index 641e431..a29b023 100644 --- a/packages/shade-streams/package.json +++ b/packages/shade-streams/package.json @@ -1,6 +1,6 @@ { "name": "@shade/streams", - "version": "4.0.1", + "version": "4.0.2", "type": "module", "main": "src/index.ts", "types": "src/index.ts", diff --git a/packages/shade-transfer/package.json b/packages/shade-transfer/package.json index b68f0fe..5ea647f 100644 --- a/packages/shade-transfer/package.json +++ b/packages/shade-transfer/package.json @@ -1,6 +1,6 @@ { "name": "@shade/transfer", - "version": "4.0.1", + "version": "4.0.2", "type": "module", "main": "src/index.ts", "types": "src/index.ts", diff --git a/packages/shade-transport-bridge/package.json b/packages/shade-transport-bridge/package.json index 4087af1..717d584 100644 --- a/packages/shade-transport-bridge/package.json +++ b/packages/shade-transport-bridge/package.json @@ -1,6 +1,6 @@ { "name": "@shade/transport-bridge", - "version": "4.0.1", + "version": "4.0.2", "type": "module", "main": "src/index.ts", "types": "src/index.ts", diff --git a/packages/shade-transport-webrtc/package.json b/packages/shade-transport-webrtc/package.json index a5e0caf..f46ab35 100644 --- a/packages/shade-transport-webrtc/package.json +++ b/packages/shade-transport-webrtc/package.json @@ -1,6 +1,6 @@ { "name": "@shade/transport-webrtc", - "version": "4.0.1", + "version": "4.0.2", "type": "module", "main": "src/index.ts", "types": "src/index.ts", diff --git a/packages/shade-transport/package.json b/packages/shade-transport/package.json index d982487..74b7b9e 100644 --- a/packages/shade-transport/package.json +++ b/packages/shade-transport/package.json @@ -1,6 +1,6 @@ { "name": "@shade/transport", - "version": "4.0.1", + "version": "4.0.2", "type": "module", "main": "src/index.ts", "types": "src/index.ts", diff --git a/packages/shade-widgets/package.json b/packages/shade-widgets/package.json index 6df21d3..8a5acb4 100644 --- a/packages/shade-widgets/package.json +++ b/packages/shade-widgets/package.json @@ -1,6 +1,6 @@ { "name": "@shade/widgets", - "version": "4.0.1", + "version": "4.0.2", "type": "module", "main": "src/index.ts", "types": "src/index.ts", diff --git a/scripts/typecheck-all.ts b/scripts/typecheck-all.ts index 9ceee8f..3c75517 100644 --- a/scripts/typecheck-all.ts +++ b/scripts/typecheck-all.ts @@ -61,6 +61,39 @@ for (const pkg of packages) { } console.log(); + +// Step 2 — consumer-strict smoke. Compiles a tiny "as if I were a +// downstream app" project against our public API surface under the +// consumer-likely tsconfig (`lib: ["DOM"]` + `exactOptionalPropertyTypes`). +// Catches type-bugs that ONLY surface when our internal narrower types +// meet a consumer's standard-library types — the class of bug `tsc` +// inside our own packages does not see (because our packages compile +// against `lib: ["ES2022"]` only). +if (filter.size === 0) { + console.log('Consumer-strict smoke (lib: DOM, exactOptional, paths→workspace) ...'); + const consumerDir = join(ROOT, 'tests', 'consumer-strict'); + if (existsSync(join(consumerDir, 'tsconfig.json'))) { + const proc = Bun.spawnSync(['bunx', 'tsc', '--noEmit', '-p', 'tsconfig.json'], { + cwd: consumerDir, + stdout: 'pipe', + stderr: 'pipe', + }); + const out = (proc.stdout.toString() + proc.stderr.toString()) + .split('\n') + .filter((l) => !/^Resolving|^Resolved|^Saved/.test(l)) + .join('\n') + .trim(); + if (proc.exitCode === 0 && out.length === 0) { + console.log(' ✓ consumer-strict'); + } else { + failures++; + failed.push({ pkg: 'consumer-strict', out }); + console.log(' ✗ consumer-strict'); + } + } + console.log(); +} + if (failures === 0) { console.log(`All ${packages.length} packages type-check cleanly.`); process.exit(0); diff --git a/tests/consumer-strict/tsconfig.json b/tests/consumer-strict/tsconfig.json new file mode 100644 index 0000000..9f407e5 --- /dev/null +++ b/tests/consumer-strict/tsconfig.json @@ -0,0 +1,45 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": true, + "noImplicitOverride": true, + "exactOptionalPropertyTypes": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "esModuleInterop": true, + "skipLibCheck": true, + "noEmit": true, + "types": ["bun-types"], + "ignoreDeprecations": "6.0", + "baseUrl": ".", + "paths": { + "@shade/core": ["../../packages/shade-core/src/index.ts"], + "@shade/proto": ["../../packages/shade-proto/src/index.ts"], + "@shade/crypto-web": ["../../packages/shade-crypto-web/src/index.ts"], + "@shade/observability": ["../../packages/shade-observability/src/index.ts"], + "@shade/keychain": ["../../packages/shade-keychain/src/index.ts"], + "@shade/key-transparency": ["../../packages/shade-key-transparency/src/index.ts"], + "@shade/storage-sqlite": ["../../packages/shade-storage-sqlite/src/index.ts"], + "@shade/storage-postgres": ["../../packages/shade-storage-postgres/src/index.ts"], + "@shade/storage-encrypted": ["../../packages/shade-storage-encrypted/src/index.ts"], + "@shade/streams": ["../../packages/shade-streams/src/index.ts"], + "@shade/transport": ["../../packages/shade-transport/src/index.ts"], + "@shade/transport-bridge": ["../../packages/shade-transport-bridge/src/index.ts"], + "@shade/transport-webrtc": ["../../packages/shade-transport-webrtc/src/index.ts"], + "@shade/server": ["../../packages/shade-server/src/index.ts"], + "@shade/inbox-server": ["../../packages/shade-inbox-server/src/index.ts"], + "@shade/inbox": ["../../packages/shade-inbox/src/index.ts"], + "@shade/transfer": ["../../packages/shade-transfer/src/index.ts"], + "@shade/files": ["../../packages/shade-files/src/index.ts"], + "@shade/recovery": ["../../packages/shade-recovery/src/index.ts"], + "@shade/observer": ["../../packages/shade-observer/src/index.ts"], + "@shade/dashboard": ["../../packages/shade-dashboard/src/index.ts"], + "@shade/sdk": ["../../packages/shade-sdk/src/index.ts"], + "@shade/widgets": ["../../packages/shade-widgets/src/index.ts"] + } + }, + "include": ["./*.ts"] +} diff --git a/tests/consumer-strict/use-files.ts b/tests/consumer-strict/use-files.ts new file mode 100644 index 0000000..a8c7326 --- /dev/null +++ b/tests/consumer-strict/use-files.ts @@ -0,0 +1,43 @@ +/** + * Consumer-strict smoke for `@shade/files`. + * + * Compiled with `lib: ["ES2022", "DOM"]` + `exactOptionalPropertyTypes` + + * `skipLibCheck: false` to mimic a downstream consumer like Dispatch. + * Catches the class of bug where our internal narrower types (e.g. a + * locally-defined `MinimalReader`) reject native browser types + * (e.g. `ReadableStreamDefaultReader`) the consumer would naturally + * pass in. + * + * If this file fails to compile, the published packages will fail in + * any consumer's strict tsc — pre-publish gate must catch it. + */ +import { decideInline, type WriteSource } from '@shade/files'; + +declare const blob: Blob; +declare const stream: ReadableStream; +declare const bytes: Uint8Array; + +async function smoke(): Promise { + // Each branch of WriteSource must round-trip through decideInline() + // when given the natively-typed inputs a browser app would supply. + const sources: WriteSource[] = [ + bytes, + blob, + stream, + { stream, size: 1024 }, + { stream, size: 1024, contentType: 'image/png' }, + ]; + for (const src of sources) { + const decision = await decideInline(src); + if (decision.kind === 'streams') { + const reader = decision.stream.getReader(); + const { value, done } = await reader.read(); + if (!done && value !== undefined) { + void value.byteLength; + } + reader.releaseLock(); + } + } +} + +void smoke; diff --git a/tests/consumer-strict/use-key-transparency.ts b/tests/consumer-strict/use-key-transparency.ts new file mode 100644 index 0000000..872d9f0 --- /dev/null +++ b/tests/consumer-strict/use-key-transparency.ts @@ -0,0 +1,42 @@ +/** + * Consumer-strict smoke for `@shade/key-transparency`. + * + * The package was the source of the 4 noUnusedLocals + the IndexProofWire + * privacy bug in 4.0.0. This smoke imports every public type and + * exercises the witness-fetcher contract that the SDK plugs into. + */ +import { + LightWitness, + type SignedTreeHead, + type STHWire, + type WitnessFetcher, +} from '@shade/key-transparency'; + +declare const crypto: import('@shade/core').CryptoProvider; +declare const logPublicKey: Uint8Array; + +async function smoke(): Promise { + const fetcher: WitnessFetcher = { + async fetchLatestSTH(): Promise { + const res = await fetch('https://shade.example.com/v1/kt/sth'); + return (await res.json()) as STHWire; + }, + async fetchConsistencyProof( + from: number, + to: number, + ): Promise<{ proof: string[] }> { + const res = await fetch( + `https://shade.example.com/v1/kt/consistency?from=${from}&to=${to}`, + ); + return (await res.json()) as { proof: string[] }; + }, + }; + const witness = new LightWitness({ crypto, logPublicKey, fetcher }); + void witness; +} + +void smoke; + +// Verify the type is reachable with no `any` leak. +declare const sth: SignedTreeHead; +void sth.treeSize; diff --git a/tests/consumer-strict/use-sdk.ts b/tests/consumer-strict/use-sdk.ts new file mode 100644 index 0000000..12a8f85 --- /dev/null +++ b/tests/consumer-strict/use-sdk.ts @@ -0,0 +1,50 @@ +/** + * Consumer-strict smoke for `@shade/sdk`. + * + * Exercises the high-level `createShade()` flow + the V3.x opt-in + * surfaces (KT, WebRTC, fingerprint gates). Compiles under DOM-lib + + * exactOptionalPropertyTypes to flag any private-type leaks like the + * `Promise` on `fetchLatestSTH` that 4.0.0 shipped. + */ +import { + createShade, + type Shade, + type ShadeConfig, + type ShadeWebRtcConfig, +} from '@shade/sdk'; + +declare const factory: ShadeWebRtcConfig['factory']; + +async function smoke(): Promise { + const config: ShadeConfig = { + prekeyServer: 'https://shade.example.com', + storage: 'memory', + address: 'alice@example.com', + keyTransparency: { + mode: 'observe-strict', + logPublicKey: new Uint8Array(32), + }, + }; + const shade: Shade = await createShade(config); + + const env = await shade.send('bob', 'hi'); + void env; + + shade.onMessage(async (from: string, plaintext: string) => { + void from; + void plaintext; + }); + + await shade.beforeFirstLargeFile(10 * 1024 * 1024, async (ctx) => { + void ctx.peerAddress; + void ctx.fingerprint; + void ctx.gate; + return true; + }); + + shade.configureWebRTC({ factory }); + + void (await shade.fingerprint); +} + +void smoke;