@hraness/ghostget 0.17.2
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/CHANGELOG.md +458 -0
- package/LICENSE +21 -0
- package/README.md +1359 -0
- package/bunfig.toml +2 -0
- package/dist/apple-photos-client.js +704 -0
- package/dist/beeper-client.js +4602 -0
- package/dist/client.js +1605 -0
- package/dist/index-26yq8q16.js +31 -0
- package/dist/index-9t13b4k7.js +508 -0
- package/dist/index-axwm9b3v.js +5 -0
- package/dist/index-gwk7rbyj.js +30 -0
- package/dist/index.js +701 -0
- package/dist/messaging.js +1130 -0
- package/dist/omni-client.js +1406 -0
- package/dist/whatsapp-client.js +355 -0
- package/docs/imessage-direct-provider.md +106 -0
- package/docs/rental-listings.md +94 -0
- package/package.json +600 -0
- package/skills/ghostget/SKILL.md +251 -0
- package/skills/ghostget/agents/openai.yaml +4 -0
- package/skills/ghostget/references/adapter-contract.md +181 -0
- package/skills/ghostget/references/apple-photos.md +115 -0
- package/skills/ghostget/references/article-drafts.md +235 -0
- package/skills/ghostget/references/code-owned-provider-scaffold.md +134 -0
- package/skills/ghostget/references/cross-posting-video.md +139 -0
- package/skills/ghostget/references/cross-posting.md +82 -0
- package/skills/ghostget/references/derivation.md +390 -0
- package/skills/ghostget/references/hraness-social-profile-stats.json +269 -0
- package/skills/ghostget/references/install.md +66 -0
- package/skills/ghostget/references/linkedin-adapter.md +442 -0
- package/skills/ghostget/references/messaging.md +168 -0
- package/skills/ghostget/references/meta-comet-contract.md +153 -0
- package/skills/ghostget/references/platform-patterns.md +122 -0
- package/skills/ghostget/references/provider-plugins.md +496 -0
- package/skills/ghostget/references/reddit-flair.md +78 -0
- package/skills/ghostget/references/safety-and-state.md +214 -0
- package/skills/ghostget/references/settlement-and-duplicate-cleanup.md +79 -0
- package/skills/ghostget/references/social-platform-routing.md +66 -0
- package/skills/ghostget/references/social-profile-stats.md +195 -0
- package/skills/ghostget/references/social-video-platform-routing.md +89 -0
- package/skills/ghostget/references/whatsapp-adapter.md +101 -0
- package/skills/ghostget/references/x-adapter.md +332 -0
- package/skills/ghostget/references/x-ai-disclosure.md +71 -0
- package/src/apple-photos-cli.ts +639 -0
- package/src/apple-photos-client-types.ts +168 -0
- package/src/apple-photos-client.ts +201 -0
- package/src/apple-photos-contact-evidence.ts +838 -0
- package/src/apple-photos-local-source.ts +1131 -0
- package/src/args.ts +2053 -0
- package/src/article-draft-document.ts +417 -0
- package/src/article-draft-embeds.ts +123 -0
- package/src/article-draft-images.ts +214 -0
- package/src/assets/adapter-template/wrench-adapter.json +40 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.json +1500 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v1.0.0.json +129 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v1.1.0.json +207 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v2.0.0.json +1463 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v2.1.0.json +1463 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v2.2.0.json +1475 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v2.3.0.json +1488 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.json +684 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.0.0.json +581 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.1.0.json +581 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.2.0.json +581 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.3.0.json +615 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.4.0.json +684 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.5.0.json +684 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.6.0.json +684 -0
- package/src/assets/adapters/clasificados/wrench-web-adapter.json +54 -0
- package/src/assets/adapters/facebook/wrench-web-adapter.json +750 -0
- package/src/assets/adapters/facebook/wrench-web-adapter.v1.0.0.json +706 -0
- package/src/assets/adapters/facebook/wrench-web-adapter.v1.1.0.json +703 -0
- package/src/assets/adapters/facebook/wrench-web-adapter.v1.2.0.json +750 -0
- package/src/assets/adapters/facebook-group/wrench-web-adapter.json +682 -0
- package/src/assets/adapters/facebook-group/wrench-web-adapter.v1.0.0.json +583 -0
- package/src/assets/adapters/facebook-group/wrench-web-adapter.v1.1.0.json +682 -0
- package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.json +437 -0
- package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.0.0.json +383 -0
- package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.1.0.json +437 -0
- package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.1.1.json +437 -0
- package/src/assets/adapters/facebook-page/wrench-web-adapter.json +880 -0
- package/src/assets/adapters/facebook-page/wrench-web-adapter.v1.0.0.json +740 -0
- package/src/assets/adapters/facebook-page/wrench-web-adapter.v1.1.0.json +880 -0
- package/src/assets/adapters/github/wrench-web-adapter.json +69 -0
- package/src/assets/adapters/github/wrench-web-adapter.v1.0.0.json +42 -0
- package/src/assets/adapters/github/wrench-web-adapter.v1.1.0.json +69 -0
- package/src/assets/adapters/gmail/wrench-adapter.json +161 -0
- package/src/assets/adapters/gmail/wrench-adapter.v1.2.0.json +157 -0
- package/src/assets/adapters/hacker-news/wrench-web-adapter.json +315 -0
- package/src/assets/adapters/hacker-news/wrench-web-adapter.v1.0.0.json +315 -0
- package/src/assets/adapters/imessage/wrench-web-adapter.json +236 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.json +719 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.0.0.json +627 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.1.0.json +609 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.2.0.json +640 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.3.0.json +671 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.4.0.json +710 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.5.0.json +710 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.6.0.json +719 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.7.0.json +719 -0
- package/src/assets/adapters/linkedin/wrench-adapter.json +442 -0
- package/src/assets/adapters/linkedin/wrench-adapter.v0.4.0.json +124 -0
- package/src/assets/adapters/linkedin/wrench-adapter.v1.0.0.json +411 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.json +914 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.0.0.json +604 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.1.0.json +604 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.10.0.json +809 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.11.0.json +809 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.12.0.json +820 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.13.0.json +819 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.14.0.json +819 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.15.0.json +819 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.16.0.json +874 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.17.0.json +874 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.18.0.json +874 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.19.0.json +874 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.2.0.json +731 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.3.0.json +768 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.4.0.json +808 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.5.0.json +808 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.6.0.json +808 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.7.0.json +825 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.8.0.json +792 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.9.0.json +809 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.json +855 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.0.0.json +577 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.1.0.json +604 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.10.0.json +712 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.11.0.json +712 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.12.0.json +712 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.2.0.json +653 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.3.0.json +712 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.4.0.json +713 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.5.0.json +712 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.6.0.json +713 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.7.0.json +713 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.8.0.json +713 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.9.0.json +713 -0
- package/src/assets/adapters/substack/wrench-web-adapter.json +936 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.0.0.json +817 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.1.0.json +812 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.2.0.json +812 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.3.0.json +902 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.4.0.json +936 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.5.0.json +936 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.6.0.json +936 -0
- package/src/assets/adapters/threads/wrench-web-adapter.json +692 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.0.0.json +629 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.1.0.json +623 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.2.0.json +622 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.3.0.json +622 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.4.0.json +622 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.5.0.json +699 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.6.0.json +693 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.7.0.json +692 -0
- package/src/assets/adapters/tiktok/wrench-web-adapter.json +762 -0
- package/src/assets/adapters/tiktok/wrench-web-adapter.v1.0.0.json +681 -0
- package/src/assets/adapters/tiktok/wrench-web-adapter.v1.1.0.json +708 -0
- package/src/assets/adapters/tiktok/wrench-web-adapter.v1.2.0.json +762 -0
- package/src/assets/adapters/tiktok/wrench-web-adapter.v1.3.0.json +762 -0
- package/src/assets/adapters/twitch/wrench-web-adapter.json +42 -0
- package/src/assets/adapters/twitch/wrench-web-adapter.v1.0.0.json +42 -0
- package/src/assets/adapters/whatsapp/wrench-web-adapter.json +400 -0
- package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.0.0.json +355 -0
- package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.1.0.json +386 -0
- package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.2.0.json +386 -0
- package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.3.0.json +400 -0
- package/src/assets/adapters/x/wrench-adapter.json +749 -0
- package/src/assets/adapters/x/wrench-adapter.v1.0.0.json +700 -0
- package/src/assets/adapters/x/wrench-adapter.v1.1.0.json +707 -0
- package/src/assets/adapters/x/wrench-web-adapter.json +712 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.1.0.json +579 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.10.0.json +685 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.11.0.json +712 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.12.0.json +712 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.13.0.json +712 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.2.0.json +578 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.3.0.json +610 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.4.0.json +617 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.5.0.json +634 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.6.0.json +632 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.7.0.json +649 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.8.0.json +649 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.9.0.json +683 -0
- package/src/assets/adapters/youtube/wrench-web-adapter.json +561 -0
- package/src/assets/adapters/youtube/wrench-web-adapter.v1.0.0.json +465 -0
- package/src/assets/adapters/youtube/wrench-web-adapter.v1.1.0.json +519 -0
- package/src/assets/adapters/youtube/wrench-web-adapter.v1.2.0.json +561 -0
- package/src/assets/adapters/youtube/wrench-web-adapter.v1.3.0.json +561 -0
- package/src/assets/code-owned-provider-template/plugin.test.ts.template +54 -0
- package/src/assets/code-owned-provider-template/plugin.ts.template +102 -0
- package/src/assets/code-owned-provider-template/runtime.internal.test.ts.template +44 -0
- package/src/assets/code-owned-provider-template/runtime.ts.template +59 -0
- package/src/assets/code-owned-provider-template/wrench-adapter.json.template +33 -0
- package/src/assets/fonts/nebula-sans/LICENSE.txt +96 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
- package/src/assets/fonts/nebula-sans/PROVENANCE.md +14 -0
- package/src/assets.d.ts +4 -0
- package/src/auth.ts +1157 -0
- package/src/beeper-client-types.ts +169 -0
- package/src/beeper-client.ts +250 -0
- package/src/beeper-contact-interactions-cli.ts +180 -0
- package/src/beeper-contact-interactions.ts +1612 -0
- package/src/beeper-message-bundle-v1.ts +257 -0
- package/src/beeper-message-like-me-cli.ts +84 -0
- package/src/beeper-message-like-me-export.ts +2274 -0
- package/src/beeper-message-like-me-recovery.ts +931 -0
- package/src/beeper-message-like-me-source.ts +4034 -0
- package/src/browser-admission.ts +928 -0
- package/src/browser-snapshots.ts +260 -0
- package/src/browser.ts +3873 -0
- package/src/canonical-json.ts +60 -0
- package/src/catalog-cli.ts +638 -0
- package/src/cli.ts +208 -0
- package/src/client-types.ts +301 -0
- package/src/client.ts +2726 -0
- package/src/confirmed-write-failure.ts +44 -0
- package/src/confirmed-write-model.ts +72 -0
- package/src/confirmed-write-platform.ts +1055 -0
- package/src/confirmed-write-program.ts +224 -0
- package/src/confirmed-write-runtime.ts +16 -0
- package/src/cursor-token.ts +228 -0
- package/src/derivation-dnr-readiness.ts +367 -0
- package/src/derivation-file-chooser.ts +384 -0
- package/src/derivation-network-boundary.ts +835 -0
- package/src/derivation-network-guard.ts +782 -0
- package/src/derivation-network-proxy-helper.ts +440 -0
- package/src/derivation-network-proxy.ts +385 -0
- package/src/derive-command-helper.ts +693 -0
- package/src/derive-fixtures.ts +343 -0
- package/src/derive-review.ts +980 -0
- package/src/derive.ts +4754 -0
- package/src/fixtures/beeper-message-like-me-v1/accounts.ndjson +2 -0
- package/src/fixtures/beeper-message-like-me-v1/conversations.ndjson +1 -0
- package/src/fixtures/beeper-message-like-me-v1/manifest.json +1 -0
- package/src/fixtures/beeper-message-like-me-v1/messages.ndjson +2 -0
- package/src/fixtures/beeper-message-like-me-v1/participants.ndjson +3 -0
- package/src/fixtures/beeper-message-like-me-v1/reactions.ndjson +1 -0
- package/src/fixtures/beeper-message-like-me-v1/tombstones.ndjson +1 -0
- package/src/ghostget.ts +3672 -0
- package/src/gmail-capture.ts +1244 -0
- package/src/har-internal.ts +1242 -0
- package/src/har.ts +743 -0
- package/src/index.ts +218 -0
- package/src/invocation-read-platform.ts +55 -0
- package/src/invocation-read-program.ts +93 -0
- package/src/invocation-read-runtime.ts +11 -0
- package/src/linked-device-lifecycle-admission.ts +450 -0
- package/src/linked-device-lifecycle-journal.ts +1560 -0
- package/src/linked-device-lifecycle-runtime.ts +1602 -0
- package/src/local-cli-admission.ts +135 -0
- package/src/local-cli-contracts.ts +271 -0
- package/src/local-cli-execution.ts +98 -0
- package/src/local-cli-surface-contract.ts +1510 -0
- package/src/local-cli-tool-identity.ts +336 -0
- package/src/media/archive.ts +2325 -0
- package/src/media/args.ts +303 -0
- package/src/media/cli.ts +287 -0
- package/src/media/doctor.ts +475 -0
- package/src/media/ffmpeg.ts +1330 -0
- package/src/media/http-capture.ts +711 -0
- package/src/media/http-probe.ts +593 -0
- package/src/media/http.ts +387 -0
- package/src/media/index.ts +10 -0
- package/src/media/local-transcription.ts +613 -0
- package/src/media/lock.ts +321 -0
- package/src/media/manifest.ts +1712 -0
- package/src/media/metadata.ts +906 -0
- package/src/media/process.ts +723 -0
- package/src/media/revision.ts +166 -0
- package/src/media/runtime-closure.ts +1063 -0
- package/src/media/source-router.ts +187 -0
- package/src/media/transcriber-config.ts +1122 -0
- package/src/media/transcript-persistence-model.ts +15 -0
- package/src/media/transcript-persistence-platform.ts +89 -0
- package/src/media/transcript-persistence-program.ts +39 -0
- package/src/media/transcript-persistence-runtime.ts +24 -0
- package/src/media/transcript.ts +616 -0
- package/src/media/utf8-order.ts +30 -0
- package/src/media/whisper-cpp.ts +464 -0
- package/src/media/whisper-language.ts +58 -0
- package/src/media/yt-dlp.ts +439 -0
- package/src/message-like-me-agentic-messaging.ts +882 -0
- package/src/messaging-action-store.ts +793 -0
- package/src/messaging-runtime.ts +2136 -0
- package/src/messaging-store.ts +539 -0
- package/src/messaging-types.ts +1663 -0
- package/src/messaging.ts +334 -0
- package/src/model.ts +2671 -0
- package/src/oauth-google.ts +888 -0
- package/src/omni-client-types.ts +306 -0
- package/src/omni-client.ts +2225 -0
- package/src/omni-limits.ts +34 -0
- package/src/omni-model.ts +1614 -0
- package/src/omni-request.ts +379 -0
- package/src/omni-runtime.ts +1632 -0
- package/src/operation-deadline.ts +189 -0
- package/src/path-helper.ts +1656 -0
- package/src/pinned-https.ts +194 -0
- package/src/plan-assets.ts +832 -0
- package/src/platform-catalog.ts +1832 -0
- package/src/plugins/beeper-linked-device/plugin.ts +310 -0
- package/src/plugins/bluesky-web/plugin.ts +178 -0
- package/src/plugins/clasificados-web/plugin.ts +66 -0
- package/src/plugins/github-web/plugin.ts +80 -0
- package/src/plugins/gmail-official/plugin.ts +110 -0
- package/src/plugins/hacker-news-web/plugin.ts +52 -0
- package/src/plugins/imessage-direct/plugin.ts +195 -0
- package/src/plugins/imessage-direct/vendor/0001-fix-keep-AppleScript-send-payloads-out-of-child-argv.patch +1430 -0
- package/src/plugins/imessage-direct/vendor/0002-feat-rpc-add-exact-chat-lookup.patch +225 -0
- package/src/plugins/imessage-direct/vendor/provenance.json +80 -0
- package/src/plugins/linkedin-official/plugin.ts +84 -0
- package/src/plugins/linkedin-web/plugin.ts +412 -0
- package/src/plugins/meta-web/plugin.ts +317 -0
- package/src/plugins/reddit-web/flair.ts +492 -0
- package/src/plugins/reddit-web/plugin.ts +477 -0
- package/src/plugins/substack-web/plugin.ts +124 -0
- package/src/plugins/tiktok-web/plugin.ts +67 -0
- package/src/plugins/twitch-web/plugin.ts +54 -0
- package/src/plugins/whatsapp-linked-device/plugin.ts +105 -0
- package/src/plugins/x-official/plugin.ts +64 -0
- package/src/plugins/x-web/plugin.ts +488 -0
- package/src/plugins/youtube-web/plugin.ts +101 -0
- package/src/portable-run-recovery.ts +466 -0
- package/src/process-identity.ts +501 -0
- package/src/profile-clone-helper.ts +104 -0
- package/src/provider-catalog-views.ts +51 -0
- package/src/provider-context.ts +69 -0
- package/src/provider-contract-definitions-x.ts +45 -0
- package/src/provider-contract-definitions.ts +948 -0
- package/src/provider-contract-input-gmail.ts +176 -0
- package/src/provider-contract-input-linkedin.ts +77 -0
- package/src/provider-contract-input-x.ts +304 -0
- package/src/provider-contract-planning.ts +23 -0
- package/src/provider-contract-semantic-identity.ts +54 -0
- package/src/provider-contracts.ts +251 -0
- package/src/provider-http.ts +723 -0
- package/src/provider-plugin-auth.ts +36 -0
- package/src/provider-plugin-builtins.ts +229 -0
- package/src/provider-plugin-cleanup-barrier.ts +229 -0
- package/src/provider-plugin-cleanup-execution.ts +79 -0
- package/src/provider-plugin-cleanup-resource.ts +470 -0
- package/src/provider-plugin-contract-identity.ts +707 -0
- package/src/provider-plugin-host.ts +1972 -0
- package/src/provider-plugin-identifiers.ts +60 -0
- package/src/provider-plugin-invocation-lease.ts +889 -0
- package/src/provider-plugin-lifecycle-kernel.ts +566 -0
- package/src/provider-plugin-lifecycle.ts +1455 -0
- package/src/provider-plugin-module-analysis.ts +336 -0
- package/src/provider-plugin-package.ts +2530 -0
- package/src/provider-plugin-portable-authority.ts +106 -0
- package/src/provider-plugin-portable-catalog.ts +94 -0
- package/src/provider-plugin-portable-identity.ts +315 -0
- package/src/provider-plugin-portable-runtime.ts +2057 -0
- package/src/provider-plugin-protocol.ts +1625 -0
- package/src/provider-plugin-registry.ts +3342 -0
- package/src/provider-plugin-store.ts +2155 -0
- package/src/provider-plugin-test-fixture.ts +5 -0
- package/src/provider-plugin.ts +4907 -0
- package/src/provider-plugins.generated.ts +43 -0
- package/src/provider-plugins.ts +8 -0
- package/src/provider-subject.ts +41 -0
- package/src/provider.ts +315 -0
- package/src/providers/beeper-local-runtime.ts +5426 -0
- package/src/providers/beeper-local.ts +2739 -0
- package/src/providers/beeper-messaging.ts +462 -0
- package/src/providers/beeper-omni.ts +1159 -0
- package/src/providers/bluesky-web-runtime.ts +2895 -0
- package/src/providers/bluesky-web.ts +1614 -0
- package/src/providers/clasificados-web-runtime.ts +255 -0
- package/src/providers/clasificados-web.ts +503 -0
- package/src/providers/contact-projection.ts +301 -0
- package/src/providers/github-read-model.ts +94 -0
- package/src/providers/github-read-platform.ts +165 -0
- package/src/providers/github-read-program.ts +97 -0
- package/src/providers/github-web-runtime.ts +320 -0
- package/src/providers/github-web.ts +403 -0
- package/src/providers/gmail-api.ts +2834 -0
- package/src/providers/gmail-omni.ts +830 -0
- package/src/providers/gmail.ts +1130 -0
- package/src/providers/hacker-news-web-runtime.ts +220 -0
- package/src/providers/hacker-news-web.ts +809 -0
- package/src/providers/imessage-direct-install.ts +354 -0
- package/src/providers/imessage-direct-messaging.ts +532 -0
- package/src/providers/imessage-direct-omni.ts +400 -0
- package/src/providers/imessage-direct-runtime.ts +1498 -0
- package/src/providers/imessage-direct.ts +351 -0
- package/src/providers/instagram-video-foundations.ts +913 -0
- package/src/providers/instagram-web-profile-browser.ts +600 -0
- package/src/providers/iso-bmff.ts +338 -0
- package/src/providers/linkedin-company-platform.ts +42 -0
- package/src/providers/linkedin-company-program.ts +88 -0
- package/src/providers/linkedin-contact-failure.ts +84 -0
- package/src/providers/linkedin-contact-platform.ts +45 -0
- package/src/providers/linkedin-contact-program.ts +91 -0
- package/src/providers/linkedin-profile-activity-failure.ts +76 -0
- package/src/providers/linkedin-profile-activity-platform.ts +41 -0
- package/src/providers/linkedin-profile-activity-program.ts +67 -0
- package/src/providers/linkedin-read-failure.ts +52 -0
- package/src/providers/linkedin-self-platform.ts +53 -0
- package/src/providers/linkedin-self-program.ts +122 -0
- package/src/providers/linkedin-web-article-browser.ts +794 -0
- package/src/providers/linkedin-web-bootstrap.ts +146 -0
- package/src/providers/linkedin-web-contact.ts +830 -0
- package/src/providers/linkedin-web-feed-browser.ts +832 -0
- package/src/providers/linkedin-web-feed.ts +864 -0
- package/src/providers/linkedin-web-post-browser.ts +653 -0
- package/src/providers/linkedin-web-profile-browser.ts +683 -0
- package/src/providers/linkedin-web-runtime.ts +2388 -0
- package/src/providers/linkedin-web.ts +3305 -0
- package/src/providers/linkedin.ts +1885 -0
- package/src/providers/meta-bootstrap.ts +1043 -0
- package/src/providers/meta-facebook-group.ts +690 -0
- package/src/providers/meta-marketplace-relay.ts +747 -0
- package/src/providers/meta-omni.ts +373 -0
- package/src/providers/meta-relay-bundle.ts +1049 -0
- package/src/providers/meta-web-descriptors.ts +1740 -0
- package/src/providers/meta-web-runtime.ts +3388 -0
- package/src/providers/meta-web.ts +2931 -0
- package/src/providers/read-failure.ts +139 -0
- package/src/providers/reddit-omni.ts +462 -0
- package/src/providers/reddit-web-runtime.ts +1929 -0
- package/src/providers/reddit-web.ts +1611 -0
- package/src/providers/rental-listings.ts +460 -0
- package/src/providers/substack-omni.ts +406 -0
- package/src/providers/substack-video-mp4.ts +33 -0
- package/src/providers/substack-web-runtime.ts +2812 -0
- package/src/providers/substack-web.ts +1502 -0
- package/src/providers/tiktok-video-mp4.ts +33 -0
- package/src/providers/tiktok-web-runtime.ts +770 -0
- package/src/providers/tiktok-web.ts +2786 -0
- package/src/providers/twitch-web-runtime.ts +226 -0
- package/src/providers/twitch-web.ts +274 -0
- package/src/providers/whatsapp-account-identity.ts +55 -0
- package/src/providers/whatsapp-contact-projection-helper.ts +874 -0
- package/src/providers/whatsapp-contact-projection-protocol.ts +403 -0
- package/src/providers/whatsapp-interaction-projection-helper.ts +1452 -0
- package/src/providers/whatsapp-interaction-projection-protocol.ts +462 -0
- package/src/providers/whatsapp-message-export-projection-protocol.ts +532 -0
- package/src/providers/whatsapp-messaging.ts +132 -0
- package/src/providers/whatsapp-omni.ts +586 -0
- package/src/providers/whatsapp-web-runtime.ts +3108 -0
- package/src/providers/whatsapp-web.ts +1037 -0
- package/src/providers/x-image-provenance.ts +523 -0
- package/src/providers/x-made-with-ai.ts +106 -0
- package/src/providers/x-transaction-id.ts +413 -0
- package/src/providers/x-web-runtime.ts +3485 -0
- package/src/providers/x-web.ts +2332 -0
- package/src/providers/x.ts +1889 -0
- package/src/providers/youtube-web-runtime.ts +1400 -0
- package/src/providers/youtube-web.ts +1221 -0
- package/src/read-admission-runtime.ts +91 -0
- package/src/read-client.ts +521 -0
- package/src/read-effect-platform.ts +7 -0
- package/src/read-effect-runtime.ts +14 -0
- package/src/read-effect.ts +52 -0
- package/src/read-projection-admission.ts +866 -0
- package/src/read-projections.ts +3151 -0
- package/src/recovery.ts +1492 -0
- package/src/reviewed-template.ts +146 -0
- package/src/run-journal.ts +1272 -0
- package/src/runtime.ts +5116 -0
- package/src/scripts/generate-provider-plugin-catalog.ts +121 -0
- package/src/scripts/install-whatsapp-protocol.sh +232 -0
- package/src/scripts/recover-install-stages.ts +1491 -0
- package/src/scripts/resolve-state-home.ts +30 -0
- package/src/scripts/scaffold-web-provider.ts +973 -0
- package/src/scripts/sync-bundled-adapters.ts +695 -0
- package/src/session-secrets.ts +1183 -0
- package/src/state-helper.bunfig.toml +2 -0
- package/src/state-helper.ts +2914 -0
- package/src/storage.ts +3842 -0
- package/src/transport-policy.ts +2 -0
- package/src/usage.ts +182 -0
- package/src/version.ts +2 -0
- package/src/web-session-authentication-policy.ts +136 -0
- package/src/web-session-cleanup-admission.ts +2344 -0
- package/src/web-session-cleanup-join.ts +99 -0
- package/src/web-session-client.ts +996 -0
- package/src/web-session-contract-definitions.ts +820 -0
- package/src/web-session-contract-planning.ts +42 -0
- package/src/web-session-contracts.ts +228 -0
- package/src/web-session-cookies.ts +500 -0
- package/src/web-session-execution.ts +385 -0
- package/src/web-session-http.ts +373 -0
- package/src/web-session-read-errors.ts +19 -0
- package/src/web-session-read-runtime.ts +124 -0
- package/src/web-session-recovery.ts +818 -0
- package/src/web-session-sites.ts +58 -0
- package/src/web-session-template.ts +994 -0
- package/src/web-session.ts +93 -0
- package/src/whatsapp-client-binding.ts +21 -0
- package/src/whatsapp-client-types.ts +75 -0
- package/src/whatsapp-client.ts +395 -0
- package/src/whatsapp-export-coordinate.ts +17 -0
- package/src/whatsapp-message-bundle-v2.ts +195 -0
- package/src/whatsapp-message-like-me-cli.ts +195 -0
- package/src/whatsapp-message-like-me-export.ts +1072 -0
- package/src/whatsapp-message-like-me-source.ts +1012 -0
- package/tsconfig.json +67 -0
|
@@ -0,0 +1,711 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import type { MediaDirectHttpProvenance, MediaDirectHttpValidator } from "./manifest";
|
|
3
|
+
import {
|
|
4
|
+
DIRECT_HTTP_MAX_BODY_BYTES,
|
|
5
|
+
DIRECT_HTTP_MAX_REDIRECTS,
|
|
6
|
+
DIRECT_HTTP_PROBE_BYTES,
|
|
7
|
+
conflictsWithDirectMedia,
|
|
8
|
+
detectDirectHttpMedia,
|
|
9
|
+
directHttpMediaForContainer,
|
|
10
|
+
normalizeDeclaredMediaType,
|
|
11
|
+
normalizeLastModified,
|
|
12
|
+
parseContentLength,
|
|
13
|
+
parseContentRange,
|
|
14
|
+
sha256Hex,
|
|
15
|
+
strongEtag,
|
|
16
|
+
validatorFromEtag,
|
|
17
|
+
type DirectHttpMedia,
|
|
18
|
+
} from "./http";
|
|
19
|
+
import {
|
|
20
|
+
DirectHttpProbeTransport,
|
|
21
|
+
fetchWithDirectRedirects,
|
|
22
|
+
type DirectHttpFetchedResponse,
|
|
23
|
+
type DirectHttpFetchOptions,
|
|
24
|
+
type DirectHttpOwnedRequest,
|
|
25
|
+
type DirectHttpProbe,
|
|
26
|
+
} from "./http-probe";
|
|
27
|
+
|
|
28
|
+
const DEFAULT_ATTEMPTS = 3;
|
|
29
|
+
const DEFAULT_INACTIVITY_TIMEOUT_MS = 30_000;
|
|
30
|
+
const DEFAULT_TOTAL_TIMEOUT_MS = 2 * 60 * 60 * 1_000;
|
|
31
|
+
|
|
32
|
+
export interface DirectHttpCaptureSink {
|
|
33
|
+
readonly write: (chunk: Uint8Array, signal?: AbortSignal) => Promise<void>;
|
|
34
|
+
readonly restart: (signal?: AbortSignal) => Promise<void>;
|
|
35
|
+
readonly close: (signal?: AbortSignal) => Promise<void>;
|
|
36
|
+
readonly abort: (signal?: AbortSignal) => Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface DirectHttpCaptureOptions extends DirectHttpFetchOptions {
|
|
40
|
+
readonly maximumBodyBytes?: number;
|
|
41
|
+
readonly maximumAttempts?: number;
|
|
42
|
+
readonly inactivityTimeoutMs?: number;
|
|
43
|
+
readonly totalTimeoutMs?: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface DirectHttpCaptureDependencies {
|
|
47
|
+
readonly request: (
|
|
48
|
+
url: string,
|
|
49
|
+
request: DirectHttpOwnedRequest,
|
|
50
|
+
options: DirectHttpFetchOptions,
|
|
51
|
+
) => Promise<DirectHttpFetchedResponse>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface DirectHttpCapture {
|
|
55
|
+
readonly bytes: number;
|
|
56
|
+
readonly sha256: string;
|
|
57
|
+
readonly media: DirectHttpMedia;
|
|
58
|
+
readonly provenance: MediaDirectHttpProvenance;
|
|
59
|
+
readonly attempts: number;
|
|
60
|
+
readonly resumed: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export type DirectHttpCaptureErrorCode =
|
|
64
|
+
| "invalid-request"
|
|
65
|
+
| "aborted"
|
|
66
|
+
| "transport"
|
|
67
|
+
| "http-status"
|
|
68
|
+
| "unsupported-content-encoding"
|
|
69
|
+
| "invalid-response-length"
|
|
70
|
+
| "body-too-large"
|
|
71
|
+
| "invalid-content-type"
|
|
72
|
+
| "declared-text"
|
|
73
|
+
| "response-read"
|
|
74
|
+
| "total-timeout"
|
|
75
|
+
| "sink"
|
|
76
|
+
| "media-unrecognized"
|
|
77
|
+
| "media-changed";
|
|
78
|
+
|
|
79
|
+
export type DirectHttpCaptureResult =
|
|
80
|
+
| Readonly<{ ok: true; capture: DirectHttpCapture }>
|
|
81
|
+
| Readonly<{
|
|
82
|
+
ok: false;
|
|
83
|
+
error: Readonly<{ code: DirectHttpCaptureErrorCode; message: string }>;
|
|
84
|
+
}>;
|
|
85
|
+
|
|
86
|
+
const defaultCaptureDependencies: DirectHttpCaptureDependencies = {
|
|
87
|
+
request: async (url, request, options) =>
|
|
88
|
+
await fetchWithDirectRedirects(url, request, options),
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
class CaptureFailure extends Error {
|
|
92
|
+
readonly code: DirectHttpCaptureErrorCode;
|
|
93
|
+
readonly retryable: boolean;
|
|
94
|
+
readonly resumeRejected: boolean;
|
|
95
|
+
|
|
96
|
+
constructor(
|
|
97
|
+
code: DirectHttpCaptureErrorCode,
|
|
98
|
+
message: string,
|
|
99
|
+
options: Readonly<{ retryable?: boolean; resumeRejected?: boolean }> = {},
|
|
100
|
+
) {
|
|
101
|
+
super(message);
|
|
102
|
+
this.name = "CaptureFailure";
|
|
103
|
+
this.code = code;
|
|
104
|
+
this.retryable = options.retryable ?? false;
|
|
105
|
+
this.resumeRejected = options.resumeRejected ?? false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
interface CaptureState {
|
|
110
|
+
bytes: number;
|
|
111
|
+
hash: ReturnType<typeof createHash>;
|
|
112
|
+
prefix: Uint8Array;
|
|
113
|
+
prefixBytes: number;
|
|
114
|
+
expectedBytes: number | null;
|
|
115
|
+
strongEtag: string | null;
|
|
116
|
+
validator: MediaDirectHttpValidator;
|
|
117
|
+
effectiveUrl: string | null;
|
|
118
|
+
effectiveUrlSha256: string | null;
|
|
119
|
+
redirectCount: number;
|
|
120
|
+
declaredMediaType: string | null;
|
|
121
|
+
lastModified: string | null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function freshState(): CaptureState {
|
|
125
|
+
return {
|
|
126
|
+
bytes: 0,
|
|
127
|
+
hash: createHash("sha256"),
|
|
128
|
+
prefix: new Uint8Array(DIRECT_HTTP_PROBE_BYTES),
|
|
129
|
+
prefixBytes: 0,
|
|
130
|
+
expectedBytes: null,
|
|
131
|
+
strongEtag: null,
|
|
132
|
+
validator: { strength: "absent" },
|
|
133
|
+
effectiveUrl: null,
|
|
134
|
+
effectiveUrlSha256: null,
|
|
135
|
+
redirectCount: 0,
|
|
136
|
+
declaredMediaType: null,
|
|
137
|
+
lastModified: null,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function normalizeBoundedInteger(
|
|
142
|
+
value: number | undefined,
|
|
143
|
+
fallback: number,
|
|
144
|
+
minimum: number,
|
|
145
|
+
maximum: number,
|
|
146
|
+
): number {
|
|
147
|
+
if (value === undefined) return fallback;
|
|
148
|
+
if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
|
|
149
|
+
throw new CaptureFailure(
|
|
150
|
+
"invalid-request",
|
|
151
|
+
"direct HTTP numeric option is outside its supported range",
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
return value;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function captureFailure(
|
|
158
|
+
error: unknown,
|
|
159
|
+
fallback: DirectHttpCaptureErrorCode,
|
|
160
|
+
retryable: boolean,
|
|
161
|
+
): CaptureFailure {
|
|
162
|
+
return error instanceof CaptureFailure
|
|
163
|
+
? error
|
|
164
|
+
: new CaptureFailure(fallback, "direct HTTP capture failed", { retryable });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function safeCancel(response: Response): void {
|
|
168
|
+
void response.body?.cancel().catch(() => {
|
|
169
|
+
// Cancellation failure cannot authorize or persist response bytes.
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function validIdentityEncoding(response: Response): boolean {
|
|
174
|
+
const encoding = response.headers.get("content-encoding")?.trim().toLowerCase();
|
|
175
|
+
return encoding === undefined || encoding === "" || encoding === "identity";
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function exactContentLength(response: Response): number | null | "invalid" {
|
|
179
|
+
const raw = response.headers.get("content-length");
|
|
180
|
+
if (raw === null) return null;
|
|
181
|
+
return parseContentLength(raw) ?? "invalid";
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function validateInitialResponse(
|
|
185
|
+
fetched: DirectHttpFetchedResponse,
|
|
186
|
+
state: CaptureState,
|
|
187
|
+
maximumBodyBytes: number,
|
|
188
|
+
): void {
|
|
189
|
+
const { response } = fetched;
|
|
190
|
+
if (response.status !== 200 && response.status !== 206) {
|
|
191
|
+
throw new CaptureFailure("http-status", "direct HTTP capture returned an unsupported status");
|
|
192
|
+
}
|
|
193
|
+
if (!validIdentityEncoding(response)) {
|
|
194
|
+
throw new CaptureFailure(
|
|
195
|
+
"unsupported-content-encoding",
|
|
196
|
+
"direct HTTP capture did not use identity encoding",
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
const rawMediaType = response.headers.get("content-type");
|
|
200
|
+
const declaredMediaType = normalizeDeclaredMediaType(rawMediaType);
|
|
201
|
+
if (rawMediaType !== null && declaredMediaType === null) {
|
|
202
|
+
throw new CaptureFailure(
|
|
203
|
+
"invalid-content-type",
|
|
204
|
+
"direct HTTP capture media type is malformed",
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
if (conflictsWithDirectMedia(rawMediaType)) {
|
|
208
|
+
throw new CaptureFailure("declared-text", "direct HTTP capture declared a text response");
|
|
209
|
+
}
|
|
210
|
+
const contentLength = exactContentLength(response);
|
|
211
|
+
if (contentLength === "invalid") {
|
|
212
|
+
throw new CaptureFailure("invalid-response-length", "direct HTTP capture length is invalid");
|
|
213
|
+
}
|
|
214
|
+
if (response.status === 206) {
|
|
215
|
+
const range = parseContentRange(response.headers.get("content-range"));
|
|
216
|
+
if (
|
|
217
|
+
range === null
|
|
218
|
+
|| range.start !== 0
|
|
219
|
+
|| range.total === null
|
|
220
|
+
|| range.end !== range.total - 1
|
|
221
|
+
|| (contentLength !== null && contentLength !== range.total)
|
|
222
|
+
) {
|
|
223
|
+
throw new CaptureFailure(
|
|
224
|
+
"invalid-response-length",
|
|
225
|
+
"direct HTTP initial range is not a complete body",
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
state.expectedBytes = range.total;
|
|
229
|
+
} else {
|
|
230
|
+
state.expectedBytes = contentLength;
|
|
231
|
+
}
|
|
232
|
+
if (state.expectedBytes !== null && state.expectedBytes > maximumBodyBytes) {
|
|
233
|
+
throw new CaptureFailure("body-too-large", "direct HTTP body exceeds the configured limit");
|
|
234
|
+
}
|
|
235
|
+
state.strongEtag = strongEtag(response.headers.get("etag"));
|
|
236
|
+
state.validator = validatorFromEtag(response.headers.get("etag"));
|
|
237
|
+
state.effectiveUrl = fetched.effectiveUrl.href;
|
|
238
|
+
state.effectiveUrlSha256 = sha256Hex(fetched.effectiveUrl.href);
|
|
239
|
+
state.redirectCount = fetched.redirectCount;
|
|
240
|
+
state.declaredMediaType = declaredMediaType;
|
|
241
|
+
state.lastModified = normalizeLastModified(response.headers.get("last-modified"));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function validateResumeResponse(
|
|
245
|
+
fetched: DirectHttpFetchedResponse,
|
|
246
|
+
state: CaptureState,
|
|
247
|
+
maximumBodyBytes: number,
|
|
248
|
+
): void {
|
|
249
|
+
const reject = (): never => {
|
|
250
|
+
throw new CaptureFailure(
|
|
251
|
+
"invalid-response-length",
|
|
252
|
+
"direct HTTP resume was not an exact continuation",
|
|
253
|
+
{ retryable: true, resumeRejected: true },
|
|
254
|
+
);
|
|
255
|
+
};
|
|
256
|
+
const { response } = fetched;
|
|
257
|
+
if (
|
|
258
|
+
response.status !== 206
|
|
259
|
+
|| !validIdentityEncoding(response)
|
|
260
|
+
|| state.strongEtag === null
|
|
261
|
+
|| strongEtag(response.headers.get("etag")) !== state.strongEtag
|
|
262
|
+
|| fetched.requestedUrl.href !== state.effectiveUrl
|
|
263
|
+
|| fetched.effectiveUrl.href !== state.effectiveUrl
|
|
264
|
+
|| fetched.redirectCount !== 0
|
|
265
|
+
) reject();
|
|
266
|
+
const range = parseContentRange(response.headers.get("content-range"));
|
|
267
|
+
if (range === null || range.total === null) {
|
|
268
|
+
throw new CaptureFailure(
|
|
269
|
+
"invalid-response-length",
|
|
270
|
+
"direct HTTP resume was not an exact continuation",
|
|
271
|
+
{ retryable: true, resumeRejected: true },
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
const total = range.total;
|
|
275
|
+
if (
|
|
276
|
+
range.start !== state.bytes
|
|
277
|
+
|| range.end !== total - 1
|
|
278
|
+
|| (state.expectedBytes !== null && total !== state.expectedBytes)
|
|
279
|
+
) reject();
|
|
280
|
+
const contentLength = exactContentLength(response);
|
|
281
|
+
const remaining = total - state.bytes;
|
|
282
|
+
if (contentLength === "invalid" || (contentLength !== null && contentLength !== remaining)) reject();
|
|
283
|
+
if (total > maximumBodyBytes) {
|
|
284
|
+
throw new CaptureFailure("body-too-large", "direct HTTP body exceeds the configured limit");
|
|
285
|
+
}
|
|
286
|
+
const rawMediaType = response.headers.get("content-type");
|
|
287
|
+
if (
|
|
288
|
+
(rawMediaType !== null && normalizeDeclaredMediaType(rawMediaType) === null)
|
|
289
|
+
|| conflictsWithDirectMedia(rawMediaType)
|
|
290
|
+
) reject();
|
|
291
|
+
state.expectedBytes = total;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
type OwnedByteReadResult =
|
|
295
|
+
| Readonly<{ done: true }>
|
|
296
|
+
| Readonly<{ done: false; value: Uint8Array }>;
|
|
297
|
+
|
|
298
|
+
interface OwnedByteReader {
|
|
299
|
+
readonly read: () => Promise<Readonly<{ done: boolean; value?: Uint8Array }>>;
|
|
300
|
+
readonly cancel: () => Promise<void>;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function readWithTimeout(
|
|
304
|
+
reader: OwnedByteReader,
|
|
305
|
+
timeoutMs: number,
|
|
306
|
+
signal: AbortSignal,
|
|
307
|
+
): Promise<OwnedByteReadResult> {
|
|
308
|
+
const result = await awaitInterruptibly(
|
|
309
|
+
reader.read(),
|
|
310
|
+
timeoutMs,
|
|
311
|
+
signal,
|
|
312
|
+
() => new Error("capture body timeout"),
|
|
313
|
+
);
|
|
314
|
+
if (result.done) return { done: true };
|
|
315
|
+
if (result.value !== undefined) return { done: false, value: result.value };
|
|
316
|
+
throw new Error("capture body reader omitted a chunk");
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async function streamResponse(
|
|
320
|
+
response: Response,
|
|
321
|
+
sink: DirectHttpCaptureSink,
|
|
322
|
+
state: CaptureState,
|
|
323
|
+
maximumBodyBytes: number,
|
|
324
|
+
inactivityTimeoutMs: number,
|
|
325
|
+
deadline: number,
|
|
326
|
+
signal: AbortSignal,
|
|
327
|
+
): Promise<void> {
|
|
328
|
+
const reader = response.body?.getReader();
|
|
329
|
+
if (reader === undefined) {
|
|
330
|
+
throw new CaptureFailure("response-read", "direct HTTP capture returned no body", { retryable: true });
|
|
331
|
+
}
|
|
332
|
+
try {
|
|
333
|
+
while (true) {
|
|
334
|
+
const totalRemainingMs = deadline - Date.now();
|
|
335
|
+
if (totalRemainingMs <= 0) {
|
|
336
|
+
throw new CaptureFailure("total-timeout", "direct HTTP capture exceeded its total timeout");
|
|
337
|
+
}
|
|
338
|
+
let read: OwnedByteReadResult;
|
|
339
|
+
try {
|
|
340
|
+
read = await readWithTimeout(
|
|
341
|
+
reader,
|
|
342
|
+
Math.min(inactivityTimeoutMs, totalRemainingMs),
|
|
343
|
+
signal,
|
|
344
|
+
);
|
|
345
|
+
} catch (error) {
|
|
346
|
+
if (error instanceof CaptureFailure) throw error;
|
|
347
|
+
throw new CaptureFailure("response-read", "direct HTTP capture body stalled or failed", { retryable: true });
|
|
348
|
+
}
|
|
349
|
+
if (read.done) break;
|
|
350
|
+
const chunk = read.value;
|
|
351
|
+
if (state.bytes + chunk.byteLength > maximumBodyBytes) {
|
|
352
|
+
throw new CaptureFailure("body-too-large", "direct HTTP body exceeds the configured limit");
|
|
353
|
+
}
|
|
354
|
+
if (state.expectedBytes !== null && state.bytes + chunk.byteLength > state.expectedBytes) {
|
|
355
|
+
throw new CaptureFailure(
|
|
356
|
+
"invalid-response-length",
|
|
357
|
+
"direct HTTP response exceeded its declared length",
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
try {
|
|
361
|
+
await sinkOperation(
|
|
362
|
+
sink.write(chunk, signal),
|
|
363
|
+
deadline,
|
|
364
|
+
signal,
|
|
365
|
+
"direct HTTP capture could not write its destination",
|
|
366
|
+
);
|
|
367
|
+
} catch (error) {
|
|
368
|
+
if (error instanceof CaptureFailure) throw error;
|
|
369
|
+
throw new CaptureFailure("sink", "direct HTTP capture could not write its destination");
|
|
370
|
+
}
|
|
371
|
+
state.hash.update(chunk);
|
|
372
|
+
if (state.prefixBytes < state.prefix.byteLength) {
|
|
373
|
+
const copied = Math.min(chunk.byteLength, state.prefix.byteLength - state.prefixBytes);
|
|
374
|
+
state.prefix.set(chunk.subarray(0, copied), state.prefixBytes);
|
|
375
|
+
state.prefixBytes += copied;
|
|
376
|
+
}
|
|
377
|
+
state.bytes += chunk.byteLength;
|
|
378
|
+
}
|
|
379
|
+
} finally {
|
|
380
|
+
void reader.cancel().catch(() => {
|
|
381
|
+
// The response is already finished or failed; no bytes are trusted here.
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
if (state.expectedBytes !== null && state.bytes !== state.expectedBytes) {
|
|
385
|
+
throw new CaptureFailure(
|
|
386
|
+
"invalid-response-length",
|
|
387
|
+
"direct HTTP response ended before its declared length",
|
|
388
|
+
{ retryable: true },
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
async function sinkOperation(
|
|
394
|
+
operation: Promise<void>,
|
|
395
|
+
deadline: number,
|
|
396
|
+
signal: AbortSignal,
|
|
397
|
+
timeoutMessage: string,
|
|
398
|
+
): Promise<void> {
|
|
399
|
+
const remainingMs = deadline - Date.now();
|
|
400
|
+
if (remainingMs <= 0) {
|
|
401
|
+
throw new CaptureFailure("total-timeout", "direct HTTP capture exceeded its total timeout");
|
|
402
|
+
}
|
|
403
|
+
await awaitInterruptibly(
|
|
404
|
+
operation,
|
|
405
|
+
remainingMs,
|
|
406
|
+
signal,
|
|
407
|
+
() => new CaptureFailure("total-timeout", timeoutMessage),
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
async function awaitInterruptibly<T>(
|
|
412
|
+
operation: Promise<T>,
|
|
413
|
+
timeoutMs: number,
|
|
414
|
+
signal: AbortSignal,
|
|
415
|
+
timeoutError: () => Error,
|
|
416
|
+
): Promise<T> {
|
|
417
|
+
if (signal.aborted) {
|
|
418
|
+
throw new CaptureFailure("aborted", "direct HTTP capture was aborted");
|
|
419
|
+
}
|
|
420
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
421
|
+
let abort: (() => void) | undefined;
|
|
422
|
+
try {
|
|
423
|
+
return await Promise.race([
|
|
424
|
+
operation,
|
|
425
|
+
new Promise<never>((_resolve, reject) => {
|
|
426
|
+
timer = setTimeout(() => reject(timeoutError()), timeoutMs);
|
|
427
|
+
abort = () => reject(new CaptureFailure("aborted", "direct HTTP capture was aborted"));
|
|
428
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
429
|
+
}),
|
|
430
|
+
]);
|
|
431
|
+
} finally {
|
|
432
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
433
|
+
if (abort !== undefined) signal.removeEventListener("abort", abort);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
async function abortSink(sink: DirectHttpCaptureSink, signal: AbortSignal): Promise<void> {
|
|
438
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
439
|
+
try {
|
|
440
|
+
await Promise.race([
|
|
441
|
+
sink.abort(signal),
|
|
442
|
+
new Promise<void>((resolve) => {
|
|
443
|
+
timer = setTimeout(resolve, 5_000);
|
|
444
|
+
}),
|
|
445
|
+
]);
|
|
446
|
+
} catch {
|
|
447
|
+
// Preserve the primary safe error; archive staging is reset on the next run.
|
|
448
|
+
} finally {
|
|
449
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function resultError(error: CaptureFailure): DirectHttpCaptureResult {
|
|
454
|
+
return { ok: false, error: { code: error.code, message: error.message } };
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/** Streams and content-qualifies one direct response without persisting transport secrets. */
|
|
458
|
+
export async function captureDirectHttp(
|
|
459
|
+
probe: DirectHttpProbe,
|
|
460
|
+
sink: DirectHttpCaptureSink,
|
|
461
|
+
options: DirectHttpCaptureOptions = {},
|
|
462
|
+
dependencies: DirectHttpCaptureDependencies = defaultCaptureDependencies,
|
|
463
|
+
): Promise<DirectHttpCaptureResult> {
|
|
464
|
+
const controller = new AbortController();
|
|
465
|
+
let maximumBodyBytes: number;
|
|
466
|
+
let maximumAttempts: number;
|
|
467
|
+
let inactivityTimeoutMs: number;
|
|
468
|
+
let totalTimeoutMs: number;
|
|
469
|
+
let probeRequestUrl: string;
|
|
470
|
+
let requestedUrlSha256: string;
|
|
471
|
+
try {
|
|
472
|
+
maximumBodyBytes = normalizeBoundedInteger(
|
|
473
|
+
options.maximumBodyBytes,
|
|
474
|
+
DIRECT_HTTP_MAX_BODY_BYTES,
|
|
475
|
+
1,
|
|
476
|
+
DIRECT_HTTP_MAX_BODY_BYTES,
|
|
477
|
+
);
|
|
478
|
+
maximumAttempts = normalizeBoundedInteger(options.maximumAttempts, DEFAULT_ATTEMPTS, 1, 10);
|
|
479
|
+
inactivityTimeoutMs = normalizeBoundedInteger(
|
|
480
|
+
options.inactivityTimeoutMs,
|
|
481
|
+
DEFAULT_INACTIVITY_TIMEOUT_MS,
|
|
482
|
+
1,
|
|
483
|
+
10 * 60_000,
|
|
484
|
+
);
|
|
485
|
+
totalTimeoutMs = normalizeBoundedInteger(
|
|
486
|
+
options.totalTimeoutMs,
|
|
487
|
+
DEFAULT_TOTAL_TIMEOUT_MS,
|
|
488
|
+
1,
|
|
489
|
+
24 * 60 * 60 * 1_000,
|
|
490
|
+
);
|
|
491
|
+
normalizeBoundedInteger(
|
|
492
|
+
options.maxRedirects,
|
|
493
|
+
DIRECT_HTTP_MAX_REDIRECTS,
|
|
494
|
+
0,
|
|
495
|
+
DIRECT_HTTP_MAX_REDIRECTS,
|
|
496
|
+
);
|
|
497
|
+
normalizeBoundedInteger(options.timeoutMs, 30_000, 1, 10 * 60_000);
|
|
498
|
+
if (!(probe.transport instanceof DirectHttpProbeTransport)) {
|
|
499
|
+
throw new CaptureFailure("invalid-request", "direct HTTP probe transport is invalid");
|
|
500
|
+
}
|
|
501
|
+
probeRequestUrl = probe.transport.requestUrl();
|
|
502
|
+
requestedUrlSha256 = sha256Hex(probeRequestUrl);
|
|
503
|
+
if (probe.requestedUrlSha256 !== requestedUrlSha256) {
|
|
504
|
+
throw new CaptureFailure("invalid-request", "direct HTTP probe request digest is invalid");
|
|
505
|
+
}
|
|
506
|
+
const ownedMedia = directHttpMediaForContainer(probe.media.container);
|
|
507
|
+
if (
|
|
508
|
+
probe.media.extension !== ownedMedia.extension
|
|
509
|
+
|| probe.media.mediaType !== ownedMedia.mediaType
|
|
510
|
+
) {
|
|
511
|
+
throw new CaptureFailure("invalid-request", "direct HTTP probe media contract is invalid");
|
|
512
|
+
}
|
|
513
|
+
} catch (error) {
|
|
514
|
+
controller.abort();
|
|
515
|
+
await abortSink(sink, controller.signal);
|
|
516
|
+
return resultError(error instanceof CaptureFailure
|
|
517
|
+
? error
|
|
518
|
+
: new CaptureFailure("invalid-request", "direct HTTP capture request is invalid"));
|
|
519
|
+
}
|
|
520
|
+
const deadline = Date.now() + totalTimeoutMs;
|
|
521
|
+
let totalTimedOut = false;
|
|
522
|
+
const totalTimer = setTimeout(() => {
|
|
523
|
+
totalTimedOut = true;
|
|
524
|
+
controller.abort();
|
|
525
|
+
}, totalTimeoutMs);
|
|
526
|
+
const abortFromParent = (): void => controller.abort();
|
|
527
|
+
options.signal?.addEventListener("abort", abortFromParent, { once: true });
|
|
528
|
+
if (options.signal?.aborted === true) controller.abort();
|
|
529
|
+
const requestOptions: DirectHttpFetchOptions = {
|
|
530
|
+
...(options.maxRedirects === undefined ? {} : { maxRedirects: options.maxRedirects }),
|
|
531
|
+
...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
|
|
532
|
+
signal: controller.signal,
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
let state = freshState();
|
|
536
|
+
let attempts = 0;
|
|
537
|
+
let next: "initial" | "resume" = "initial";
|
|
538
|
+
let resumed = false;
|
|
539
|
+
try {
|
|
540
|
+
while (attempts < maximumAttempts) {
|
|
541
|
+
attempts += 1;
|
|
542
|
+
const request: DirectHttpOwnedRequest = next === "resume" && state.strongEtag !== null
|
|
543
|
+
? { method: "GET", range: `bytes=${String(state.bytes)}-`, ifRange: state.strongEtag }
|
|
544
|
+
: { method: "GET" };
|
|
545
|
+
const requestUrl = next === "resume" && state.effectiveUrl !== null
|
|
546
|
+
? state.effectiveUrl
|
|
547
|
+
: probeRequestUrl;
|
|
548
|
+
const attemptOptions: DirectHttpFetchOptions = next === "resume"
|
|
549
|
+
? { ...requestOptions, maxRedirects: 0 }
|
|
550
|
+
: requestOptions;
|
|
551
|
+
let fetched: DirectHttpFetchedResponse;
|
|
552
|
+
try {
|
|
553
|
+
fetched = await awaitInterruptibly(
|
|
554
|
+
dependencies.request(requestUrl, request, attemptOptions),
|
|
555
|
+
Math.max(1, deadline - Date.now()),
|
|
556
|
+
controller.signal,
|
|
557
|
+
() => new CaptureFailure("total-timeout", "direct HTTP capture exceeded its total timeout"),
|
|
558
|
+
);
|
|
559
|
+
} catch (error) {
|
|
560
|
+
const failure = totalTimedOut
|
|
561
|
+
? new CaptureFailure("total-timeout", "direct HTTP capture exceeded its total timeout")
|
|
562
|
+
: error instanceof CaptureFailure
|
|
563
|
+
? error
|
|
564
|
+
: new CaptureFailure(
|
|
565
|
+
"transport",
|
|
566
|
+
"direct HTTP capture request failed",
|
|
567
|
+
{ retryable: true },
|
|
568
|
+
);
|
|
569
|
+
if (!failure.retryable || attempts >= maximumAttempts) {
|
|
570
|
+
await abortSink(sink, controller.signal);
|
|
571
|
+
return resultError(failure);
|
|
572
|
+
}
|
|
573
|
+
if (next === "initial" && state.bytes === 0) continue;
|
|
574
|
+
try {
|
|
575
|
+
await sinkOperation(
|
|
576
|
+
sink.restart(controller.signal),
|
|
577
|
+
deadline,
|
|
578
|
+
controller.signal,
|
|
579
|
+
"direct HTTP capture could not restart before its total timeout",
|
|
580
|
+
);
|
|
581
|
+
} catch (error) {
|
|
582
|
+
await abortSink(sink, controller.signal);
|
|
583
|
+
if (error instanceof CaptureFailure) {
|
|
584
|
+
return resultError(totalTimedOut
|
|
585
|
+
? new CaptureFailure("total-timeout", "direct HTTP capture exceeded its total timeout")
|
|
586
|
+
: error);
|
|
587
|
+
}
|
|
588
|
+
return resultError(new CaptureFailure("sink", "direct HTTP capture could not restart its destination"));
|
|
589
|
+
}
|
|
590
|
+
state = freshState();
|
|
591
|
+
next = "initial";
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
try {
|
|
596
|
+
if (next === "initial" && fetched.requestedUrl.href !== probeRequestUrl) {
|
|
597
|
+
throw new CaptureFailure("transport", "direct HTTP capture request identity changed");
|
|
598
|
+
}
|
|
599
|
+
if (next === "resume") validateResumeResponse(fetched, state, maximumBodyBytes);
|
|
600
|
+
else validateInitialResponse(fetched, state, maximumBodyBytes);
|
|
601
|
+
await streamResponse(
|
|
602
|
+
fetched.response,
|
|
603
|
+
sink,
|
|
604
|
+
state,
|
|
605
|
+
maximumBodyBytes,
|
|
606
|
+
inactivityTimeoutMs,
|
|
607
|
+
deadline,
|
|
608
|
+
controller.signal,
|
|
609
|
+
);
|
|
610
|
+
if (next === "resume") resumed = true;
|
|
611
|
+
} catch (error) {
|
|
612
|
+
safeCancel(fetched.response);
|
|
613
|
+
const failure = captureFailure(error, "response-read", true);
|
|
614
|
+
if (!failure.retryable || attempts >= maximumAttempts || totalTimedOut) {
|
|
615
|
+
await abortSink(sink, controller.signal);
|
|
616
|
+
return resultError(totalTimedOut
|
|
617
|
+
? new CaptureFailure("total-timeout", "direct HTTP capture exceeded its total timeout")
|
|
618
|
+
: failure);
|
|
619
|
+
}
|
|
620
|
+
if (failure.resumeRejected || state.strongEtag === null || state.bytes === 0) {
|
|
621
|
+
try {
|
|
622
|
+
await sinkOperation(
|
|
623
|
+
sink.restart(controller.signal),
|
|
624
|
+
deadline,
|
|
625
|
+
controller.signal,
|
|
626
|
+
"direct HTTP capture could not restart before its total timeout",
|
|
627
|
+
);
|
|
628
|
+
} catch (restartError) {
|
|
629
|
+
await abortSink(sink, controller.signal);
|
|
630
|
+
if (restartError instanceof CaptureFailure) {
|
|
631
|
+
return resultError(totalTimedOut
|
|
632
|
+
? new CaptureFailure("total-timeout", "direct HTTP capture exceeded its total timeout")
|
|
633
|
+
: restartError);
|
|
634
|
+
}
|
|
635
|
+
return resultError(new CaptureFailure("sink", "direct HTTP capture could not restart its destination"));
|
|
636
|
+
}
|
|
637
|
+
state = freshState();
|
|
638
|
+
next = "initial";
|
|
639
|
+
} else {
|
|
640
|
+
next = "resume";
|
|
641
|
+
}
|
|
642
|
+
continue;
|
|
643
|
+
} finally {
|
|
644
|
+
fetched.dispose();
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const media = detectDirectHttpMedia(state.prefix.subarray(0, state.prefixBytes));
|
|
648
|
+
if (media === null) {
|
|
649
|
+
await abortSink(sink, controller.signal);
|
|
650
|
+
return resultError(new CaptureFailure("media-unrecognized", "direct HTTP capture did not contain recognized media"));
|
|
651
|
+
}
|
|
652
|
+
const ownedMedia = directHttpMediaForContainer(media.container);
|
|
653
|
+
if (
|
|
654
|
+
media.container !== probe.media.container
|
|
655
|
+
|| media.extension !== ownedMedia.extension
|
|
656
|
+
|| media.mediaType !== ownedMedia.mediaType
|
|
657
|
+
) {
|
|
658
|
+
await abortSink(sink, controller.signal);
|
|
659
|
+
return resultError(new CaptureFailure("media-changed", "direct HTTP media changed after its probe"));
|
|
660
|
+
}
|
|
661
|
+
if (state.effectiveUrlSha256 === null) {
|
|
662
|
+
await abortSink(sink, controller.signal);
|
|
663
|
+
return resultError(new CaptureFailure("transport", "direct HTTP capture omitted effective provenance"));
|
|
664
|
+
}
|
|
665
|
+
let sha256: string;
|
|
666
|
+
try {
|
|
667
|
+
sha256 = state.hash.digest("hex");
|
|
668
|
+
await sinkOperation(
|
|
669
|
+
sink.close(controller.signal),
|
|
670
|
+
deadline,
|
|
671
|
+
controller.signal,
|
|
672
|
+
"direct HTTP capture could not close before its total timeout",
|
|
673
|
+
);
|
|
674
|
+
} catch (error) {
|
|
675
|
+
await abortSink(sink, controller.signal);
|
|
676
|
+
if (error instanceof CaptureFailure) {
|
|
677
|
+
return resultError(totalTimedOut
|
|
678
|
+
? new CaptureFailure("total-timeout", "direct HTTP capture exceeded its total timeout")
|
|
679
|
+
: error);
|
|
680
|
+
}
|
|
681
|
+
return resultError(new CaptureFailure("sink", "direct HTTP capture could not finalize its destination"));
|
|
682
|
+
}
|
|
683
|
+
const provenance: MediaDirectHttpProvenance = {
|
|
684
|
+
requestedUrlSha256,
|
|
685
|
+
effectiveUrlSha256: state.effectiveUrlSha256,
|
|
686
|
+
validator: state.validator,
|
|
687
|
+
lastModified: state.lastModified,
|
|
688
|
+
declaredMediaType: state.declaredMediaType,
|
|
689
|
+
container: media.container,
|
|
690
|
+
body: { bytes: state.bytes, sha256 },
|
|
691
|
+
redirectCount: state.redirectCount,
|
|
692
|
+
};
|
|
693
|
+
return {
|
|
694
|
+
ok: true,
|
|
695
|
+
capture: {
|
|
696
|
+
bytes: state.bytes,
|
|
697
|
+
sha256,
|
|
698
|
+
media,
|
|
699
|
+
provenance,
|
|
700
|
+
attempts,
|
|
701
|
+
resumed,
|
|
702
|
+
},
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
await abortSink(sink, controller.signal);
|
|
706
|
+
return resultError(new CaptureFailure("transport", "direct HTTP capture exhausted its attempts"));
|
|
707
|
+
} finally {
|
|
708
|
+
clearTimeout(totalTimer);
|
|
709
|
+
options.signal?.removeEventListener("abort", abortFromParent);
|
|
710
|
+
}
|
|
711
|
+
}
|