first commit
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
# `install-prompt/install-prompt.ts`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`InstallPromptManager` captures the browser-fired `beforeinstallprompt` event
|
||||
and exposes it as a typed state machine the application can drive
|
||||
programmatically. The manager also handles `appinstalled` for hot
|
||||
installations, persists dismissal across browser sessions (so dismissed users
|
||||
are not re-prompted on every visit), and emits OTel counters partitioned by
|
||||
outcome (`accepted` / `dismissed`).
|
||||
|
||||
## Features
|
||||
|
||||
- State machine: `idle → available → (accepted | dismissed)`.
|
||||
- `prompt()` invokes the captured deferred event and awaits the user's
|
||||
choice.
|
||||
- `dismiss()` clears the captured event without showing it, persisting the
|
||||
decision so future installs require an explicit `clearDismissed()`.
|
||||
- Injectable `WindowLike` + `StorageLike` seams so tests run without
|
||||
`globalThis` patching.
|
||||
- OTel `pwa_install_prompt_total` counter with `outcome` attribute.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { InstallPromptManager } from "@suaveplan/pwa";
|
||||
|
||||
const ipm = new InstallPromptManager();
|
||||
ipm.listen();
|
||||
ipm.onStateChange((s) => {
|
||||
if (s === "available") showInstallBanner();
|
||||
});
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Inject window + storage seams and drive every transition:
|
||||
|
||||
```ts
|
||||
import { InstallPromptManager } from "@suaveplan/pwa";
|
||||
|
||||
const window = createFakeWindow();
|
||||
const storage = createMemoryStorage();
|
||||
const ipm = new InstallPromptManager({ window, storage });
|
||||
ipm.listen();
|
||||
window.dispatchBeforeInstallPrompt();
|
||||
expect(ipm.isAvailable).toBe(true);
|
||||
await ipm.prompt();
|
||||
expect(ipm.state).toBe("accepted");
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `new InstallPromptManager(options?: InstallPromptManagerOptions)`.
|
||||
- `state: InstallPromptState` (readonly).
|
||||
- `isAvailable: boolean`.
|
||||
- `wasDismissed: boolean`.
|
||||
- `listen(): void`.
|
||||
- `unlisten(): void`.
|
||||
- `prompt(): Promise<InstallPromptState>`.
|
||||
- `dismiss(): void`.
|
||||
- `clearDismissed(): void`.
|
||||
- `onStateChange(listener): () => void`.
|
||||
- `reset(): void`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The `WindowLike` seam exposes only `addEventListener` / `removeEventListener`
|
||||
so tests can pass a tiny `EventTarget` subclass without faking the full
|
||||
`Window` interface. The dismissal-persisting writes use a try/catch because
|
||||
`StorageLike` implementations can throw (`QuotaExceededError` under heavy
|
||||
quotas) and the install-prompt should remain functional even when persistence
|
||||
fails — at worst the user will be re-prompted next session. The manager
|
||||
explicitly does NOT trigger any platform-native UI itself: `prompt()` defers
|
||||
entirely to the browser's deferred event, and `dismiss()` only updates the
|
||||
internal state machine + storage.
|
||||
Reference in New Issue
Block a user