@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,1625 @@
|
|
|
1
|
+
import { isIP } from "node:net";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
PORTABLE_PROVIDER_PLUGIN_HOST_API_VERSION,
|
|
5
|
+
isPortableProviderPluginVersion,
|
|
6
|
+
portableProviderPluginSessionMaterialNames,
|
|
7
|
+
type PortableProviderPluginAuthKind,
|
|
8
|
+
type PortableProviderPluginCapabilitiesV1,
|
|
9
|
+
type PortableProviderPluginSessionMaterialName,
|
|
10
|
+
type PortableProviderPluginTransport,
|
|
11
|
+
} from "./provider-plugin-package";
|
|
12
|
+
|
|
13
|
+
export const PORTABLE_PROVIDER_PLUGIN_PROTOCOL_VERSION = 1 as const;
|
|
14
|
+
export const MAX_PORTABLE_PROVIDER_PLUGIN_FRAME_BYTES = 1024 * 1024;
|
|
15
|
+
|
|
16
|
+
export type PortablePluginJsonValue =
|
|
17
|
+
| null
|
|
18
|
+
| boolean
|
|
19
|
+
| number
|
|
20
|
+
| string
|
|
21
|
+
| readonly PortablePluginJsonValue[]
|
|
22
|
+
| PortablePluginJsonObject;
|
|
23
|
+
|
|
24
|
+
export interface PortablePluginJsonObject {
|
|
25
|
+
readonly [key: string]: PortablePluginJsonValue;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type PortablePluginIdentity = {
|
|
29
|
+
readonly id: string;
|
|
30
|
+
readonly version: string;
|
|
31
|
+
readonly bundleSha256: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type PortablePluginRoute = {
|
|
35
|
+
readonly transport: PortableProviderPluginTransport;
|
|
36
|
+
readonly surfaceId: string;
|
|
37
|
+
readonly operation: string;
|
|
38
|
+
readonly contractVersion: number;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type PortablePluginInvocationAuth = {
|
|
42
|
+
readonly kind: PortableProviderPluginAuthKind;
|
|
43
|
+
readonly handle: string;
|
|
44
|
+
readonly subject?: string;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type PortablePluginInvocationFile = {
|
|
48
|
+
readonly input: string;
|
|
49
|
+
readonly handle: string;
|
|
50
|
+
readonly bytes: number;
|
|
51
|
+
readonly mediaType: string;
|
|
52
|
+
readonly sha256: string;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type PortablePluginCredentialSink =
|
|
56
|
+
| {
|
|
57
|
+
readonly kind: "cookie-jar";
|
|
58
|
+
}
|
|
59
|
+
| {
|
|
60
|
+
readonly kind: "header";
|
|
61
|
+
readonly name: "authorization";
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export type PortablePluginCredentialBinding = {
|
|
65
|
+
readonly handle: string;
|
|
66
|
+
readonly sink: PortablePluginCredentialSink;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export type PortablePluginHttpBody =
|
|
70
|
+
| {
|
|
71
|
+
readonly kind: "none";
|
|
72
|
+
}
|
|
73
|
+
| {
|
|
74
|
+
readonly kind: "utf8";
|
|
75
|
+
readonly mediaType: string;
|
|
76
|
+
readonly text: string;
|
|
77
|
+
}
|
|
78
|
+
| {
|
|
79
|
+
readonly kind: "base64";
|
|
80
|
+
readonly mediaType: string;
|
|
81
|
+
readonly data: string;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export type PortablePluginCapabilityRequest =
|
|
85
|
+
| {
|
|
86
|
+
readonly kind: "dispatch.begin";
|
|
87
|
+
readonly dispatchId: string;
|
|
88
|
+
}
|
|
89
|
+
| {
|
|
90
|
+
readonly kind: "dispatch.verify";
|
|
91
|
+
readonly dispatchHandle: string;
|
|
92
|
+
readonly proof: PortablePluginJsonObject;
|
|
93
|
+
}
|
|
94
|
+
| {
|
|
95
|
+
readonly kind: "http.request";
|
|
96
|
+
readonly method: "DELETE" | "GET" | "HEAD" | "OPTIONS" | "PATCH" | "POST" | "PUT";
|
|
97
|
+
readonly url: string;
|
|
98
|
+
readonly headers: readonly {
|
|
99
|
+
readonly name: string;
|
|
100
|
+
readonly value: string;
|
|
101
|
+
}[];
|
|
102
|
+
readonly credentials: readonly PortablePluginCredentialBinding[];
|
|
103
|
+
readonly body: PortablePluginHttpBody;
|
|
104
|
+
readonly redirect: "error";
|
|
105
|
+
readonly timeoutMs: number;
|
|
106
|
+
readonly maxOutputBytes: number;
|
|
107
|
+
readonly dispatchHandle?: string;
|
|
108
|
+
}
|
|
109
|
+
| {
|
|
110
|
+
readonly kind: "file.read";
|
|
111
|
+
readonly handle: string;
|
|
112
|
+
readonly offset: number;
|
|
113
|
+
readonly length: number;
|
|
114
|
+
}
|
|
115
|
+
| {
|
|
116
|
+
readonly kind: "state.read";
|
|
117
|
+
readonly key: string;
|
|
118
|
+
/** Opt in to the additive exact-byte version result. */
|
|
119
|
+
readonly includeVersion?: true;
|
|
120
|
+
}
|
|
121
|
+
| {
|
|
122
|
+
readonly kind: "state.write";
|
|
123
|
+
readonly key: string;
|
|
124
|
+
readonly value: PortablePluginJsonValue;
|
|
125
|
+
/** Exact-byte version returned by state.read; null creates only if absent. */
|
|
126
|
+
readonly expectedVersion?: string | null;
|
|
127
|
+
}
|
|
128
|
+
| {
|
|
129
|
+
readonly kind: "state.delete";
|
|
130
|
+
readonly key: string;
|
|
131
|
+
/** Exact-byte version returned by state.read. */
|
|
132
|
+
readonly expectedVersion?: string;
|
|
133
|
+
}
|
|
134
|
+
| {
|
|
135
|
+
readonly kind: "session.acquire";
|
|
136
|
+
readonly name: PortableProviderPluginSessionMaterialName;
|
|
137
|
+
}
|
|
138
|
+
| {
|
|
139
|
+
readonly kind: "log.write";
|
|
140
|
+
readonly level: "debug" | "info" | "warn";
|
|
141
|
+
readonly message: string;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export type PortablePluginCapabilityResult =
|
|
145
|
+
| {
|
|
146
|
+
readonly kind: "dispatch.begin";
|
|
147
|
+
readonly dispatchHandle: string;
|
|
148
|
+
}
|
|
149
|
+
| {
|
|
150
|
+
readonly kind: "dispatch.verify";
|
|
151
|
+
readonly verified: true;
|
|
152
|
+
}
|
|
153
|
+
| {
|
|
154
|
+
readonly kind: "http.request";
|
|
155
|
+
readonly status: number;
|
|
156
|
+
readonly headers: readonly {
|
|
157
|
+
readonly name: string;
|
|
158
|
+
readonly value: string;
|
|
159
|
+
}[];
|
|
160
|
+
readonly body:
|
|
161
|
+
| {
|
|
162
|
+
readonly kind: "utf8";
|
|
163
|
+
readonly text: string;
|
|
164
|
+
}
|
|
165
|
+
| {
|
|
166
|
+
readonly kind: "base64";
|
|
167
|
+
readonly data: string;
|
|
168
|
+
};
|
|
169
|
+
readonly finalUrl: string;
|
|
170
|
+
}
|
|
171
|
+
| {
|
|
172
|
+
readonly kind: "file.read";
|
|
173
|
+
readonly data: string;
|
|
174
|
+
readonly eof: boolean;
|
|
175
|
+
}
|
|
176
|
+
| {
|
|
177
|
+
readonly kind: "state.read";
|
|
178
|
+
readonly found: false;
|
|
179
|
+
}
|
|
180
|
+
| {
|
|
181
|
+
readonly kind: "state.read";
|
|
182
|
+
readonly found: true;
|
|
183
|
+
readonly value: PortablePluginJsonValue;
|
|
184
|
+
}
|
|
185
|
+
| {
|
|
186
|
+
readonly kind: "state.write";
|
|
187
|
+
readonly stored: true;
|
|
188
|
+
}
|
|
189
|
+
| {
|
|
190
|
+
readonly kind: "state.delete";
|
|
191
|
+
readonly removed: boolean;
|
|
192
|
+
}
|
|
193
|
+
| {
|
|
194
|
+
readonly kind: "session.acquire";
|
|
195
|
+
readonly materialHandle: string;
|
|
196
|
+
}
|
|
197
|
+
| {
|
|
198
|
+
readonly kind: "log.write";
|
|
199
|
+
readonly accepted: true;
|
|
200
|
+
}
|
|
201
|
+
| PortablePluginVersionedStateResult;
|
|
202
|
+
|
|
203
|
+
/** Additive result shape emitted by the host for version-aware state peers. */
|
|
204
|
+
export type PortablePluginVersionedStateResult =
|
|
205
|
+
| {
|
|
206
|
+
readonly kind: "state.read";
|
|
207
|
+
readonly found: false;
|
|
208
|
+
readonly version: null;
|
|
209
|
+
}
|
|
210
|
+
| {
|
|
211
|
+
readonly kind: "state.read";
|
|
212
|
+
readonly found: true;
|
|
213
|
+
readonly value: PortablePluginJsonValue;
|
|
214
|
+
readonly version: string;
|
|
215
|
+
}
|
|
216
|
+
| {
|
|
217
|
+
readonly kind: "state.write";
|
|
218
|
+
readonly stored: true;
|
|
219
|
+
readonly version: string;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
export type PortableProviderPluginHostMessage =
|
|
223
|
+
| {
|
|
224
|
+
readonly protocolVersion: typeof PORTABLE_PROVIDER_PLUGIN_PROTOCOL_VERSION;
|
|
225
|
+
readonly kind: "host.hello";
|
|
226
|
+
readonly hostVersion: string;
|
|
227
|
+
readonly hostApiVersion: typeof PORTABLE_PROVIDER_PLUGIN_HOST_API_VERSION;
|
|
228
|
+
readonly plugin: PortablePluginIdentity;
|
|
229
|
+
readonly granted: PortableProviderPluginCapabilitiesV1;
|
|
230
|
+
}
|
|
231
|
+
| {
|
|
232
|
+
readonly protocolVersion: typeof PORTABLE_PROVIDER_PLUGIN_PROTOCOL_VERSION;
|
|
233
|
+
readonly kind: "host.invoke";
|
|
234
|
+
readonly invocationId: string;
|
|
235
|
+
readonly route: PortablePluginRoute;
|
|
236
|
+
readonly input: PortablePluginJsonObject;
|
|
237
|
+
readonly auth: PortablePluginInvocationAuth;
|
|
238
|
+
readonly files: readonly PortablePluginInvocationFile[];
|
|
239
|
+
readonly timeoutMs: number;
|
|
240
|
+
}
|
|
241
|
+
| {
|
|
242
|
+
readonly protocolVersion: typeof PORTABLE_PROVIDER_PLUGIN_PROTOCOL_VERSION;
|
|
243
|
+
readonly kind: "host.capability.result";
|
|
244
|
+
readonly invocationId: string;
|
|
245
|
+
readonly requestId: string;
|
|
246
|
+
readonly result: PortablePluginCapabilityResult;
|
|
247
|
+
}
|
|
248
|
+
| {
|
|
249
|
+
readonly protocolVersion: typeof PORTABLE_PROVIDER_PLUGIN_PROTOCOL_VERSION;
|
|
250
|
+
readonly kind: "host.capability.error";
|
|
251
|
+
readonly invocationId: string;
|
|
252
|
+
readonly requestId: string;
|
|
253
|
+
readonly capability: PortablePluginCapabilityRequest["kind"];
|
|
254
|
+
readonly error: {
|
|
255
|
+
readonly code: string;
|
|
256
|
+
readonly message: string;
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
| {
|
|
260
|
+
readonly protocolVersion: typeof PORTABLE_PROVIDER_PLUGIN_PROTOCOL_VERSION;
|
|
261
|
+
readonly kind: "host.cancel";
|
|
262
|
+
readonly invocationId: string;
|
|
263
|
+
readonly reason: "user" | "timeout" | "shutdown";
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
export type PortableProviderPluginProcessMessage =
|
|
267
|
+
| {
|
|
268
|
+
readonly protocolVersion: typeof PORTABLE_PROVIDER_PLUGIN_PROTOCOL_VERSION;
|
|
269
|
+
readonly kind: "plugin.ready";
|
|
270
|
+
readonly plugin: PortablePluginIdentity;
|
|
271
|
+
}
|
|
272
|
+
| {
|
|
273
|
+
readonly protocolVersion: typeof PORTABLE_PROVIDER_PLUGIN_PROTOCOL_VERSION;
|
|
274
|
+
readonly kind: "plugin.capability.request";
|
|
275
|
+
readonly invocationId: string;
|
|
276
|
+
readonly requestId: string;
|
|
277
|
+
readonly request: PortablePluginCapabilityRequest;
|
|
278
|
+
}
|
|
279
|
+
| {
|
|
280
|
+
readonly protocolVersion: typeof PORTABLE_PROVIDER_PLUGIN_PROTOCOL_VERSION;
|
|
281
|
+
readonly kind: "plugin.result";
|
|
282
|
+
readonly invocationId: string;
|
|
283
|
+
readonly output: PortablePluginJsonValue;
|
|
284
|
+
readonly finalUrl: string | null;
|
|
285
|
+
}
|
|
286
|
+
| {
|
|
287
|
+
readonly protocolVersion: typeof PORTABLE_PROVIDER_PLUGIN_PROTOCOL_VERSION;
|
|
288
|
+
readonly kind: "plugin.error";
|
|
289
|
+
readonly stage: "startup";
|
|
290
|
+
readonly code: string;
|
|
291
|
+
readonly message: string;
|
|
292
|
+
}
|
|
293
|
+
| {
|
|
294
|
+
readonly protocolVersion: typeof PORTABLE_PROVIDER_PLUGIN_PROTOCOL_VERSION;
|
|
295
|
+
readonly kind: "plugin.error";
|
|
296
|
+
readonly stage: "invocation";
|
|
297
|
+
readonly invocationId: string;
|
|
298
|
+
readonly code: string;
|
|
299
|
+
readonly message: string;
|
|
300
|
+
}
|
|
301
|
+
| {
|
|
302
|
+
readonly protocolVersion: typeof PORTABLE_PROVIDER_PLUGIN_PROTOCOL_VERSION;
|
|
303
|
+
readonly kind: "plugin.cancelled";
|
|
304
|
+
readonly invocationId: string;
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
export type PortableProviderPluginMessage =
|
|
308
|
+
| PortableProviderPluginHostMessage
|
|
309
|
+
| PortableProviderPluginProcessMessage;
|
|
310
|
+
|
|
311
|
+
export type PortableProviderPluginMessageResult =
|
|
312
|
+
| {
|
|
313
|
+
readonly ok: true;
|
|
314
|
+
readonly value: PortableProviderPluginMessage;
|
|
315
|
+
}
|
|
316
|
+
| {
|
|
317
|
+
readonly ok: false;
|
|
318
|
+
readonly issues: readonly string[];
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const MAX_JSON_DEPTH = 32;
|
|
322
|
+
const MAX_JSON_ARRAY_ITEMS = 10_000;
|
|
323
|
+
const MAX_JSON_OBJECT_KEYS = 1_000;
|
|
324
|
+
const MAX_JSON_STRING_BYTES = 512 * 1024;
|
|
325
|
+
const MAX_HEADERS = 128;
|
|
326
|
+
const MAX_CREDENTIAL_BINDINGS = 32;
|
|
327
|
+
const MAX_INVOCATION_FILES = 128;
|
|
328
|
+
const MAX_HTTP_BODY_BYTES = 768 * 1024;
|
|
329
|
+
const MAX_HTTP_OUTPUT_BYTES = 512 * 1024;
|
|
330
|
+
const MAX_HTTP_BASE64_OUTPUT_TEXT_BYTES =
|
|
331
|
+
4 * Math.ceil(MAX_HTTP_OUTPUT_BYTES / 3);
|
|
332
|
+
|
|
333
|
+
const tokenPattern = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
334
|
+
const pluginIdPattern = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u;
|
|
335
|
+
const operationPattern =
|
|
336
|
+
/^[a-z][a-z0-9-]{0,39}(?:\.[a-z][a-z0-9-]{0,39}){1,3}$/u;
|
|
337
|
+
const materialNamePattern =
|
|
338
|
+
/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/u;
|
|
339
|
+
const stateKeyPattern =
|
|
340
|
+
/^[A-Za-z0-9][A-Za-z0-9._/-]{0,255}$/u;
|
|
341
|
+
const mediaTypePattern =
|
|
342
|
+
/^[A-Za-z0-9!#$&^_.+-]+\/[A-Za-z0-9!#$&^_.+-]+(?:;[A-Za-z0-9!#$&^_.+*= -]+)?$/u;
|
|
343
|
+
const headerNamePattern = /^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,128}$/u;
|
|
344
|
+
const errorCodePattern = /^[A-Z][A-Z0-9_]{0,63}$/u;
|
|
345
|
+
const sha256Pattern = /^[a-f0-9]{64}$/u;
|
|
346
|
+
const base64Pattern = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u;
|
|
347
|
+
|
|
348
|
+
const requestCapabilityKinds = [
|
|
349
|
+
"dispatch.begin",
|
|
350
|
+
"dispatch.verify",
|
|
351
|
+
"http.request",
|
|
352
|
+
"file.read",
|
|
353
|
+
"state.read",
|
|
354
|
+
"state.write",
|
|
355
|
+
"state.delete",
|
|
356
|
+
"session.acquire",
|
|
357
|
+
"log.write",
|
|
358
|
+
] as const satisfies readonly PortablePluginCapabilityRequest["kind"][];
|
|
359
|
+
|
|
360
|
+
const forbiddenRawCredentialHeaders = new Set([
|
|
361
|
+
"authorization",
|
|
362
|
+
"cookie",
|
|
363
|
+
"proxy-authorization",
|
|
364
|
+
"set-cookie",
|
|
365
|
+
"x-api-key",
|
|
366
|
+
]);
|
|
367
|
+
|
|
368
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
369
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
const prototype = Object.getPrototypeOf(value) as unknown;
|
|
373
|
+
if (prototype !== Object.prototype && prototype !== null) return false;
|
|
374
|
+
return Reflect.ownKeys(value).every((key) => {
|
|
375
|
+
if (typeof key !== "string") return false;
|
|
376
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
377
|
+
return descriptor !== undefined
|
|
378
|
+
&& descriptor.enumerable
|
|
379
|
+
&& "value" in descriptor;
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function record(value: unknown, label: string): Record<string, unknown> {
|
|
384
|
+
if (!isRecord(value)) throw new Error(`${label} must be an object`);
|
|
385
|
+
return value;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function exactKeys(
|
|
389
|
+
value: Readonly<Record<string, unknown>>,
|
|
390
|
+
expected: readonly string[],
|
|
391
|
+
label: string,
|
|
392
|
+
): void {
|
|
393
|
+
const actual = Object.keys(value).sort();
|
|
394
|
+
const wanted = [...expected].sort();
|
|
395
|
+
if (
|
|
396
|
+
actual.length !== wanted.length
|
|
397
|
+
|| actual.some((key, index) => key !== wanted[index])
|
|
398
|
+
) {
|
|
399
|
+
throw new Error(`${label} must contain exactly: ${wanted.join(", ")}`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function boundedString(
|
|
404
|
+
value: unknown,
|
|
405
|
+
label: string,
|
|
406
|
+
maximumBytes: number,
|
|
407
|
+
options: {
|
|
408
|
+
readonly allowEmpty?: boolean;
|
|
409
|
+
readonly allowNewlines?: boolean;
|
|
410
|
+
} = {},
|
|
411
|
+
): string {
|
|
412
|
+
if (
|
|
413
|
+
typeof value !== "string"
|
|
414
|
+
|| (!options.allowEmpty && value.length < 1)
|
|
415
|
+
|| Buffer.byteLength(value, "utf8") > maximumBytes
|
|
416
|
+
|| value.includes("\0")
|
|
417
|
+
|| (!options.allowNewlines && /[\r\n]/u.test(value))
|
|
418
|
+
) {
|
|
419
|
+
throw new Error(`${label} must be bounded UTF-8 text`);
|
|
420
|
+
}
|
|
421
|
+
return value;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function boundedInteger(
|
|
425
|
+
value: unknown,
|
|
426
|
+
label: string,
|
|
427
|
+
minimum: number,
|
|
428
|
+
maximum: number,
|
|
429
|
+
): number {
|
|
430
|
+
if (
|
|
431
|
+
!Number.isSafeInteger(value)
|
|
432
|
+
|| (value as number) < minimum
|
|
433
|
+
|| (value as number) > maximum
|
|
434
|
+
) {
|
|
435
|
+
throw new Error(`${label} must be an integer from ${minimum} to ${maximum}`);
|
|
436
|
+
}
|
|
437
|
+
return value as number;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function boundedToken(value: unknown, label: string): string {
|
|
441
|
+
const token = boundedString(value, label, 128);
|
|
442
|
+
if (!tokenPattern.test(token)) throw new Error(`${label} is malformed`);
|
|
443
|
+
return token;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function sha256(value: unknown, label: string): string {
|
|
447
|
+
const digest = boundedString(value, label, 64);
|
|
448
|
+
if (!sha256Pattern.test(digest)) {
|
|
449
|
+
throw new Error(`${label} must be a lowercase SHA-256 digest`);
|
|
450
|
+
}
|
|
451
|
+
return digest;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function isCanonicalBase64(value: string): boolean {
|
|
455
|
+
if (!base64Pattern.test(value)) return false;
|
|
456
|
+
return Buffer.from(value, "base64").toString("base64") === value;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function normalizeJsonValue(
|
|
460
|
+
value: unknown,
|
|
461
|
+
path = "JSON value",
|
|
462
|
+
depth = 0,
|
|
463
|
+
): PortablePluginJsonValue {
|
|
464
|
+
if (depth > MAX_JSON_DEPTH) {
|
|
465
|
+
throw new Error(`${path} exceeds maximum JSON depth ${MAX_JSON_DEPTH}`);
|
|
466
|
+
}
|
|
467
|
+
if (value === null || typeof value === "boolean") return value;
|
|
468
|
+
if (typeof value === "number") {
|
|
469
|
+
if (!Number.isFinite(value)) throw new Error(`${path} must be a finite JSON number`);
|
|
470
|
+
return Object.is(value, -0) ? 0 : value;
|
|
471
|
+
}
|
|
472
|
+
if (typeof value === "string") {
|
|
473
|
+
return boundedString(value, path, MAX_JSON_STRING_BYTES, {
|
|
474
|
+
allowEmpty: true,
|
|
475
|
+
allowNewlines: true,
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
if (Array.isArray(value)) {
|
|
479
|
+
if (value.length > MAX_JSON_ARRAY_ITEMS) {
|
|
480
|
+
throw new Error(`${path} exceeds ${MAX_JSON_ARRAY_ITEMS} array items`);
|
|
481
|
+
}
|
|
482
|
+
return Object.freeze(
|
|
483
|
+
value.map((item, index) =>
|
|
484
|
+
normalizeJsonValue(item, `${path}[${index}]`, depth + 1),
|
|
485
|
+
),
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
if (!isRecord(value)) throw new Error(`${path} must be JSON-compatible`);
|
|
489
|
+
const keys = Object.keys(value).sort();
|
|
490
|
+
if (keys.length > MAX_JSON_OBJECT_KEYS) {
|
|
491
|
+
throw new Error(`${path} exceeds ${MAX_JSON_OBJECT_KEYS} object keys`);
|
|
492
|
+
}
|
|
493
|
+
const normalized = Object.create(null) as Record<string, PortablePluginJsonValue>;
|
|
494
|
+
for (const key of keys) {
|
|
495
|
+
if (key.includes("\0") || Buffer.byteLength(key, "utf8") > 512) {
|
|
496
|
+
throw new Error(`${path} contains an invalid object key`);
|
|
497
|
+
}
|
|
498
|
+
normalized[key] = normalizeJsonValue(value[key], `${path}.${key}`, depth + 1);
|
|
499
|
+
}
|
|
500
|
+
return Object.freeze(normalized);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function normalizeJsonObject(
|
|
504
|
+
value: unknown,
|
|
505
|
+
label: string,
|
|
506
|
+
): PortablePluginJsonObject {
|
|
507
|
+
const normalized = normalizeJsonValue(value, label);
|
|
508
|
+
if (!isRecord(normalized)) throw new Error(`${label} must be a JSON object`);
|
|
509
|
+
return normalized;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Parse a foreign value into the protocol's bounded, detached JSON model.
|
|
514
|
+
* Lifecycle fixtures and host messages deliberately share this one boundary.
|
|
515
|
+
*/
|
|
516
|
+
export function normalizePortablePluginJsonValue(
|
|
517
|
+
value: unknown,
|
|
518
|
+
label = "portable plugin JSON value",
|
|
519
|
+
): PortablePluginJsonValue {
|
|
520
|
+
return normalizeJsonValue(value, label);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
export function normalizePortablePluginJsonObject(
|
|
524
|
+
value: unknown,
|
|
525
|
+
label = "portable plugin JSON object",
|
|
526
|
+
): PortablePluginJsonObject {
|
|
527
|
+
return normalizeJsonObject(value, label);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function exactHttpsUrl(value: unknown, label: string): string {
|
|
531
|
+
const text = boundedString(value, label, 8_192);
|
|
532
|
+
let url: URL;
|
|
533
|
+
try {
|
|
534
|
+
url = new URL(text);
|
|
535
|
+
} catch {
|
|
536
|
+
throw new Error(`${label} must be an HTTPS URL`);
|
|
537
|
+
}
|
|
538
|
+
const hostname = url.hostname.toLowerCase();
|
|
539
|
+
if (
|
|
540
|
+
url.protocol !== "https:"
|
|
541
|
+
|| url.username !== ""
|
|
542
|
+
|| url.password !== ""
|
|
543
|
+
|| url.hash !== ""
|
|
544
|
+
|| hostname.endsWith(".")
|
|
545
|
+
|| hostname === "localhost"
|
|
546
|
+
|| hostname.endsWith(".localhost")
|
|
547
|
+
|| hostname.endsWith(".local")
|
|
548
|
+
|| hostname.endsWith(".internal")
|
|
549
|
+
|| !hostname.includes(".")
|
|
550
|
+
|| isIP(hostname) !== 0
|
|
551
|
+
) {
|
|
552
|
+
throw new Error(`${label} must be a public credential-free HTTPS URL`);
|
|
553
|
+
}
|
|
554
|
+
return url.toString();
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function exactHttpsOrigin(value: unknown, label: string): `https://${string}` {
|
|
558
|
+
const text = boundedString(value, label, 2_048);
|
|
559
|
+
let url: URL;
|
|
560
|
+
try {
|
|
561
|
+
url = new URL(text);
|
|
562
|
+
} catch {
|
|
563
|
+
throw new Error(`${label} must be an HTTPS origin`);
|
|
564
|
+
}
|
|
565
|
+
const hostname = url.hostname.toLowerCase();
|
|
566
|
+
if (
|
|
567
|
+
url.protocol !== "https:"
|
|
568
|
+
|| url.username !== ""
|
|
569
|
+
|| url.password !== ""
|
|
570
|
+
|| url.pathname !== "/"
|
|
571
|
+
|| url.search !== ""
|
|
572
|
+
|| url.hash !== ""
|
|
573
|
+
|| url.origin !== text
|
|
574
|
+
|| hostname.endsWith(".")
|
|
575
|
+
|| hostname === "localhost"
|
|
576
|
+
|| hostname.endsWith(".localhost")
|
|
577
|
+
|| hostname.endsWith(".local")
|
|
578
|
+
|| hostname.endsWith(".internal")
|
|
579
|
+
|| !hostname.includes(".")
|
|
580
|
+
|| isIP(hostname) !== 0
|
|
581
|
+
) {
|
|
582
|
+
throw new Error(`${label} must be an exact public HTTPS origin`);
|
|
583
|
+
}
|
|
584
|
+
return text as `https://${string}`;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function parsePluginIdentity(value: unknown): PortablePluginIdentity {
|
|
588
|
+
const identity = record(value, "plugin identity");
|
|
589
|
+
exactKeys(identity, ["id", "version", "bundleSha256"], "plugin identity");
|
|
590
|
+
const id = boundedString(identity.id, "plugin identity id", 63);
|
|
591
|
+
if (!pluginIdPattern.test(id)) throw new Error("plugin identity id is malformed");
|
|
592
|
+
const version = boundedString(identity.version, "plugin identity version", 128);
|
|
593
|
+
if (!isPortableProviderPluginVersion(version)) {
|
|
594
|
+
throw new Error("plugin identity version must be semantic version text");
|
|
595
|
+
}
|
|
596
|
+
return Object.freeze({
|
|
597
|
+
id,
|
|
598
|
+
version,
|
|
599
|
+
bundleSha256: sha256(identity.bundleSha256, "plugin identity bundleSha256"),
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function parseCapabilities(value: unknown): PortableProviderPluginCapabilitiesV1 {
|
|
604
|
+
const capabilities = record(value, "granted capabilities");
|
|
605
|
+
exactKeys(
|
|
606
|
+
capabilities,
|
|
607
|
+
["networkOrigins", "planFiles", "state", "sessionMaterial"],
|
|
608
|
+
"granted capabilities",
|
|
609
|
+
);
|
|
610
|
+
if (
|
|
611
|
+
capabilities.planFiles !== "none"
|
|
612
|
+
&& capabilities.planFiles !== "read"
|
|
613
|
+
) {
|
|
614
|
+
throw new Error("granted planFiles capability is invalid");
|
|
615
|
+
}
|
|
616
|
+
if (
|
|
617
|
+
capabilities.state !== "none"
|
|
618
|
+
&& capabilities.state !== "namespaced"
|
|
619
|
+
) {
|
|
620
|
+
throw new Error("granted state capability is invalid");
|
|
621
|
+
}
|
|
622
|
+
if (
|
|
623
|
+
!Array.isArray(capabilities.networkOrigins)
|
|
624
|
+
|| capabilities.networkOrigins.length > 64
|
|
625
|
+
) {
|
|
626
|
+
throw new Error("granted network origins must be a bounded array");
|
|
627
|
+
}
|
|
628
|
+
const origins = capabilities.networkOrigins.map((origin, index) => {
|
|
629
|
+
return exactHttpsOrigin(origin, `granted network origin ${index}`);
|
|
630
|
+
});
|
|
631
|
+
if (new Set(origins).size !== origins.length) {
|
|
632
|
+
throw new Error("granted network capabilities repeat an origin");
|
|
633
|
+
}
|
|
634
|
+
const sortedOrigins = [...origins].sort();
|
|
635
|
+
if (sortedOrigins.some((origin, index) => origin !== origins[index])) {
|
|
636
|
+
throw new Error("granted network origins must be sorted");
|
|
637
|
+
}
|
|
638
|
+
if (
|
|
639
|
+
!Array.isArray(capabilities.sessionMaterial)
|
|
640
|
+
|| capabilities.sessionMaterial.length > 64
|
|
641
|
+
) {
|
|
642
|
+
throw new Error("granted session material must be a bounded array");
|
|
643
|
+
}
|
|
644
|
+
const material = capabilities.sessionMaterial.map((name) => {
|
|
645
|
+
const parsed = boundedString(name, "granted session material name", 128);
|
|
646
|
+
if (!materialNamePattern.test(parsed)) {
|
|
647
|
+
throw new Error("granted session material name is malformed");
|
|
648
|
+
}
|
|
649
|
+
if (
|
|
650
|
+
!portableProviderPluginSessionMaterialNames.includes(
|
|
651
|
+
parsed as PortableProviderPluginSessionMaterialName,
|
|
652
|
+
)
|
|
653
|
+
) {
|
|
654
|
+
throw new Error(
|
|
655
|
+
`granted session material ${parsed} is unsupported by host API v1`,
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
return parsed as PortableProviderPluginSessionMaterialName;
|
|
659
|
+
});
|
|
660
|
+
if (new Set(material).size !== material.length) {
|
|
661
|
+
throw new Error("granted session material repeats a name");
|
|
662
|
+
}
|
|
663
|
+
const sortedMaterial = [...material].sort();
|
|
664
|
+
if (sortedMaterial.some((name, index) => name !== material[index])) {
|
|
665
|
+
throw new Error("granted session material names must be sorted");
|
|
666
|
+
}
|
|
667
|
+
return Object.freeze({
|
|
668
|
+
networkOrigins: Object.freeze(sortedOrigins),
|
|
669
|
+
planFiles: capabilities.planFiles,
|
|
670
|
+
state: capabilities.state,
|
|
671
|
+
sessionMaterial: Object.freeze(sortedMaterial),
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function parseRoute(value: unknown): PortablePluginRoute {
|
|
676
|
+
const route = record(value, "plugin route");
|
|
677
|
+
exactKeys(
|
|
678
|
+
route,
|
|
679
|
+
["transport", "surfaceId", "operation", "contractVersion"],
|
|
680
|
+
"plugin route",
|
|
681
|
+
);
|
|
682
|
+
if (
|
|
683
|
+
route.transport !== "provider-api"
|
|
684
|
+
&& route.transport !== "web-session-api"
|
|
685
|
+
&& route.transport !== "linked-device"
|
|
686
|
+
) {
|
|
687
|
+
throw new Error("plugin route transport is unsupported");
|
|
688
|
+
}
|
|
689
|
+
const surfaceId = boundedString(route.surfaceId, "plugin route surfaceId", 63);
|
|
690
|
+
if (!pluginIdPattern.test(surfaceId)) throw new Error("plugin route surfaceId is malformed");
|
|
691
|
+
const operation = boundedString(route.operation, "plugin route operation", 163);
|
|
692
|
+
if (!operationPattern.test(operation)) throw new Error("plugin route operation is malformed");
|
|
693
|
+
return Object.freeze({
|
|
694
|
+
transport: route.transport,
|
|
695
|
+
surfaceId,
|
|
696
|
+
operation,
|
|
697
|
+
contractVersion: boundedInteger(
|
|
698
|
+
route.contractVersion,
|
|
699
|
+
"plugin route contractVersion",
|
|
700
|
+
1,
|
|
701
|
+
1_000_000,
|
|
702
|
+
),
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function parseAuth(value: unknown): PortablePluginInvocationAuth {
|
|
707
|
+
const auth = record(value, "plugin invocation auth");
|
|
708
|
+
const keys = auth.subject === undefined
|
|
709
|
+
? ["kind", "handle"]
|
|
710
|
+
: ["kind", "handle", "subject"];
|
|
711
|
+
exactKeys(auth, keys, "plugin invocation auth");
|
|
712
|
+
if (
|
|
713
|
+
auth.kind !== "cookie-source"
|
|
714
|
+
&& auth.kind !== "cookies-file"
|
|
715
|
+
&& auth.kind !== "browser-profile"
|
|
716
|
+
&& auth.kind !== "oauth-token-file"
|
|
717
|
+
&& auth.kind !== "linked-device-store"
|
|
718
|
+
) {
|
|
719
|
+
throw new Error("plugin invocation auth kind is unsupported");
|
|
720
|
+
}
|
|
721
|
+
const handle = boundedToken(auth.handle, "plugin invocation auth handle");
|
|
722
|
+
if (auth.subject === undefined) return Object.freeze({ kind: auth.kind, handle });
|
|
723
|
+
return Object.freeze({
|
|
724
|
+
kind: auth.kind,
|
|
725
|
+
handle,
|
|
726
|
+
subject: boundedString(auth.subject, "plugin invocation auth subject", 1_024),
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
function parseInvocationFile(value: unknown): PortablePluginInvocationFile {
|
|
731
|
+
const file = record(value, "plugin invocation file");
|
|
732
|
+
exactKeys(
|
|
733
|
+
file,
|
|
734
|
+
["input", "handle", "bytes", "mediaType", "sha256"],
|
|
735
|
+
"plugin invocation file",
|
|
736
|
+
);
|
|
737
|
+
const input = boundedString(file.input, "plugin invocation file input", 128);
|
|
738
|
+
if (!/^[a-z][a-z0-9_]{0,63}$/u.test(input)) {
|
|
739
|
+
throw new Error("plugin invocation file input is malformed");
|
|
740
|
+
}
|
|
741
|
+
const mediaType = boundedString(
|
|
742
|
+
file.mediaType,
|
|
743
|
+
"plugin invocation file mediaType",
|
|
744
|
+
256,
|
|
745
|
+
);
|
|
746
|
+
if (!mediaTypePattern.test(mediaType)) {
|
|
747
|
+
throw new Error("plugin invocation file mediaType is malformed");
|
|
748
|
+
}
|
|
749
|
+
return Object.freeze({
|
|
750
|
+
input,
|
|
751
|
+
handle: boundedToken(file.handle, "plugin invocation file handle"),
|
|
752
|
+
bytes: boundedInteger(
|
|
753
|
+
file.bytes,
|
|
754
|
+
"plugin invocation file bytes",
|
|
755
|
+
1,
|
|
756
|
+
2_147_483_647,
|
|
757
|
+
),
|
|
758
|
+
mediaType,
|
|
759
|
+
sha256: sha256(file.sha256, "plugin invocation file sha256"),
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
function parseHeader(
|
|
764
|
+
value: unknown,
|
|
765
|
+
label: string,
|
|
766
|
+
allowCredentialHeaders: boolean,
|
|
767
|
+
): { readonly name: string; readonly value: string } {
|
|
768
|
+
const header = record(value, label);
|
|
769
|
+
exactKeys(header, ["name", "value"], label);
|
|
770
|
+
const name = boundedString(header.name, `${label} name`, 128);
|
|
771
|
+
if (!headerNamePattern.test(name)) throw new Error(`${label} name is malformed`);
|
|
772
|
+
if (!allowCredentialHeaders && forbiddenRawCredentialHeaders.has(name.toLowerCase())) {
|
|
773
|
+
throw new Error(`${label} cannot carry raw credential material`);
|
|
774
|
+
}
|
|
775
|
+
return Object.freeze({
|
|
776
|
+
name: name.toLowerCase(),
|
|
777
|
+
value: boundedString(header.value, `${label} value`, 8_192),
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
function parseCredentialSink(value: unknown): PortablePluginCredentialSink {
|
|
782
|
+
const sink = record(value, "plugin credential sink");
|
|
783
|
+
if (sink.kind === "cookie-jar") {
|
|
784
|
+
exactKeys(sink, ["kind"], "plugin cookie credential sink");
|
|
785
|
+
return Object.freeze({ kind: "cookie-jar" });
|
|
786
|
+
}
|
|
787
|
+
if (sink.kind !== "header") {
|
|
788
|
+
throw new Error(
|
|
789
|
+
"plugin credential sink is unsupported by host API v1",
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
exactKeys(sink, ["kind", "name"], "plugin named credential sink");
|
|
793
|
+
const name = boundedString(sink.name, "plugin credential sink name", 128);
|
|
794
|
+
if (!headerNamePattern.test(name)) {
|
|
795
|
+
throw new Error("plugin credential sink name is malformed");
|
|
796
|
+
}
|
|
797
|
+
if (name.toLowerCase() !== "authorization") {
|
|
798
|
+
throw new Error(
|
|
799
|
+
"host API v1 permits only the Authorization credential header sink",
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
return Object.freeze({
|
|
803
|
+
kind: "header",
|
|
804
|
+
name: "authorization",
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function parseCredentialBinding(
|
|
809
|
+
value: unknown,
|
|
810
|
+
): PortablePluginCredentialBinding {
|
|
811
|
+
const binding = record(value, "plugin credential binding");
|
|
812
|
+
exactKeys(binding, ["handle", "sink"], "plugin credential binding");
|
|
813
|
+
return Object.freeze({
|
|
814
|
+
handle: boundedToken(binding.handle, "plugin credential handle"),
|
|
815
|
+
sink: parseCredentialSink(binding.sink),
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
function parseHttpBody(value: unknown): PortablePluginHttpBody {
|
|
820
|
+
const body = record(value, "plugin HTTP body");
|
|
821
|
+
if (body.kind === "none") {
|
|
822
|
+
exactKeys(body, ["kind"], "plugin empty HTTP body");
|
|
823
|
+
return Object.freeze({ kind: "none" });
|
|
824
|
+
}
|
|
825
|
+
if (body.kind !== "utf8" && body.kind !== "base64") {
|
|
826
|
+
throw new Error("plugin HTTP body kind is unsupported");
|
|
827
|
+
}
|
|
828
|
+
const payloadKey = body.kind === "utf8" ? "text" : "data";
|
|
829
|
+
exactKeys(body, ["kind", "mediaType", payloadKey], "plugin HTTP body");
|
|
830
|
+
const mediaType = boundedString(body.mediaType, "plugin HTTP body mediaType", 256);
|
|
831
|
+
if (!mediaTypePattern.test(mediaType)) {
|
|
832
|
+
throw new Error("plugin HTTP body mediaType is malformed");
|
|
833
|
+
}
|
|
834
|
+
const payload = boundedString(
|
|
835
|
+
body[payloadKey],
|
|
836
|
+
`plugin HTTP body ${payloadKey}`,
|
|
837
|
+
MAX_HTTP_BODY_BYTES,
|
|
838
|
+
{ allowEmpty: true, allowNewlines: true },
|
|
839
|
+
);
|
|
840
|
+
if (body.kind === "base64") {
|
|
841
|
+
if (!isCanonicalBase64(payload) || Buffer.byteLength(payload, "base64") > MAX_HTTP_BODY_BYTES) {
|
|
842
|
+
throw new Error("plugin HTTP body data must be bounded canonical base64");
|
|
843
|
+
}
|
|
844
|
+
return Object.freeze({ kind: "base64", mediaType, data: payload });
|
|
845
|
+
}
|
|
846
|
+
return Object.freeze({ kind: "utf8", mediaType, text: payload });
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
function parseHttpMethod(
|
|
850
|
+
value: unknown,
|
|
851
|
+
): Extract<
|
|
852
|
+
PortablePluginCapabilityRequest,
|
|
853
|
+
{ readonly kind: "http.request" }
|
|
854
|
+
>["method"] {
|
|
855
|
+
switch (value) {
|
|
856
|
+
case "DELETE":
|
|
857
|
+
case "GET":
|
|
858
|
+
case "HEAD":
|
|
859
|
+
case "OPTIONS":
|
|
860
|
+
case "PATCH":
|
|
861
|
+
case "POST":
|
|
862
|
+
case "PUT":
|
|
863
|
+
return value;
|
|
864
|
+
default:
|
|
865
|
+
throw new Error("plugin HTTP method is unsupported");
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
function parseCapabilityRequest(
|
|
870
|
+
value: unknown,
|
|
871
|
+
): PortablePluginCapabilityRequest {
|
|
872
|
+
const request = record(value, "plugin capability request");
|
|
873
|
+
if (request.kind === "dispatch.begin") {
|
|
874
|
+
exactKeys(request, ["kind", "dispatchId"], "dispatch begin request");
|
|
875
|
+
return Object.freeze({
|
|
876
|
+
kind: "dispatch.begin",
|
|
877
|
+
dispatchId: boundedToken(request.dispatchId, "dispatch ID"),
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
if (request.kind === "dispatch.verify") {
|
|
881
|
+
exactKeys(
|
|
882
|
+
request,
|
|
883
|
+
["kind", "dispatchHandle", "proof"],
|
|
884
|
+
"dispatch verify request",
|
|
885
|
+
);
|
|
886
|
+
return Object.freeze({
|
|
887
|
+
kind: "dispatch.verify",
|
|
888
|
+
dispatchHandle: boundedToken(request.dispatchHandle, "dispatch handle"),
|
|
889
|
+
proof: normalizeJsonObject(request.proof, "dispatch proof"),
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
if (request.kind === "http.request") {
|
|
893
|
+
const keys = request.dispatchHandle === undefined
|
|
894
|
+
? [
|
|
895
|
+
"kind",
|
|
896
|
+
"method",
|
|
897
|
+
"url",
|
|
898
|
+
"headers",
|
|
899
|
+
"credentials",
|
|
900
|
+
"body",
|
|
901
|
+
"redirect",
|
|
902
|
+
"timeoutMs",
|
|
903
|
+
"maxOutputBytes",
|
|
904
|
+
]
|
|
905
|
+
: [
|
|
906
|
+
"kind",
|
|
907
|
+
"method",
|
|
908
|
+
"url",
|
|
909
|
+
"headers",
|
|
910
|
+
"credentials",
|
|
911
|
+
"body",
|
|
912
|
+
"redirect",
|
|
913
|
+
"timeoutMs",
|
|
914
|
+
"maxOutputBytes",
|
|
915
|
+
"dispatchHandle",
|
|
916
|
+
];
|
|
917
|
+
exactKeys(request, keys, "HTTP capability request");
|
|
918
|
+
const method = parseHttpMethod(request.method);
|
|
919
|
+
if (!Array.isArray(request.headers) || request.headers.length > MAX_HEADERS) {
|
|
920
|
+
throw new Error("plugin HTTP headers must be a bounded array");
|
|
921
|
+
}
|
|
922
|
+
const headers = request.headers.map((header, index) =>
|
|
923
|
+
parseHeader(header, `plugin HTTP header ${index}`, false),
|
|
924
|
+
);
|
|
925
|
+
const headerNames = headers.map((header) => header.name);
|
|
926
|
+
if (new Set(headerNames).size !== headerNames.length) {
|
|
927
|
+
throw new Error("plugin HTTP request repeats a header");
|
|
928
|
+
}
|
|
929
|
+
if (
|
|
930
|
+
!Array.isArray(request.credentials)
|
|
931
|
+
|| request.credentials.length > MAX_CREDENTIAL_BINDINGS
|
|
932
|
+
) {
|
|
933
|
+
throw new Error("plugin HTTP credentials must be a bounded array");
|
|
934
|
+
}
|
|
935
|
+
const credentials = request.credentials.map(parseCredentialBinding);
|
|
936
|
+
const credentialSinks = credentials.map(
|
|
937
|
+
(binding) =>
|
|
938
|
+
binding.sink.kind === "cookie-jar"
|
|
939
|
+
? "cookie-jar"
|
|
940
|
+
: `${binding.sink.kind}:${binding.sink.name}`,
|
|
941
|
+
);
|
|
942
|
+
if (new Set(credentialSinks).size !== credentialSinks.length) {
|
|
943
|
+
throw new Error("plugin HTTP request repeats a credential sink");
|
|
944
|
+
}
|
|
945
|
+
if (request.redirect !== "error") {
|
|
946
|
+
throw new Error("plugin HTTP redirects must fail closed");
|
|
947
|
+
}
|
|
948
|
+
const common = {
|
|
949
|
+
kind: "http.request" as const,
|
|
950
|
+
method,
|
|
951
|
+
url: exactHttpsUrl(request.url, "plugin HTTP URL"),
|
|
952
|
+
headers: Object.freeze(headers),
|
|
953
|
+
credentials: Object.freeze(credentials),
|
|
954
|
+
body: parseHttpBody(request.body),
|
|
955
|
+
redirect: "error" as const,
|
|
956
|
+
timeoutMs: boundedInteger(
|
|
957
|
+
request.timeoutMs,
|
|
958
|
+
"plugin HTTP timeoutMs",
|
|
959
|
+
1,
|
|
960
|
+
120_000,
|
|
961
|
+
),
|
|
962
|
+
maxOutputBytes: boundedInteger(
|
|
963
|
+
request.maxOutputBytes,
|
|
964
|
+
"plugin HTTP maxOutputBytes",
|
|
965
|
+
1,
|
|
966
|
+
MAX_HTTP_OUTPUT_BYTES,
|
|
967
|
+
),
|
|
968
|
+
};
|
|
969
|
+
if (request.dispatchHandle === undefined) return Object.freeze(common);
|
|
970
|
+
return Object.freeze({
|
|
971
|
+
...common,
|
|
972
|
+
dispatchHandle: boundedToken(request.dispatchHandle, "dispatch handle"),
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
if (request.kind === "file.read") {
|
|
976
|
+
exactKeys(request, ["kind", "handle", "offset", "length"], "file read request");
|
|
977
|
+
return Object.freeze({
|
|
978
|
+
kind: "file.read",
|
|
979
|
+
handle: boundedToken(request.handle, "file handle"),
|
|
980
|
+
offset: boundedInteger(request.offset, "file read offset", 0, 2_147_483_647),
|
|
981
|
+
length: boundedInteger(request.length, "file read length", 1, 256 * 1024),
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
if (
|
|
985
|
+
request.kind === "state.read"
|
|
986
|
+
|| request.kind === "state.write"
|
|
987
|
+
|| request.kind === "state.delete"
|
|
988
|
+
) {
|
|
989
|
+
const hasExpectedVersion = Object.hasOwn(request, "expectedVersion");
|
|
990
|
+
const hasIncludeVersion = Object.hasOwn(request, "includeVersion");
|
|
991
|
+
exactKeys(
|
|
992
|
+
request,
|
|
993
|
+
request.kind === "state.write"
|
|
994
|
+
? hasExpectedVersion
|
|
995
|
+
? ["kind", "key", "value", "expectedVersion"]
|
|
996
|
+
: ["kind", "key", "value"]
|
|
997
|
+
: request.kind === "state.delete"
|
|
998
|
+
? hasExpectedVersion
|
|
999
|
+
? ["kind", "key", "expectedVersion"]
|
|
1000
|
+
: ["kind", "key"]
|
|
1001
|
+
: hasIncludeVersion
|
|
1002
|
+
? ["kind", "key", "includeVersion"]
|
|
1003
|
+
: ["kind", "key"],
|
|
1004
|
+
`${request.kind} request`,
|
|
1005
|
+
);
|
|
1006
|
+
const key = boundedString(request.key, "plugin state key", 256);
|
|
1007
|
+
if (
|
|
1008
|
+
!stateKeyPattern.test(key)
|
|
1009
|
+
|| key.startsWith("/")
|
|
1010
|
+
|| key.endsWith("/")
|
|
1011
|
+
|| key.includes("//")
|
|
1012
|
+
|| key.split("/").some((segment) => segment === "." || segment === "..")
|
|
1013
|
+
) {
|
|
1014
|
+
throw new Error("plugin state key is malformed");
|
|
1015
|
+
}
|
|
1016
|
+
if (request.kind === "state.read") {
|
|
1017
|
+
if (!hasIncludeVersion) return Object.freeze({ kind: "state.read", key });
|
|
1018
|
+
if (request.includeVersion !== true) {
|
|
1019
|
+
throw new Error("plugin state read includeVersion must be true");
|
|
1020
|
+
}
|
|
1021
|
+
return Object.freeze({ kind: "state.read", key, includeVersion: true });
|
|
1022
|
+
}
|
|
1023
|
+
if (request.kind === "state.delete") {
|
|
1024
|
+
if (!hasExpectedVersion) return Object.freeze({ kind: "state.delete", key });
|
|
1025
|
+
return Object.freeze({
|
|
1026
|
+
kind: "state.delete",
|
|
1027
|
+
key,
|
|
1028
|
+
expectedVersion: sha256(
|
|
1029
|
+
request.expectedVersion,
|
|
1030
|
+
"plugin state expectedVersion",
|
|
1031
|
+
),
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
const write = {
|
|
1035
|
+
kind: "state.write",
|
|
1036
|
+
key,
|
|
1037
|
+
value: normalizeJsonValue(request.value, "plugin state value"),
|
|
1038
|
+
} as const;
|
|
1039
|
+
if (!hasExpectedVersion) return Object.freeze(write);
|
|
1040
|
+
if (request.expectedVersion === null) {
|
|
1041
|
+
return Object.freeze({ ...write, expectedVersion: null });
|
|
1042
|
+
}
|
|
1043
|
+
return Object.freeze({
|
|
1044
|
+
...write,
|
|
1045
|
+
expectedVersion: sha256(
|
|
1046
|
+
request.expectedVersion,
|
|
1047
|
+
"plugin state expectedVersion",
|
|
1048
|
+
),
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
if (request.kind === "session.acquire") {
|
|
1052
|
+
exactKeys(request, ["kind", "name"], "session acquire request");
|
|
1053
|
+
const name = boundedString(request.name, "session material name", 128);
|
|
1054
|
+
if (!materialNamePattern.test(name)) {
|
|
1055
|
+
throw new Error("session material name is malformed");
|
|
1056
|
+
}
|
|
1057
|
+
if (
|
|
1058
|
+
!portableProviderPluginSessionMaterialNames.includes(
|
|
1059
|
+
name as PortableProviderPluginSessionMaterialName,
|
|
1060
|
+
)
|
|
1061
|
+
) {
|
|
1062
|
+
throw new Error(
|
|
1063
|
+
`session material ${name} is unsupported by host API v1`,
|
|
1064
|
+
);
|
|
1065
|
+
}
|
|
1066
|
+
return Object.freeze({
|
|
1067
|
+
kind: "session.acquire",
|
|
1068
|
+
name: name as PortableProviderPluginSessionMaterialName,
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
if (request.kind === "log.write") {
|
|
1072
|
+
exactKeys(request, ["kind", "level", "message"], "log write request");
|
|
1073
|
+
if (
|
|
1074
|
+
request.level !== "debug"
|
|
1075
|
+
&& request.level !== "info"
|
|
1076
|
+
&& request.level !== "warn"
|
|
1077
|
+
) {
|
|
1078
|
+
throw new Error("plugin log level is unsupported");
|
|
1079
|
+
}
|
|
1080
|
+
return Object.freeze({
|
|
1081
|
+
kind: "log.write",
|
|
1082
|
+
level: request.level,
|
|
1083
|
+
message: boundedString(request.message, "plugin log message", 16_384, {
|
|
1084
|
+
allowNewlines: true,
|
|
1085
|
+
}),
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
throw new Error("plugin capability request kind is unsupported");
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
function parseHttpResponseBody(
|
|
1092
|
+
value: unknown,
|
|
1093
|
+
): Extract<PortablePluginCapabilityResult, { readonly kind: "http.request" }>["body"] {
|
|
1094
|
+
const body = record(value, "plugin HTTP response body");
|
|
1095
|
+
if (body.kind === "utf8") {
|
|
1096
|
+
exactKeys(body, ["kind", "text"], "plugin UTF-8 HTTP response body");
|
|
1097
|
+
return Object.freeze({
|
|
1098
|
+
kind: "utf8",
|
|
1099
|
+
text: boundedString(body.text, "plugin HTTP response text", MAX_HTTP_OUTPUT_BYTES, {
|
|
1100
|
+
allowEmpty: true,
|
|
1101
|
+
allowNewlines: true,
|
|
1102
|
+
}),
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
if (body.kind !== "base64") {
|
|
1106
|
+
throw new Error("plugin HTTP response body kind is unsupported");
|
|
1107
|
+
}
|
|
1108
|
+
exactKeys(body, ["kind", "data"], "plugin base64 HTTP response body");
|
|
1109
|
+
const data = boundedString(
|
|
1110
|
+
body.data,
|
|
1111
|
+
"plugin HTTP response data",
|
|
1112
|
+
MAX_HTTP_BASE64_OUTPUT_TEXT_BYTES,
|
|
1113
|
+
{ allowEmpty: true },
|
|
1114
|
+
);
|
|
1115
|
+
if (
|
|
1116
|
+
!isCanonicalBase64(data)
|
|
1117
|
+
|| Buffer.byteLength(data, "base64") > MAX_HTTP_OUTPUT_BYTES
|
|
1118
|
+
) {
|
|
1119
|
+
throw new Error(
|
|
1120
|
+
"plugin HTTP response data must be bounded canonical base64",
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1123
|
+
return Object.freeze({ kind: "base64", data });
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
function parseCapabilityResult(
|
|
1127
|
+
value: unknown,
|
|
1128
|
+
): PortablePluginCapabilityResult {
|
|
1129
|
+
const result = record(value, "plugin capability result");
|
|
1130
|
+
if (result.kind === "dispatch.begin") {
|
|
1131
|
+
exactKeys(result, ["kind", "dispatchHandle"], "dispatch begin result");
|
|
1132
|
+
return Object.freeze({
|
|
1133
|
+
kind: "dispatch.begin",
|
|
1134
|
+
dispatchHandle: boundedToken(result.dispatchHandle, "dispatch handle"),
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
if (result.kind === "dispatch.verify") {
|
|
1138
|
+
exactKeys(result, ["kind", "verified"], "dispatch verify result");
|
|
1139
|
+
if (result.verified !== true) throw new Error("dispatch verify result must be true");
|
|
1140
|
+
return Object.freeze({ kind: "dispatch.verify", verified: true });
|
|
1141
|
+
}
|
|
1142
|
+
if (result.kind === "http.request") {
|
|
1143
|
+
exactKeys(
|
|
1144
|
+
result,
|
|
1145
|
+
["kind", "status", "headers", "body", "finalUrl"],
|
|
1146
|
+
"HTTP capability result",
|
|
1147
|
+
);
|
|
1148
|
+
if (!Array.isArray(result.headers) || result.headers.length > MAX_HEADERS) {
|
|
1149
|
+
throw new Error("plugin HTTP result headers must be a bounded array");
|
|
1150
|
+
}
|
|
1151
|
+
const headers = result.headers.map((header, index) =>
|
|
1152
|
+
parseHeader(header, `plugin HTTP result header ${index}`, false),
|
|
1153
|
+
);
|
|
1154
|
+
const headerNames = headers.map((header) => header.name);
|
|
1155
|
+
if (new Set(headerNames).size !== headerNames.length) {
|
|
1156
|
+
throw new Error("plugin HTTP result repeats a header");
|
|
1157
|
+
}
|
|
1158
|
+
return Object.freeze({
|
|
1159
|
+
kind: "http.request",
|
|
1160
|
+
status: boundedInteger(result.status, "plugin HTTP status", 100, 599),
|
|
1161
|
+
headers: Object.freeze(headers),
|
|
1162
|
+
body: parseHttpResponseBody(result.body),
|
|
1163
|
+
finalUrl: exactHttpsUrl(result.finalUrl, "plugin HTTP finalUrl"),
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1166
|
+
if (result.kind === "file.read") {
|
|
1167
|
+
exactKeys(result, ["kind", "data", "eof"], "file read result");
|
|
1168
|
+
const data = boundedString(result.data, "file read result data", 350 * 1024, {
|
|
1169
|
+
allowEmpty: true,
|
|
1170
|
+
});
|
|
1171
|
+
if (!isCanonicalBase64(data)) {
|
|
1172
|
+
throw new Error("file read result data must be canonical base64");
|
|
1173
|
+
}
|
|
1174
|
+
if (typeof result.eof !== "boolean") throw new Error("file read result eof must be boolean");
|
|
1175
|
+
return Object.freeze({ kind: "file.read", data, eof: result.eof });
|
|
1176
|
+
}
|
|
1177
|
+
if (result.kind === "state.read") {
|
|
1178
|
+
if (result.found === false) {
|
|
1179
|
+
const hasVersion = Object.hasOwn(result, "version");
|
|
1180
|
+
exactKeys(
|
|
1181
|
+
result,
|
|
1182
|
+
hasVersion ? ["kind", "found", "version"] : ["kind", "found"],
|
|
1183
|
+
"missing state read result",
|
|
1184
|
+
);
|
|
1185
|
+
if (!hasVersion) return Object.freeze({ kind: "state.read", found: false });
|
|
1186
|
+
if (result.version !== null) {
|
|
1187
|
+
throw new Error("missing state read result version must be null");
|
|
1188
|
+
}
|
|
1189
|
+
return Object.freeze({ kind: "state.read", found: false, version: null });
|
|
1190
|
+
}
|
|
1191
|
+
const hasVersion = Object.hasOwn(result, "version");
|
|
1192
|
+
exactKeys(
|
|
1193
|
+
result,
|
|
1194
|
+
hasVersion
|
|
1195
|
+
? ["kind", "found", "value", "version"]
|
|
1196
|
+
: ["kind", "found", "value"],
|
|
1197
|
+
"state read result",
|
|
1198
|
+
);
|
|
1199
|
+
if (result.found !== true) throw new Error("state read result found must be boolean");
|
|
1200
|
+
const read = {
|
|
1201
|
+
kind: "state.read",
|
|
1202
|
+
found: true,
|
|
1203
|
+
value: normalizeJsonValue(result.value, "plugin state result value"),
|
|
1204
|
+
} as const;
|
|
1205
|
+
if (!hasVersion) return Object.freeze(read);
|
|
1206
|
+
return Object.freeze({
|
|
1207
|
+
...read,
|
|
1208
|
+
version: sha256(result.version, "plugin state result version"),
|
|
1209
|
+
});
|
|
1210
|
+
}
|
|
1211
|
+
if (result.kind === "state.write") {
|
|
1212
|
+
const hasVersion = Object.hasOwn(result, "version");
|
|
1213
|
+
exactKeys(
|
|
1214
|
+
result,
|
|
1215
|
+
hasVersion ? ["kind", "stored", "version"] : ["kind", "stored"],
|
|
1216
|
+
"state write result",
|
|
1217
|
+
);
|
|
1218
|
+
if (result.stored !== true) {
|
|
1219
|
+
throw new Error("state write result stored must be true");
|
|
1220
|
+
}
|
|
1221
|
+
if (!hasVersion) return Object.freeze({ kind: "state.write", stored: true });
|
|
1222
|
+
return Object.freeze({
|
|
1223
|
+
kind: "state.write",
|
|
1224
|
+
stored: true,
|
|
1225
|
+
version: sha256(result.version, "plugin state result version"),
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
if (result.kind === "state.delete") {
|
|
1229
|
+
exactKeys(result, ["kind", "removed"], "state delete result");
|
|
1230
|
+
if (typeof result.removed !== "boolean") {
|
|
1231
|
+
throw new Error("state delete result removed must be boolean");
|
|
1232
|
+
}
|
|
1233
|
+
return Object.freeze({ kind: "state.delete", removed: result.removed });
|
|
1234
|
+
}
|
|
1235
|
+
if (result.kind === "session.acquire") {
|
|
1236
|
+
exactKeys(result, ["kind", "materialHandle"], "session acquire result");
|
|
1237
|
+
return Object.freeze({
|
|
1238
|
+
kind: "session.acquire",
|
|
1239
|
+
materialHandle: boundedToken(result.materialHandle, "session material handle"),
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
if (result.kind === "log.write") {
|
|
1243
|
+
exactKeys(result, ["kind", "accepted"], "log write result");
|
|
1244
|
+
if (result.accepted !== true) throw new Error("log write result accepted must be true");
|
|
1245
|
+
return Object.freeze({ kind: "log.write", accepted: true });
|
|
1246
|
+
}
|
|
1247
|
+
throw new Error("plugin capability result kind is unsupported");
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
function parseError(value: unknown): { readonly code: string; readonly message: string } {
|
|
1251
|
+
const error = record(value, "plugin protocol error");
|
|
1252
|
+
exactKeys(error, ["code", "message"], "plugin protocol error");
|
|
1253
|
+
const code = boundedString(error.code, "plugin protocol error code", 64);
|
|
1254
|
+
if (!errorCodePattern.test(code)) throw new Error("plugin protocol error code is malformed");
|
|
1255
|
+
return Object.freeze({
|
|
1256
|
+
code,
|
|
1257
|
+
message: boundedString(error.message, "plugin protocol error message", 16_384, {
|
|
1258
|
+
allowNewlines: true,
|
|
1259
|
+
}),
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
function parseMessageOrThrow(value: unknown): PortableProviderPluginMessage {
|
|
1264
|
+
const message = record(value, "portable provider plugin message");
|
|
1265
|
+
if (message.protocolVersion !== PORTABLE_PROVIDER_PLUGIN_PROTOCOL_VERSION) {
|
|
1266
|
+
throw new Error(
|
|
1267
|
+
`portable provider plugin protocolVersion must be ${PORTABLE_PROVIDER_PLUGIN_PROTOCOL_VERSION}`,
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1270
|
+
if (message.kind === "host.hello") {
|
|
1271
|
+
exactKeys(
|
|
1272
|
+
message,
|
|
1273
|
+
["protocolVersion", "kind", "hostVersion", "hostApiVersion", "plugin", "granted"],
|
|
1274
|
+
"host hello message",
|
|
1275
|
+
);
|
|
1276
|
+
if (message.hostApiVersion !== PORTABLE_PROVIDER_PLUGIN_HOST_API_VERSION) {
|
|
1277
|
+
throw new Error(
|
|
1278
|
+
`portable provider plugin hostApiVersion must be ${PORTABLE_PROVIDER_PLUGIN_HOST_API_VERSION}`,
|
|
1279
|
+
);
|
|
1280
|
+
}
|
|
1281
|
+
return Object.freeze({
|
|
1282
|
+
protocolVersion: PORTABLE_PROVIDER_PLUGIN_PROTOCOL_VERSION,
|
|
1283
|
+
kind: "host.hello",
|
|
1284
|
+
hostVersion: boundedString(message.hostVersion, "host version", 128),
|
|
1285
|
+
hostApiVersion: PORTABLE_PROVIDER_PLUGIN_HOST_API_VERSION,
|
|
1286
|
+
plugin: parsePluginIdentity(message.plugin),
|
|
1287
|
+
granted: parseCapabilities(message.granted),
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
if (message.kind === "host.invoke") {
|
|
1291
|
+
exactKeys(
|
|
1292
|
+
message,
|
|
1293
|
+
["protocolVersion", "kind", "invocationId", "route", "input", "auth", "files", "timeoutMs"],
|
|
1294
|
+
"host invoke message",
|
|
1295
|
+
);
|
|
1296
|
+
if (!Array.isArray(message.files) || message.files.length > MAX_INVOCATION_FILES) {
|
|
1297
|
+
throw new Error("host invocation files must be a bounded array");
|
|
1298
|
+
}
|
|
1299
|
+
const files = message.files.map(parseInvocationFile);
|
|
1300
|
+
const handles = files.map((file) => file.handle);
|
|
1301
|
+
if (new Set(handles).size !== handles.length) {
|
|
1302
|
+
throw new Error("host invocation repeats a file handle");
|
|
1303
|
+
}
|
|
1304
|
+
return Object.freeze({
|
|
1305
|
+
protocolVersion: PORTABLE_PROVIDER_PLUGIN_PROTOCOL_VERSION,
|
|
1306
|
+
kind: "host.invoke",
|
|
1307
|
+
invocationId: boundedToken(message.invocationId, "invocation ID"),
|
|
1308
|
+
route: parseRoute(message.route),
|
|
1309
|
+
input: normalizeJsonObject(message.input, "plugin invocation input"),
|
|
1310
|
+
auth: parseAuth(message.auth),
|
|
1311
|
+
files: Object.freeze(files),
|
|
1312
|
+
timeoutMs: boundedInteger(
|
|
1313
|
+
message.timeoutMs,
|
|
1314
|
+
"plugin invocation timeoutMs",
|
|
1315
|
+
1,
|
|
1316
|
+
10 * 60_000,
|
|
1317
|
+
),
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1320
|
+
if (message.kind === "host.capability.result") {
|
|
1321
|
+
exactKeys(
|
|
1322
|
+
message,
|
|
1323
|
+
["protocolVersion", "kind", "invocationId", "requestId", "result"],
|
|
1324
|
+
"host capability result message",
|
|
1325
|
+
);
|
|
1326
|
+
return Object.freeze({
|
|
1327
|
+
protocolVersion: PORTABLE_PROVIDER_PLUGIN_PROTOCOL_VERSION,
|
|
1328
|
+
kind: "host.capability.result",
|
|
1329
|
+
invocationId: boundedToken(message.invocationId, "invocation ID"),
|
|
1330
|
+
requestId: boundedToken(message.requestId, "capability request ID"),
|
|
1331
|
+
result: parseCapabilityResult(message.result),
|
|
1332
|
+
});
|
|
1333
|
+
}
|
|
1334
|
+
if (message.kind === "host.capability.error") {
|
|
1335
|
+
exactKeys(
|
|
1336
|
+
message,
|
|
1337
|
+
["protocolVersion", "kind", "invocationId", "requestId", "capability", "error"],
|
|
1338
|
+
"host capability error message",
|
|
1339
|
+
);
|
|
1340
|
+
if (
|
|
1341
|
+
typeof message.capability !== "string"
|
|
1342
|
+
|| !requestCapabilityKinds.includes(
|
|
1343
|
+
message.capability as PortablePluginCapabilityRequest["kind"],
|
|
1344
|
+
)
|
|
1345
|
+
) {
|
|
1346
|
+
throw new Error("host capability error names an unsupported capability");
|
|
1347
|
+
}
|
|
1348
|
+
return Object.freeze({
|
|
1349
|
+
protocolVersion: PORTABLE_PROVIDER_PLUGIN_PROTOCOL_VERSION,
|
|
1350
|
+
kind: "host.capability.error",
|
|
1351
|
+
invocationId: boundedToken(message.invocationId, "invocation ID"),
|
|
1352
|
+
requestId: boundedToken(message.requestId, "capability request ID"),
|
|
1353
|
+
capability: message.capability as PortablePluginCapabilityRequest["kind"],
|
|
1354
|
+
error: parseError(message.error),
|
|
1355
|
+
});
|
|
1356
|
+
}
|
|
1357
|
+
if (message.kind === "host.cancel") {
|
|
1358
|
+
exactKeys(
|
|
1359
|
+
message,
|
|
1360
|
+
["protocolVersion", "kind", "invocationId", "reason"],
|
|
1361
|
+
"host cancel message",
|
|
1362
|
+
);
|
|
1363
|
+
if (
|
|
1364
|
+
message.reason !== "user"
|
|
1365
|
+
&& message.reason !== "timeout"
|
|
1366
|
+
&& message.reason !== "shutdown"
|
|
1367
|
+
) {
|
|
1368
|
+
throw new Error("host cancellation reason is unsupported");
|
|
1369
|
+
}
|
|
1370
|
+
return Object.freeze({
|
|
1371
|
+
protocolVersion: PORTABLE_PROVIDER_PLUGIN_PROTOCOL_VERSION,
|
|
1372
|
+
kind: "host.cancel",
|
|
1373
|
+
invocationId: boundedToken(message.invocationId, "invocation ID"),
|
|
1374
|
+
reason: message.reason,
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1377
|
+
if (message.kind === "plugin.ready") {
|
|
1378
|
+
exactKeys(
|
|
1379
|
+
message,
|
|
1380
|
+
["protocolVersion", "kind", "plugin"],
|
|
1381
|
+
"plugin ready message",
|
|
1382
|
+
);
|
|
1383
|
+
return Object.freeze({
|
|
1384
|
+
protocolVersion: PORTABLE_PROVIDER_PLUGIN_PROTOCOL_VERSION,
|
|
1385
|
+
kind: "plugin.ready",
|
|
1386
|
+
plugin: parsePluginIdentity(message.plugin),
|
|
1387
|
+
});
|
|
1388
|
+
}
|
|
1389
|
+
if (message.kind === "plugin.capability.request") {
|
|
1390
|
+
exactKeys(
|
|
1391
|
+
message,
|
|
1392
|
+
["protocolVersion", "kind", "invocationId", "requestId", "request"],
|
|
1393
|
+
"plugin capability request message",
|
|
1394
|
+
);
|
|
1395
|
+
return Object.freeze({
|
|
1396
|
+
protocolVersion: PORTABLE_PROVIDER_PLUGIN_PROTOCOL_VERSION,
|
|
1397
|
+
kind: "plugin.capability.request",
|
|
1398
|
+
invocationId: boundedToken(message.invocationId, "invocation ID"),
|
|
1399
|
+
requestId: boundedToken(message.requestId, "capability request ID"),
|
|
1400
|
+
request: parseCapabilityRequest(message.request),
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
if (message.kind === "plugin.result") {
|
|
1404
|
+
exactKeys(
|
|
1405
|
+
message,
|
|
1406
|
+
["protocolVersion", "kind", "invocationId", "output", "finalUrl"],
|
|
1407
|
+
"plugin result message",
|
|
1408
|
+
);
|
|
1409
|
+
if (message.finalUrl !== null && typeof message.finalUrl !== "string") {
|
|
1410
|
+
throw new Error("plugin result finalUrl must be null or an HTTPS URL");
|
|
1411
|
+
}
|
|
1412
|
+
return Object.freeze({
|
|
1413
|
+
protocolVersion: PORTABLE_PROVIDER_PLUGIN_PROTOCOL_VERSION,
|
|
1414
|
+
kind: "plugin.result",
|
|
1415
|
+
invocationId: boundedToken(message.invocationId, "invocation ID"),
|
|
1416
|
+
output: normalizeJsonValue(message.output, "plugin output"),
|
|
1417
|
+
finalUrl: message.finalUrl === null
|
|
1418
|
+
? null
|
|
1419
|
+
: exactHttpsUrl(message.finalUrl, "plugin result finalUrl"),
|
|
1420
|
+
});
|
|
1421
|
+
}
|
|
1422
|
+
if (message.kind === "plugin.error") {
|
|
1423
|
+
if (message.stage === "startup") {
|
|
1424
|
+
exactKeys(
|
|
1425
|
+
message,
|
|
1426
|
+
["protocolVersion", "kind", "stage", "code", "message"],
|
|
1427
|
+
"plugin startup error message",
|
|
1428
|
+
);
|
|
1429
|
+
const parsed = parseError({ code: message.code, message: message.message });
|
|
1430
|
+
return Object.freeze({
|
|
1431
|
+
protocolVersion: PORTABLE_PROVIDER_PLUGIN_PROTOCOL_VERSION,
|
|
1432
|
+
kind: "plugin.error",
|
|
1433
|
+
stage: "startup",
|
|
1434
|
+
code: parsed.code,
|
|
1435
|
+
message: parsed.message,
|
|
1436
|
+
});
|
|
1437
|
+
}
|
|
1438
|
+
if (message.stage !== "invocation") {
|
|
1439
|
+
throw new Error("plugin error stage must be startup or invocation");
|
|
1440
|
+
}
|
|
1441
|
+
exactKeys(
|
|
1442
|
+
message,
|
|
1443
|
+
["protocolVersion", "kind", "stage", "invocationId", "code", "message"],
|
|
1444
|
+
"plugin invocation error message",
|
|
1445
|
+
);
|
|
1446
|
+
const parsed = parseError({ code: message.code, message: message.message });
|
|
1447
|
+
return Object.freeze({
|
|
1448
|
+
protocolVersion: PORTABLE_PROVIDER_PLUGIN_PROTOCOL_VERSION,
|
|
1449
|
+
kind: "plugin.error",
|
|
1450
|
+
stage: "invocation",
|
|
1451
|
+
invocationId: boundedToken(message.invocationId, "invocation ID"),
|
|
1452
|
+
code: parsed.code,
|
|
1453
|
+
message: parsed.message,
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1456
|
+
if (message.kind === "plugin.cancelled") {
|
|
1457
|
+
exactKeys(
|
|
1458
|
+
message,
|
|
1459
|
+
["protocolVersion", "kind", "invocationId"],
|
|
1460
|
+
"plugin cancelled message",
|
|
1461
|
+
);
|
|
1462
|
+
return Object.freeze({
|
|
1463
|
+
protocolVersion: PORTABLE_PROVIDER_PLUGIN_PROTOCOL_VERSION,
|
|
1464
|
+
kind: "plugin.cancelled",
|
|
1465
|
+
invocationId: boundedToken(message.invocationId, "invocation ID"),
|
|
1466
|
+
});
|
|
1467
|
+
}
|
|
1468
|
+
throw new Error("portable provider plugin message kind is unsupported");
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
export function parsePortableProviderPluginMessage(
|
|
1472
|
+
value: unknown,
|
|
1473
|
+
): PortableProviderPluginMessageResult {
|
|
1474
|
+
try {
|
|
1475
|
+
return { ok: true, value: parseMessageOrThrow(value) };
|
|
1476
|
+
} catch (error) {
|
|
1477
|
+
return {
|
|
1478
|
+
ok: false,
|
|
1479
|
+
issues: Object.freeze([
|
|
1480
|
+
error instanceof Error ? error.message : "invalid portable provider plugin message",
|
|
1481
|
+
]),
|
|
1482
|
+
};
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
export function encodePortableProviderPluginMessage(
|
|
1487
|
+
value: PortableProviderPluginMessage,
|
|
1488
|
+
): string {
|
|
1489
|
+
const parsed = parsePortableProviderPluginMessage(value);
|
|
1490
|
+
if (!parsed.ok) {
|
|
1491
|
+
throw new Error(
|
|
1492
|
+
`invalid portable provider plugin message: ${parsed.issues.join("; ")}`,
|
|
1493
|
+
);
|
|
1494
|
+
}
|
|
1495
|
+
const frame = JSON.stringify(parsed.value);
|
|
1496
|
+
if (Buffer.byteLength(frame, "utf8") > MAX_PORTABLE_PROVIDER_PLUGIN_FRAME_BYTES) {
|
|
1497
|
+
throw new Error("portable provider plugin message exceeds the frame bound");
|
|
1498
|
+
}
|
|
1499
|
+
return `${frame}\n`;
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
export function parsePortableProviderPluginFrame(
|
|
1503
|
+
frame: Uint8Array | string,
|
|
1504
|
+
): PortableProviderPluginMessage {
|
|
1505
|
+
const bytes = typeof frame === "string"
|
|
1506
|
+
? Buffer.from(frame, "utf8")
|
|
1507
|
+
: Buffer.from(frame);
|
|
1508
|
+
if (
|
|
1509
|
+
bytes.byteLength < 2
|
|
1510
|
+
|| bytes.byteLength > MAX_PORTABLE_PROVIDER_PLUGIN_FRAME_BYTES + 1
|
|
1511
|
+
|| bytes.at(-1) !== 0x0a
|
|
1512
|
+
) {
|
|
1513
|
+
throw new Error("portable provider plugin frame must be one bounded LF-terminated record");
|
|
1514
|
+
}
|
|
1515
|
+
const lineBytes = bytes.subarray(0, -1);
|
|
1516
|
+
if (lineBytes.includes(0x0a) || lineBytes.includes(0x0d)) {
|
|
1517
|
+
throw new Error("portable provider plugin frames use LF only and contain one record");
|
|
1518
|
+
}
|
|
1519
|
+
let line: string;
|
|
1520
|
+
try {
|
|
1521
|
+
line = new TextDecoder("utf-8", { fatal: true }).decode(lineBytes);
|
|
1522
|
+
} catch {
|
|
1523
|
+
throw new Error("portable provider plugin frame must contain valid UTF-8");
|
|
1524
|
+
}
|
|
1525
|
+
let value: unknown;
|
|
1526
|
+
try {
|
|
1527
|
+
value = JSON.parse(line) as unknown;
|
|
1528
|
+
} catch {
|
|
1529
|
+
throw new Error("portable provider plugin frame must contain valid JSON");
|
|
1530
|
+
}
|
|
1531
|
+
const parsed = parsePortableProviderPluginMessage(value);
|
|
1532
|
+
if (!parsed.ok) {
|
|
1533
|
+
throw new Error(
|
|
1534
|
+
`invalid portable provider plugin frame: ${parsed.issues.join("; ")}`,
|
|
1535
|
+
);
|
|
1536
|
+
}
|
|
1537
|
+
if (JSON.stringify(parsed.value) !== line) {
|
|
1538
|
+
throw new Error(
|
|
1539
|
+
"portable provider plugin frame must use canonical JSON encoding",
|
|
1540
|
+
);
|
|
1541
|
+
}
|
|
1542
|
+
return parsed.value;
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
export class PortableProviderPluginFrameDecoder {
|
|
1546
|
+
#buffer = Buffer.alloc(0);
|
|
1547
|
+
#length = 0;
|
|
1548
|
+
#finished = false;
|
|
1549
|
+
#failed = false;
|
|
1550
|
+
|
|
1551
|
+
#ensureCapacity(required: number): void {
|
|
1552
|
+
if (required <= this.#buffer.byteLength) return;
|
|
1553
|
+
let capacity = Math.max(256, this.#buffer.byteLength);
|
|
1554
|
+
while (capacity < required) {
|
|
1555
|
+
capacity = Math.min(
|
|
1556
|
+
MAX_PORTABLE_PROVIDER_PLUGIN_FRAME_BYTES + 1,
|
|
1557
|
+
capacity * 2,
|
|
1558
|
+
);
|
|
1559
|
+
}
|
|
1560
|
+
const next = Buffer.allocUnsafe(capacity);
|
|
1561
|
+
this.#buffer.copy(next, 0, 0, this.#length);
|
|
1562
|
+
this.#buffer = next;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
#append(fragment: Uint8Array): void {
|
|
1566
|
+
const required = this.#length + fragment.byteLength;
|
|
1567
|
+
if (required > MAX_PORTABLE_PROVIDER_PLUGIN_FRAME_BYTES) {
|
|
1568
|
+
throw new Error("portable provider plugin frame exceeds the byte bound");
|
|
1569
|
+
}
|
|
1570
|
+
this.#ensureCapacity(required);
|
|
1571
|
+
this.#buffer.set(fragment, this.#length);
|
|
1572
|
+
this.#length = required;
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
#parseFrame(fragment: Uint8Array): PortableProviderPluginMessage {
|
|
1576
|
+
this.#append(fragment);
|
|
1577
|
+
this.#ensureCapacity(this.#length + 1);
|
|
1578
|
+
this.#buffer[this.#length] = 0x0a;
|
|
1579
|
+
const frame = this.#buffer.subarray(0, this.#length + 1);
|
|
1580
|
+
const message = parsePortableProviderPluginFrame(frame);
|
|
1581
|
+
this.#length = 0;
|
|
1582
|
+
return message;
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
push(chunk: Uint8Array): readonly PortableProviderPluginMessage[] {
|
|
1586
|
+
if (this.#finished) {
|
|
1587
|
+
throw new Error("portable provider plugin frame decoder is finished");
|
|
1588
|
+
}
|
|
1589
|
+
if (this.#failed) {
|
|
1590
|
+
throw new Error("portable provider plugin frame decoder has failed");
|
|
1591
|
+
}
|
|
1592
|
+
if (chunk.byteLength === 0) return [];
|
|
1593
|
+
const messages: PortableProviderPluginMessage[] = [];
|
|
1594
|
+
try {
|
|
1595
|
+
let offset = 0;
|
|
1596
|
+
while (offset < chunk.byteLength) {
|
|
1597
|
+
const newline = chunk.indexOf(0x0a, offset);
|
|
1598
|
+
if (newline < 0) {
|
|
1599
|
+
this.#append(chunk.subarray(offset));
|
|
1600
|
+
break;
|
|
1601
|
+
}
|
|
1602
|
+
messages.push(this.#parseFrame(chunk.subarray(offset, newline)));
|
|
1603
|
+
offset = newline + 1;
|
|
1604
|
+
}
|
|
1605
|
+
} catch (error) {
|
|
1606
|
+
this.#failed = true;
|
|
1607
|
+
this.#length = 0;
|
|
1608
|
+
throw error;
|
|
1609
|
+
}
|
|
1610
|
+
return Object.freeze(messages);
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
finish(): void {
|
|
1614
|
+
if (this.#finished) {
|
|
1615
|
+
throw new Error("portable provider plugin frame decoder is already finished");
|
|
1616
|
+
}
|
|
1617
|
+
if (this.#failed) {
|
|
1618
|
+
throw new Error("portable provider plugin frame decoder has failed");
|
|
1619
|
+
}
|
|
1620
|
+
this.#finished = true;
|
|
1621
|
+
if (this.#length !== 0) {
|
|
1622
|
+
throw new Error("portable provider plugin stream ended with a partial frame");
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
}
|