allow setting `post-uri` as well to skip resolving that
4191e6b0
I think this works with both bsky.app urls and at uris
1 file(s) · +59 −19
I think this works with both bsky.app urls and at uris
| 12 | 12 | * 2. A <link rel="site.standard.document" href="at://..."> tag in the document head |
|
| 13 | 13 | * |
|
| 14 | 14 | * Attributes: |
|
| 15 | + | * - post-uri: Bluesky post as AT-URI (at://...) or bsky.app URL — skips PDS document lookup |
|
| 15 | 16 | * - document-uri: AT Protocol URI for the document (optional if link tag exists) |
|
| 16 | 17 | * - depth: Maximum depth of nested replies to fetch (default: 6) |
|
| 17 | 18 | * - hide: Set to "auto" to hide if no document link is detected |
|
| 614 | 615 | * @param {string} postUri - AT Protocol URI for the post |
|
| 615 | 616 | * @returns {Promise<Array>} Array of PostView objects |
|
| 616 | 617 | */ |
|
| 618 | + | /** |
|
| 619 | + | * Normalise a user-supplied post reference to an AT-URI. |
|
| 620 | + | * Accepts: |
|
| 621 | + | * - AT-URIs as-is: at://did:plc:.../app.bsky.feed.post/rkey |
|
| 622 | + | * - bsky.app post URLs: https://bsky.app/profile/<handle-or-did>/post/<rkey> |
|
| 623 | + | * When the profile segment is already a DID no network request is made. |
|
| 624 | + | * @param {string} uriOrUrl |
|
| 625 | + | * @returns {Promise<string>} AT-URI |
|
| 626 | + | */ |
|
| 627 | + | async function resolvePostUri(uriOrUrl) { |
|
| 628 | + | if (uriOrUrl.startsWith("at://")) return uriOrUrl; |
|
| 629 | + | ||
| 630 | + | const match = uriOrUrl.match( |
|
| 631 | + | /bsky\.app\/profile\/([^/?#]+)\/post\/([^/?#]+)/, |
|
| 632 | + | ); |
|
| 633 | + | if (!match) throw new Error(`Cannot parse Bluesky URL: ${uriOrUrl}`); |
|
| 634 | + | ||
| 635 | + | const [, handleOrDid, rkey] = match; |
|
| 636 | + | ||
| 637 | + | let did = handleOrDid; |
|
| 638 | + | if (!handleOrDid.startsWith("did:")) { |
|
| 639 | + | const url = new URL( |
|
| 640 | + | "https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle", |
|
| 641 | + | ); |
|
| 642 | + | url.searchParams.set("handle", handleOrDid); |
|
| 643 | + | const response = await fetch(url.toString()); |
|
| 644 | + | if (!response.ok) |
|
| 645 | + | throw new Error(`Failed to resolve handle: ${response.status}`); |
|
| 646 | + | did = (await response.json()).did; |
|
| 647 | + | } |
|
| 648 | + | ||
| 649 | + | return `at://${did}/app.bsky.feed.post/${rkey}`; |
|
| 650 | + | } |
|
| 651 | + | ||
| 617 | 652 | async function getQuotes(postUri) { |
|
| 618 | 653 | const quotes = []; |
|
| 619 | 654 | let cursor; |
|
| 676 | 711 | } |
|
| 677 | 712 | ||
| 678 | 713 | static get observedAttributes() { |
|
| 679 | - | return ["document-uri", "depth", "hide"]; |
|
| 714 | + | return ["post-uri", "document-uri", "depth", "hide"]; |
|
| 680 | 715 | } |
|
| 681 | 716 | ||
| 682 | 717 | connectedCallback() { |
|
| 726 | 761 | this.state = { type: "loading" }; |
|
| 727 | 762 | this.render(); |
|
| 728 | 763 | ||
| 729 | - | const docUri = this.documentUri; |
|
| 730 | - | if (!docUri) { |
|
| 731 | - | this.state = { type: "no-document" }; |
|
| 732 | - | this.render(); |
|
| 733 | - | return; |
|
| 734 | - | } |
|
| 764 | + | try { |
|
| 765 | + | // Resolve the post URI — either directly from the attribute or via the |
|
| 766 | + | // document record (which requires a PDS roundtrip) |
|
| 767 | + | const rawPostUri = this.getAttribute("post-uri"); |
|
| 768 | + | let postUri = rawPostUri ? await resolvePostUri(rawPostUri) : null; |
|
| 769 | + | if (!postUri) { |
|
| 770 | + | const docUri = this.documentUri; |
|
| 771 | + | if (!docUri) { |
|
| 772 | + | this.state = { type: "no-document" }; |
|
| 773 | + | this.render(); |
|
| 774 | + | return; |
|
| 775 | + | } |
|
| 735 | 776 | ||
| 736 | - | try { |
|
| 737 | - | // Fetch the document record |
|
| 738 | - | const document = await getDocument(docUri); |
|
| 777 | + | const document = await getDocument(docUri); |
|
| 778 | + | if (!document.bskyPostRef) { |
|
| 779 | + | this.state = { type: "no-comments-enabled" }; |
|
| 780 | + | this.render(); |
|
| 781 | + | return; |
|
| 782 | + | } |
|
| 739 | 783 | ||
| 740 | - | // Check if document has a Bluesky post reference |
|
| 741 | - | if (!document.bskyPostRef) { |
|
| 742 | - | this.state = { type: "no-comments-enabled" }; |
|
| 743 | - | this.render(); |
|
| 744 | - | return; |
|
| 784 | + | postUri = document.bskyPostRef.uri; |
|
| 745 | 785 | } |
|
| 746 | 786 | ||
| 747 | - | const postUrl = buildBskyAppUrl(document.bskyPostRef.uri); |
|
| 748 | - | const blackskyPostUrl = buildBlackskyAppUrl(document.bskyPostRef.uri); |
|
| 787 | + | const postUrl = buildBskyAppUrl(postUri); |
|
| 788 | + | const blackskyPostUrl = buildBlackskyAppUrl(postUri); |
|
| 749 | 789 | ||
| 750 | 790 | // Fetch thread and quotes in parallel; quote failures degrade gracefully |
|
| 751 | 791 | const [threadResult, quotesResult] = await Promise.allSettled([ |
|
| 752 | - | getPostThread(document.bskyPostRef.uri, this.depth), |
|
| 753 | - | getQuotes(document.bskyPostRef.uri), |
|
| 792 | + | getPostThread(postUri, this.depth), |
|
| 793 | + | getQuotes(postUri), |
|
| 754 | 794 | ]); |
|
| 755 | 795 | ||
| 756 | 796 | if (threadResult.status === "rejected") { |
|