@zucker-framework/ext-kit 1.0.2 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -0
- package/assets/offscreen.js +20 -4
- package/dist/ext-kit-popup.js +438 -154
- package/dist/ext-kit.js +1605 -663
- package/dist/index.d.mts +132 -31
- package/dist/index.d.ts +132 -31
- package/dist/index.js +2075 -956
- package/dist/index.mjs +2069 -956
- package/dist/scroll-capture.js +309 -0
- package/package.json +4 -2
package/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# @zucker-framework/ext-kit
|
|
2
|
+
|
|
3
|
+
MV3 capture lifecycle, queue popup driver, licensing cache, confirmed downloads and reversible scroll expansion. Browser-only; no Node/native imports in runtime exports.
|
|
4
|
+
|
|
5
|
+
## Source status / compatibility
|
|
6
|
+
|
|
7
|
+
The v2 lifecycle in this tree is **not in published 1.0.2**. A new release is required before consumers replace vendored assets. The new licensing policy port and explicit `freeDailyLimit` are breaking configuration changes. Do not copy sibling source into production or silently replace a mature consumer with 1.0.2 assets.
|
|
8
|
+
|
|
9
|
+
Runtime modules are native JavaScript with explicit public `.d.ts` contracts. Page functions are serialized to CDP/`executeScript`; bundling must not inject function-name helpers (`keepNames`). `build-vendored.mjs` emits `ext-kit.js`, `ext-kit-popup.js`, and `scroll-capture.js`; `assets/offscreen.{html,js}` belong in the extension root. The engine imports the scroll implementation directly, so it does not depend on a global being loaded first. The standalone scroll asset remains available to direct consumers.
|
|
10
|
+
|
|
11
|
+
## Public contracts
|
|
12
|
+
|
|
13
|
+
- `initExtension(ExtKitConfig)` / `ZuckerExt.init(config)`: single startup registration per worker. Keep the original `product` storage namespace, `messagePrefix`, `brand`, badge and download naming policy.
|
|
14
|
+
- `CaptureContext.send`: abort-aware CDP commands. **`cleanup` is not blocked by abort**, and must be used by consumer `afterQuick`/restoration hooks.
|
|
15
|
+
- `createDownloader(justification, DownloadOptions?)`: waits for Chrome `complete`, rejects `interrupted`, cancels at a bounded deadline, reclaims blobs. `onPrepared(url)` and `onCreated(id)` must durably persist intent before proceeding. `recover(checkpoint, signal?)` verifies extension ownership and reconciles the actual download instead of creating another file. Defaults: 120s deadline, 180s blob lease, 1s polling; `blobLeaseMs` must exceed deadline.
|
|
16
|
+
- `createLicensing(namespace, brand, LicensingConfig)`: `activate`, `getStatus`, `getEntitlement`, `deactivate`. No implicit endpoint, response envelope, plan names, credentials, code formatting or timing policy. Configure `requestTimeoutMs`, `cacheTtlMs`, `offlineGraceMs`, `retryDelayMs`, `request(operation,input,signal)` and `normalizeEntitlement(unknown)`. Normalization must validate the consumer's grant; the kit also requires a future `expiresAt`. Status `pro` means a validated paid grant, regardless of its consumer plan name.
|
|
17
|
+
- `scrollCapture.prepare(ctx, waitForContent?)` / `restore(ctx)`: expand loaded vertical content without changing widths, reject visible virtual lists rather than claim completeness, retain exact changed CSS properties and original scroll offsets. A page-local lease restores the page after worker loss; normal cleanup is idempotent and uses the non-aborted channel. Call restore in `finally`, including failed prepare. Viewport-only capture must not expand.
|
|
18
|
+
- `initPopup(PopupConfig)`: persisted URL queue and settings, inline draft preservation across polling, generation-fenced queue polling, numeric draft validation, bounded same-origin sitemap collection, pagehide cancellation. Uses the existing popup element IDs and `.settings` controls. Consumer owns HTML/CSS, labels, output-specific settings and result presentation.
|
|
19
|
+
- Capture budget/naming exports remain public. URL normalization preserves distinct hash routes (no longer strips fragments).
|
|
20
|
+
|
|
21
|
+
### Consumer policy ports
|
|
22
|
+
|
|
23
|
+
`LicensingConfig.request` returns `{ accepted, denied?, entitlement?, token?, deactivated? }`. Only set `denied:true` for an **authoritative** rejection/inactive grant from the consumer's protocol. Proxy 403, malformed payloads and transport failures must not erase a recoverable activation. Remote deactivation must succeed (`accepted && deactivated`) before local token removal. Request timeout also bounds a transport that ignores its signal; transport must still use the signal to stop underlying I/O.
|
|
24
|
+
|
|
25
|
+
`normalizeSettings(defaults,input)` holds PDF/image formats, margins and product UI policy; `migrateSettings(saved)` maps old product settings before normalization. Generic engine readiness/delay flags are bounded separately. `captureScope`, `captureQuick`, `beforeQuick`, `afterQuick`, `beforeScroll`, and `capture` preserve output-specific logic in the consumer. `freeDailyLimit` is explicitly required; no product quota is chosen by Framework.
|
|
26
|
+
|
|
27
|
+
## Persistence and recovery invariants
|
|
28
|
+
|
|
29
|
+
- Storage keys remain `<namespace>_batch_state`, `_quick_state`, `_free_usage`, `_owned_tab`, `_license_token`, `_entitlement`, `_device_id`.
|
|
30
|
+
- Batch state is version 2. Download URL intent precedes file creation, download ID precedes completion wait. Successful completion and free usage are committed in **one** `chrome.storage.local.set` call. A failed read/write keeps an uncharged download recoverable. Only one capture/reconciliation runs per worker.
|
|
31
|
+
- This relies on Chrome's local storage operation semantics and the single engine writer, not on a distributed transaction. Do not write these keys from parallel engines or replace persistence with memory.
|
|
32
|
+
- Stop/Clear reconcile existing downloads before dropping checkpoints. Completed output is charged once even if Stop races with completion. Upgrade does not clear a live batch.
|
|
33
|
+
- Tab ownership lives in `storage.session`, never durable local storage: stale browser-session tab IDs must not be closed. Cleanup removes only the owned capture tab and untouched idle tab, not a window containing user tabs.
|
|
34
|
+
- Offscreen blob leases survive worker suspension; explicit revocation and document pagehide drain owned URLs. Keeping the offscreen document warm avoids a close/create race; URLs, not unrelated documents, are the bounded owned resources.
|
|
35
|
+
|
|
36
|
+
## Targeted verification
|
|
37
|
+
|
|
38
|
+
From repository root, using existing dependencies:
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
node --test packages/ext-kit/test/engine.test.cjs packages/ext-kit/test/licensing.test.cjs packages/ext-kit/test/lifecycle.test.cjs
|
|
42
|
+
node --test packages/ext-kit/test/browser.test.cjs
|
|
43
|
+
pnpm exec vitest run packages/ext-kit/src/naming.spec.ts
|
|
44
|
+
pnpm exec tsc --noEmit -p packages/ext-kit/tsconfig.json
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Browser tests use disposable Chromium contexts and synthetic pages/Chrome ports. Set `CHROMIUM_EXECUTABLE` if needed. Engine/license regressions were extracted from two mature consumers and use a synthetic policy adapter; domain output tests remain with consumers. Type checking verifies TS/public declarations, **not all JS implementation internals**. Runtime failure tests and source-bundle browser checks remain required. No real licenses, DB, provider calls or store publication are tested.
|
|
48
|
+
|
|
49
|
+
Release integration must verify the immutable archive's exports/assets and fixed version, then rerun each consumer's existing capture/download/recovery and popup tests before deleting its old generic implementation. No release or consumer switch is implied by local tests.
|
package/assets/offscreen.js
CHANGED
|
@@ -12,21 +12,37 @@ function base64ToBytes(base64) {
|
|
|
12
12
|
return bytes;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
// This document outlives a suspended worker. Every owned URL has a bounded lease.
|
|
16
|
+
const blobs = new Map();
|
|
17
|
+
function revoke(url) {
|
|
18
|
+
if (!blobs.has(url)) return;
|
|
19
|
+
URL.revokeObjectURL(url);
|
|
20
|
+
clearTimeout(blobs.get(url));
|
|
21
|
+
blobs.delete(url);
|
|
22
|
+
}
|
|
23
|
+
addEventListener('pagehide', () => {
|
|
24
|
+
for (const url of blobs.keys()) revoke(url);
|
|
25
|
+
});
|
|
26
|
+
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
27
|
+
if (message?.target !== "offscreen" || (sender.id && sender.id !== chrome.runtime.id)) {
|
|
17
28
|
return;
|
|
18
29
|
}
|
|
19
30
|
if (message.type === "CREATE_BLOB_URL") {
|
|
20
31
|
try {
|
|
32
|
+
const leaseMs = message.leaseMs ?? 180000;
|
|
33
|
+
if (!Number.isFinite(leaseMs) || leaseMs <= 0 || leaseMs > 2147483647)
|
|
34
|
+
throw new RangeError('Invalid blob lease');
|
|
21
35
|
const blob = new Blob([base64ToBytes(message.base64)], { type: message.mime });
|
|
22
|
-
|
|
36
|
+
const url = URL.createObjectURL(blob);
|
|
37
|
+
blobs.set(url, setTimeout(() => revoke(url), leaseMs));
|
|
38
|
+
sendResponse({ ok: true, url });
|
|
23
39
|
} catch (error) {
|
|
24
40
|
sendResponse({ ok: false, error: String(error) });
|
|
25
41
|
}
|
|
26
42
|
return;
|
|
27
43
|
}
|
|
28
44
|
if (message.type === "REVOKE_BLOB_URL") {
|
|
29
|
-
|
|
45
|
+
revoke(message.url);
|
|
30
46
|
sendResponse({ ok: true });
|
|
31
47
|
}
|
|
32
48
|
});
|