@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,593 @@
|
|
|
1
|
+
import type { MediaDirectHttpValidator } from "./manifest";
|
|
2
|
+
import {
|
|
3
|
+
DIRECT_HTTP_MAX_BODY_BYTES,
|
|
4
|
+
DIRECT_HTTP_MAX_REDIRECTS,
|
|
5
|
+
DIRECT_HTTP_PROBE_BYTES,
|
|
6
|
+
DirectHttpBoundaryError,
|
|
7
|
+
conflictsWithDirectMedia,
|
|
8
|
+
detectDirectHttpMedia,
|
|
9
|
+
normalizeDeclaredMediaType,
|
|
10
|
+
normalizeLastModified,
|
|
11
|
+
parseContentLength,
|
|
12
|
+
parseContentRange,
|
|
13
|
+
parsePublicHttpUrl,
|
|
14
|
+
publicOrigin,
|
|
15
|
+
resolveDirectHttpRedirect,
|
|
16
|
+
sha256Hex,
|
|
17
|
+
strongEtag,
|
|
18
|
+
validatorFromEtag,
|
|
19
|
+
type DirectHttpMedia,
|
|
20
|
+
} from "./http";
|
|
21
|
+
|
|
22
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
|
|
23
|
+
const DIRECT_HTTP_ACCEPT = "video/*, audio/*, application/ogg, application/octet-stream;q=0.5";
|
|
24
|
+
const REDIRECT_STATUSES: ReadonlySet<number> = new Set([301, 302, 303, 307, 308]);
|
|
25
|
+
|
|
26
|
+
export type DirectHttpOwnedRequest =
|
|
27
|
+
| Readonly<{ method: "HEAD" }>
|
|
28
|
+
| Readonly<{ method: "GET"; range?: string; ifRange?: string }>;
|
|
29
|
+
|
|
30
|
+
export interface DirectHttpFetchOptions {
|
|
31
|
+
readonly maxRedirects?: number;
|
|
32
|
+
readonly timeoutMs?: number;
|
|
33
|
+
readonly signal?: AbortSignal;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface DirectHttpFetchDependencies {
|
|
37
|
+
readonly fetch: (input: string, init: RequestInit) => Promise<Response>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface DirectHttpFetchedResponse {
|
|
41
|
+
readonly response: Response;
|
|
42
|
+
readonly requestedUrl: URL;
|
|
43
|
+
readonly effectiveUrl: URL;
|
|
44
|
+
readonly redirectCount: number;
|
|
45
|
+
/** Releases the parent-abort bridge after the response body is finished. */
|
|
46
|
+
readonly dispose: () => void;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type DirectHttpTransportErrorCode =
|
|
50
|
+
| "invalid-request"
|
|
51
|
+
| "redirect-policy"
|
|
52
|
+
| "too-many-redirects"
|
|
53
|
+
| "timeout"
|
|
54
|
+
| "aborted"
|
|
55
|
+
| "network";
|
|
56
|
+
|
|
57
|
+
export class DirectHttpTransportError extends Error {
|
|
58
|
+
readonly code: DirectHttpTransportErrorCode;
|
|
59
|
+
|
|
60
|
+
constructor(code: DirectHttpTransportErrorCode, message: string) {
|
|
61
|
+
super(message);
|
|
62
|
+
this.name = "DirectHttpTransportError";
|
|
63
|
+
this.code = code;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Keeps a request URL available to the adapter without making it serializable. */
|
|
68
|
+
export class DirectHttpProbeTransport {
|
|
69
|
+
readonly #requestUrl: string;
|
|
70
|
+
|
|
71
|
+
constructor(value: string) {
|
|
72
|
+
this.#requestUrl = parsePublicHttpUrl(value).href;
|
|
73
|
+
Object.freeze(this);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
requestUrl(): string {
|
|
77
|
+
return this.#requestUrl;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
toJSON(): Readonly<{ kind: "opaque-direct-http-transport" }> {
|
|
81
|
+
return { kind: "opaque-direct-http-transport" };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface DirectHttpProbe {
|
|
86
|
+
/** Raw request URL is held only inside this non-enumerable transport handle. */
|
|
87
|
+
readonly transport: DirectHttpProbeTransport;
|
|
88
|
+
readonly publicOrigin: string;
|
|
89
|
+
readonly requestedUrlSha256: string;
|
|
90
|
+
readonly effectiveUrlSha256: string;
|
|
91
|
+
readonly redirectCount: number;
|
|
92
|
+
readonly declaredMediaType: string | null;
|
|
93
|
+
readonly lastModified: string | null;
|
|
94
|
+
readonly validator: MediaDirectHttpValidator;
|
|
95
|
+
readonly media: DirectHttpMedia;
|
|
96
|
+
readonly expectedBytes: number | null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface DirectHttpProbeOptions extends DirectHttpFetchOptions {
|
|
100
|
+
readonly maximumBodyBytes?: number;
|
|
101
|
+
readonly probeBytes?: number;
|
|
102
|
+
readonly headTimeoutMs?: number;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export type DirectHttpProbeNotApplicableReason =
|
|
106
|
+
| "http-status"
|
|
107
|
+
| "empty-response"
|
|
108
|
+
| "declared-text"
|
|
109
|
+
| "unrecognized-media";
|
|
110
|
+
|
|
111
|
+
export type DirectHttpProbeErrorCode =
|
|
112
|
+
| DirectHttpTransportErrorCode
|
|
113
|
+
| "unsupported-content-encoding"
|
|
114
|
+
| "invalid-response-length"
|
|
115
|
+
| "invalid-content-type"
|
|
116
|
+
| "body-too-large"
|
|
117
|
+
| "response-read";
|
|
118
|
+
|
|
119
|
+
export type DirectHttpProbeResult =
|
|
120
|
+
| Readonly<{ ok: true; probe: DirectHttpProbe }>
|
|
121
|
+
| Readonly<{ ok: false; kind: "not-applicable"; reason: DirectHttpProbeNotApplicableReason }>
|
|
122
|
+
| Readonly<{
|
|
123
|
+
ok: false;
|
|
124
|
+
kind: "error";
|
|
125
|
+
error: Readonly<{ code: DirectHttpProbeErrorCode; message: string }>;
|
|
126
|
+
}>;
|
|
127
|
+
|
|
128
|
+
const defaultFetchDependencies: DirectHttpFetchDependencies = {
|
|
129
|
+
fetch: async (input, init) => await fetch(input, init),
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
function normalizeBoundedInteger(
|
|
133
|
+
value: number | undefined,
|
|
134
|
+
fallback: number,
|
|
135
|
+
minimum: number,
|
|
136
|
+
maximum: number,
|
|
137
|
+
): number {
|
|
138
|
+
if (value === undefined) return fallback;
|
|
139
|
+
if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
|
|
140
|
+
throw new DirectHttpTransportError(
|
|
141
|
+
"invalid-request",
|
|
142
|
+
"direct HTTP numeric option is outside its supported range",
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
return value;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function ownedHeaders(request: DirectHttpOwnedRequest): Headers {
|
|
149
|
+
const headers = new Headers({
|
|
150
|
+
Accept: DIRECT_HTTP_ACCEPT,
|
|
151
|
+
"Accept-Encoding": "identity",
|
|
152
|
+
});
|
|
153
|
+
if (request.method === "GET") {
|
|
154
|
+
if (request.range !== undefined) headers.set("Range", request.range);
|
|
155
|
+
if (request.ifRange !== undefined) headers.set("If-Range", request.ifRange);
|
|
156
|
+
}
|
|
157
|
+
return headers;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function cancelBody(response: Response): void {
|
|
161
|
+
void response.body?.cancel().catch(() => {
|
|
162
|
+
// The connection is already unusable; no response bytes cross the boundary.
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function validRange(value: string): boolean {
|
|
167
|
+
const match = /^bytes=(0|[1-9]\d*)-(0|[1-9]\d*)?$/u.exec(value);
|
|
168
|
+
if (match === null) return false;
|
|
169
|
+
const start = Number(match[1]);
|
|
170
|
+
const end = match[2] === undefined ? null : Number(match[2]);
|
|
171
|
+
return Number.isSafeInteger(start)
|
|
172
|
+
&& start >= 0
|
|
173
|
+
&& (end === null || (Number.isSafeInteger(end) && end >= start));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function validateOwnedRequest(request: DirectHttpOwnedRequest): void {
|
|
177
|
+
if (request.method === "HEAD") return;
|
|
178
|
+
if (request.method !== "GET") {
|
|
179
|
+
throw new DirectHttpTransportError("invalid-request", "direct HTTP method is invalid");
|
|
180
|
+
}
|
|
181
|
+
if (request.range !== undefined && !validRange(request.range)) {
|
|
182
|
+
throw new DirectHttpTransportError("invalid-request", "direct HTTP range is invalid");
|
|
183
|
+
}
|
|
184
|
+
if (request.ifRange !== undefined && strongEtag(request.ifRange) !== request.ifRange) {
|
|
185
|
+
throw new DirectHttpTransportError("invalid-request", "direct HTTP resume validator is invalid");
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function boundaryAsTransport(error: DirectHttpBoundaryError): DirectHttpTransportError {
|
|
190
|
+
return new DirectHttpTransportError(
|
|
191
|
+
error.code === "too-many-redirects" ? "too-many-redirects" : "redirect-policy",
|
|
192
|
+
"direct HTTP rejected the request or redirect policy",
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function abortReason(signal: AbortSignal, timedOut: boolean): DirectHttpTransportError {
|
|
197
|
+
return timedOut
|
|
198
|
+
? new DirectHttpTransportError("timeout", "direct HTTP request timed out")
|
|
199
|
+
: new DirectHttpTransportError(
|
|
200
|
+
signal.aborted ? "aborted" : "network",
|
|
201
|
+
signal.aborted ? "direct HTTP request was aborted" : "direct HTTP request failed",
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Performs one closed, manually redirected request with Ghostget media-owned headers only. */
|
|
206
|
+
export async function fetchWithDirectRedirects(
|
|
207
|
+
value: string,
|
|
208
|
+
request: DirectHttpOwnedRequest,
|
|
209
|
+
options: DirectHttpFetchOptions = {},
|
|
210
|
+
dependencies: DirectHttpFetchDependencies = defaultFetchDependencies,
|
|
211
|
+
): Promise<DirectHttpFetchedResponse> {
|
|
212
|
+
validateOwnedRequest(request);
|
|
213
|
+
let requestedUrl: URL;
|
|
214
|
+
try {
|
|
215
|
+
requestedUrl = parsePublicHttpUrl(value);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
if (error instanceof DirectHttpBoundaryError) throw boundaryAsTransport(error);
|
|
218
|
+
throw new DirectHttpTransportError("invalid-request", "direct HTTP request is invalid");
|
|
219
|
+
}
|
|
220
|
+
const maxRedirects = normalizeBoundedInteger(
|
|
221
|
+
options.maxRedirects,
|
|
222
|
+
DIRECT_HTTP_MAX_REDIRECTS,
|
|
223
|
+
0,
|
|
224
|
+
DIRECT_HTTP_MAX_REDIRECTS,
|
|
225
|
+
);
|
|
226
|
+
const timeoutMs = normalizeBoundedInteger(
|
|
227
|
+
options.timeoutMs,
|
|
228
|
+
DEFAULT_REQUEST_TIMEOUT_MS,
|
|
229
|
+
1,
|
|
230
|
+
10 * 60_000,
|
|
231
|
+
);
|
|
232
|
+
if (request.method === "GET" && request.ifRange !== undefined && maxRedirects !== 0) {
|
|
233
|
+
throw new DirectHttpTransportError(
|
|
234
|
+
"invalid-request",
|
|
235
|
+
"direct HTTP resume requests must disable redirects",
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
const controller = new AbortController();
|
|
239
|
+
let timedOut = false;
|
|
240
|
+
const timeout = setTimeout(() => {
|
|
241
|
+
timedOut = true;
|
|
242
|
+
controller.abort();
|
|
243
|
+
}, timeoutMs);
|
|
244
|
+
const abortFromParent = (): void => controller.abort();
|
|
245
|
+
options.signal?.addEventListener("abort", abortFromParent, { once: true });
|
|
246
|
+
if (options.signal?.aborted === true) controller.abort();
|
|
247
|
+
|
|
248
|
+
let current = requestedUrl;
|
|
249
|
+
let redirectCount = 0;
|
|
250
|
+
let handedOff = false;
|
|
251
|
+
let disposed = false;
|
|
252
|
+
const dispose = (): void => {
|
|
253
|
+
if (disposed) return;
|
|
254
|
+
disposed = true;
|
|
255
|
+
clearTimeout(timeout);
|
|
256
|
+
options.signal?.removeEventListener("abort", abortFromParent);
|
|
257
|
+
};
|
|
258
|
+
try {
|
|
259
|
+
while (true) {
|
|
260
|
+
let response: Response;
|
|
261
|
+
try {
|
|
262
|
+
response = await dependencies.fetch(current.href, {
|
|
263
|
+
method: request.method,
|
|
264
|
+
headers: ownedHeaders(request),
|
|
265
|
+
redirect: "manual",
|
|
266
|
+
credentials: "omit",
|
|
267
|
+
cache: "no-store",
|
|
268
|
+
referrerPolicy: "no-referrer",
|
|
269
|
+
signal: controller.signal,
|
|
270
|
+
});
|
|
271
|
+
} catch {
|
|
272
|
+
throw abortReason(controller.signal, timedOut);
|
|
273
|
+
}
|
|
274
|
+
if (!REDIRECT_STATUSES.has(response.status)) {
|
|
275
|
+
clearTimeout(timeout);
|
|
276
|
+
handedOff = true;
|
|
277
|
+
return { response, requestedUrl, effectiveUrl: current, redirectCount, dispose };
|
|
278
|
+
}
|
|
279
|
+
const location = response.headers.get("location");
|
|
280
|
+
cancelBody(response);
|
|
281
|
+
if (location === null) {
|
|
282
|
+
throw new DirectHttpTransportError(
|
|
283
|
+
"redirect-policy",
|
|
284
|
+
"direct HTTP redirect omitted its target",
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
if (redirectCount >= maxRedirects) {
|
|
288
|
+
throw new DirectHttpTransportError(
|
|
289
|
+
"too-many-redirects",
|
|
290
|
+
"direct HTTP exceeded the redirect limit",
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
try {
|
|
294
|
+
current = resolveDirectHttpRedirect(current, location);
|
|
295
|
+
} catch (error) {
|
|
296
|
+
if (error instanceof DirectHttpBoundaryError) throw boundaryAsTransport(error);
|
|
297
|
+
throw new DirectHttpTransportError(
|
|
298
|
+
"redirect-policy",
|
|
299
|
+
"direct HTTP rejected a redirect",
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
redirectCount += 1;
|
|
303
|
+
}
|
|
304
|
+
} finally {
|
|
305
|
+
if (!handedOff) dispose();
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function probeError(
|
|
310
|
+
code: DirectHttpProbeErrorCode,
|
|
311
|
+
message: string,
|
|
312
|
+
): Extract<DirectHttpProbeResult, { readonly kind: "error" }> {
|
|
313
|
+
return { ok: false, kind: "error", error: { code, message } };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function declaredLength(headers: Headers): Readonly<{
|
|
317
|
+
expectedBytes: number | null;
|
|
318
|
+
responseBytes: number | null;
|
|
319
|
+
}> | null {
|
|
320
|
+
const rawLength = headers.get("content-length");
|
|
321
|
+
const contentLength = parseContentLength(rawLength);
|
|
322
|
+
if (rawLength !== null && contentLength === null) return null;
|
|
323
|
+
return { expectedBytes: contentLength, responseBytes: contentLength };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function rangedLength(response: Response, probeBytes: number): Readonly<{
|
|
327
|
+
expectedBytes: number | null;
|
|
328
|
+
responseBytes: number;
|
|
329
|
+
}> | null {
|
|
330
|
+
const contentRange = parseContentRange(response.headers.get("content-range"));
|
|
331
|
+
if (contentRange === null || contentRange.start !== 0 || contentRange.end >= probeBytes) {
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
const responseBytes = contentRange.end - contentRange.start + 1;
|
|
335
|
+
const rawLength = response.headers.get("content-length");
|
|
336
|
+
const contentLength = parseContentLength(rawLength);
|
|
337
|
+
if (rawLength !== null && (contentLength === null || contentLength !== responseBytes)) return null;
|
|
338
|
+
return { expectedBytes: contentRange.total, responseBytes };
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async function boundedResponsePrefix(
|
|
342
|
+
response: Response,
|
|
343
|
+
maximumBytes: number,
|
|
344
|
+
timeoutMs: number,
|
|
345
|
+
inspectOverflowAtLimit: boolean,
|
|
346
|
+
): Promise<Readonly<{ bytes: Uint8Array; ended: boolean; overflowed: boolean }>> {
|
|
347
|
+
const reader = response.body?.getReader();
|
|
348
|
+
if (reader === undefined) {
|
|
349
|
+
return { bytes: new Uint8Array(), ended: true, overflowed: false };
|
|
350
|
+
}
|
|
351
|
+
const chunks: Uint8Array[] = [];
|
|
352
|
+
let total = 0;
|
|
353
|
+
let ended = false;
|
|
354
|
+
let overflowed = false;
|
|
355
|
+
const deadline = Date.now() + timeoutMs;
|
|
356
|
+
try {
|
|
357
|
+
while (true) {
|
|
358
|
+
if (total === maximumBytes && !inspectOverflowAtLimit) break;
|
|
359
|
+
const remainingMs = deadline - Date.now();
|
|
360
|
+
if (remainingMs <= 0) throw new Error("probe body timeout");
|
|
361
|
+
const read = await readWithDeadline(reader, remainingMs);
|
|
362
|
+
if (read.done) {
|
|
363
|
+
ended = true;
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
if (total === maximumBytes) {
|
|
367
|
+
overflowed = read.value.byteLength > 0;
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
const remaining = maximumBytes - total;
|
|
371
|
+
const chunk = read.value.byteLength <= remaining
|
|
372
|
+
? read.value
|
|
373
|
+
: read.value.subarray(0, remaining);
|
|
374
|
+
chunks.push(chunk);
|
|
375
|
+
total += chunk.byteLength;
|
|
376
|
+
if (chunk.byteLength < read.value.byteLength) {
|
|
377
|
+
overflowed = true;
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
} finally {
|
|
382
|
+
void reader.cancel().catch(() => {
|
|
383
|
+
// A failed cancellation cannot make bounded bytes trusted or persistent.
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
const bytes = new Uint8Array(total);
|
|
387
|
+
let offset = 0;
|
|
388
|
+
for (const chunk of chunks) {
|
|
389
|
+
bytes.set(chunk, offset);
|
|
390
|
+
offset += chunk.byteLength;
|
|
391
|
+
}
|
|
392
|
+
return { bytes, ended, overflowed };
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
type OwnedByteReadResult =
|
|
396
|
+
| Readonly<{ done: true }>
|
|
397
|
+
| Readonly<{ done: false; value: Uint8Array }>;
|
|
398
|
+
|
|
399
|
+
interface OwnedByteReader {
|
|
400
|
+
readonly read: () => Promise<Readonly<{ done: boolean; value?: Uint8Array }>>;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
async function readWithDeadline(
|
|
404
|
+
reader: OwnedByteReader,
|
|
405
|
+
timeoutMs: number,
|
|
406
|
+
): Promise<OwnedByteReadResult> {
|
|
407
|
+
return await new Promise<OwnedByteReadResult>((resolve, reject) => {
|
|
408
|
+
const timer = setTimeout(() => reject(new Error("probe body timeout")), timeoutMs);
|
|
409
|
+
void reader.read().then(
|
|
410
|
+
(result) => {
|
|
411
|
+
clearTimeout(timer);
|
|
412
|
+
if (result.done) {
|
|
413
|
+
resolve({ done: true });
|
|
414
|
+
} else if (result.value !== undefined) {
|
|
415
|
+
resolve({ done: false, value: result.value });
|
|
416
|
+
} else {
|
|
417
|
+
reject(new Error("probe body reader omitted a chunk"));
|
|
418
|
+
}
|
|
419
|
+
},
|
|
420
|
+
(error: unknown) => {
|
|
421
|
+
clearTimeout(timer);
|
|
422
|
+
reject(error instanceof Error ? error : new Error("probe body read failed"));
|
|
423
|
+
},
|
|
424
|
+
);
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/** Probes one public URL without allowing a page response to become a capture. */
|
|
429
|
+
export async function probeDirectHttp(
|
|
430
|
+
value: string,
|
|
431
|
+
options: DirectHttpProbeOptions = {},
|
|
432
|
+
dependencies: DirectHttpFetchDependencies = defaultFetchDependencies,
|
|
433
|
+
): Promise<DirectHttpProbeResult> {
|
|
434
|
+
let maximumBodyBytes: number;
|
|
435
|
+
let probeBytes: number;
|
|
436
|
+
let bodyTimeoutMs: number;
|
|
437
|
+
let headTimeoutMs: number;
|
|
438
|
+
try {
|
|
439
|
+
maximumBodyBytes = normalizeBoundedInteger(
|
|
440
|
+
options.maximumBodyBytes,
|
|
441
|
+
DIRECT_HTTP_MAX_BODY_BYTES,
|
|
442
|
+
1,
|
|
443
|
+
DIRECT_HTTP_MAX_BODY_BYTES,
|
|
444
|
+
);
|
|
445
|
+
probeBytes = normalizeBoundedInteger(
|
|
446
|
+
options.probeBytes,
|
|
447
|
+
DIRECT_HTTP_PROBE_BYTES,
|
|
448
|
+
1,
|
|
449
|
+
DIRECT_HTTP_PROBE_BYTES,
|
|
450
|
+
);
|
|
451
|
+
bodyTimeoutMs = normalizeBoundedInteger(
|
|
452
|
+
options.timeoutMs,
|
|
453
|
+
DEFAULT_REQUEST_TIMEOUT_MS,
|
|
454
|
+
1,
|
|
455
|
+
10 * 60_000,
|
|
456
|
+
);
|
|
457
|
+
headTimeoutMs = normalizeBoundedInteger(options.headTimeoutMs, 5_000, 1, 30_000);
|
|
458
|
+
normalizeBoundedInteger(
|
|
459
|
+
options.maxRedirects,
|
|
460
|
+
DIRECT_HTTP_MAX_REDIRECTS,
|
|
461
|
+
0,
|
|
462
|
+
DIRECT_HTTP_MAX_REDIRECTS,
|
|
463
|
+
);
|
|
464
|
+
} catch (error) {
|
|
465
|
+
return probeError(
|
|
466
|
+
"invalid-request",
|
|
467
|
+
error instanceof DirectHttpTransportError
|
|
468
|
+
? error.message
|
|
469
|
+
: "direct HTTP probe options are invalid",
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
const common: DirectHttpFetchOptions = {
|
|
473
|
+
...(options.maxRedirects === undefined ? {} : { maxRedirects: options.maxRedirects }),
|
|
474
|
+
...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
|
|
475
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
476
|
+
};
|
|
477
|
+
const probeLimit = Math.min(probeBytes, maximumBodyBytes);
|
|
478
|
+
|
|
479
|
+
// HEAD may cheaply warm a connection or expose size, but it is never trusted
|
|
480
|
+
// as the media decision and its ordinary failures do not block the range GET.
|
|
481
|
+
try {
|
|
482
|
+
const head = await fetchWithDirectRedirects(
|
|
483
|
+
value,
|
|
484
|
+
{ method: "HEAD" },
|
|
485
|
+
{
|
|
486
|
+
...common,
|
|
487
|
+
timeoutMs: headTimeoutMs,
|
|
488
|
+
},
|
|
489
|
+
dependencies,
|
|
490
|
+
);
|
|
491
|
+
cancelBody(head.response);
|
|
492
|
+
head.dispose();
|
|
493
|
+
} catch {
|
|
494
|
+
// The GET below repeats every redirect and security check independently.
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
let fetched: DirectHttpFetchedResponse;
|
|
498
|
+
try {
|
|
499
|
+
fetched = await fetchWithDirectRedirects(
|
|
500
|
+
value,
|
|
501
|
+
{ method: "GET", range: `bytes=0-${String(probeLimit - 1)}` },
|
|
502
|
+
common,
|
|
503
|
+
dependencies,
|
|
504
|
+
);
|
|
505
|
+
} catch (error) {
|
|
506
|
+
if (error instanceof DirectHttpTransportError) return probeError(error.code, error.message);
|
|
507
|
+
return probeError("network", "direct HTTP probe failed");
|
|
508
|
+
}
|
|
509
|
+
const { response } = fetched;
|
|
510
|
+
try {
|
|
511
|
+
if (response.status !== 200 && response.status !== 206) {
|
|
512
|
+
cancelBody(response);
|
|
513
|
+
return { ok: false, kind: "not-applicable", reason: "http-status" };
|
|
514
|
+
}
|
|
515
|
+
const contentEncoding = response.headers.get("content-encoding")?.trim().toLowerCase();
|
|
516
|
+
if (contentEncoding !== undefined && contentEncoding !== "" && contentEncoding !== "identity") {
|
|
517
|
+
cancelBody(response);
|
|
518
|
+
return probeError(
|
|
519
|
+
"unsupported-content-encoding",
|
|
520
|
+
"direct HTTP response did not use identity encoding",
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
const length = response.status === 206
|
|
524
|
+
? rangedLength(response, probeLimit)
|
|
525
|
+
: declaredLength(response.headers);
|
|
526
|
+
if (length === null) {
|
|
527
|
+
cancelBody(response);
|
|
528
|
+
return probeError("invalid-response-length", "direct HTTP response length is inconsistent");
|
|
529
|
+
}
|
|
530
|
+
if (length.expectedBytes !== null && length.expectedBytes > maximumBodyBytes) {
|
|
531
|
+
cancelBody(response);
|
|
532
|
+
return probeError("body-too-large", "direct HTTP body exceeds the configured limit");
|
|
533
|
+
}
|
|
534
|
+
let prefix: Awaited<ReturnType<typeof boundedResponsePrefix>>;
|
|
535
|
+
try {
|
|
536
|
+
prefix = await boundedResponsePrefix(
|
|
537
|
+
response,
|
|
538
|
+
probeLimit,
|
|
539
|
+
bodyTimeoutMs,
|
|
540
|
+
length.responseBytes !== null || probeLimit === maximumBodyBytes,
|
|
541
|
+
);
|
|
542
|
+
} catch {
|
|
543
|
+
return probeError("response-read", "direct HTTP probe body could not be read");
|
|
544
|
+
}
|
|
545
|
+
if (
|
|
546
|
+
length.responseBytes !== null
|
|
547
|
+
&& (
|
|
548
|
+
(length.responseBytes <= probeLimit
|
|
549
|
+
&& (prefix.bytes.byteLength !== length.responseBytes || prefix.overflowed))
|
|
550
|
+
|| (prefix.ended && prefix.bytes.byteLength !== length.responseBytes)
|
|
551
|
+
)
|
|
552
|
+
) {
|
|
553
|
+
return probeError("invalid-response-length", "direct HTTP response body length disagrees with headers");
|
|
554
|
+
}
|
|
555
|
+
if (probeLimit === maximumBodyBytes && prefix.overflowed) {
|
|
556
|
+
return probeError("body-too-large", "direct HTTP body exceeds the configured limit");
|
|
557
|
+
}
|
|
558
|
+
if (prefix.bytes.byteLength === 0) {
|
|
559
|
+
return { ok: false, kind: "not-applicable", reason: "empty-response" };
|
|
560
|
+
}
|
|
561
|
+
const rawMediaType = response.headers.get("content-type");
|
|
562
|
+
const declaredMediaType = normalizeDeclaredMediaType(rawMediaType);
|
|
563
|
+
if (rawMediaType !== null && declaredMediaType === null) {
|
|
564
|
+
return probeError("invalid-content-type", "direct HTTP response media type is malformed");
|
|
565
|
+
}
|
|
566
|
+
if (conflictsWithDirectMedia(rawMediaType)) {
|
|
567
|
+
return { ok: false, kind: "not-applicable", reason: "declared-text" };
|
|
568
|
+
}
|
|
569
|
+
const media = detectDirectHttpMedia(prefix.bytes);
|
|
570
|
+
if (media === null) {
|
|
571
|
+
return { ok: false, kind: "not-applicable", reason: "unrecognized-media" };
|
|
572
|
+
}
|
|
573
|
+
const requestedUrl = fetched.requestedUrl.href;
|
|
574
|
+
const effectiveUrl = fetched.effectiveUrl.href;
|
|
575
|
+
return {
|
|
576
|
+
ok: true,
|
|
577
|
+
probe: {
|
|
578
|
+
transport: new DirectHttpProbeTransport(requestedUrl),
|
|
579
|
+
publicOrigin: publicOrigin(fetched.requestedUrl),
|
|
580
|
+
requestedUrlSha256: sha256Hex(requestedUrl),
|
|
581
|
+
effectiveUrlSha256: sha256Hex(effectiveUrl),
|
|
582
|
+
redirectCount: fetched.redirectCount,
|
|
583
|
+
declaredMediaType,
|
|
584
|
+
lastModified: normalizeLastModified(response.headers.get("last-modified")),
|
|
585
|
+
validator: validatorFromEtag(response.headers.get("etag")),
|
|
586
|
+
media,
|
|
587
|
+
expectedBytes: length.expectedBytes,
|
|
588
|
+
},
|
|
589
|
+
};
|
|
590
|
+
} finally {
|
|
591
|
+
fetched.dispose();
|
|
592
|
+
}
|
|
593
|
+
}
|