feat(efp): add Ethereum Follow Protocol plugin#12377
Open
Quantumlyy wants to merge 16 commits intoDimensionDev:developfrom
Open
feat(efp): add Ethereum Follow Protocol plugin#12377Quantumlyy wants to merge 16 commits intoDimensionDev:developfrom
Quantumlyy wants to merge 16 commits intoDimensionDev:developfrom
Conversation
Replace the body-wide GlobalInjection MutationObserver with a per-post hook that uses usePostInfoDetails.rootNode() (NextID pattern) and queries [data-testid=card.wrapper] within the post (Mask Twitter PostInspector pattern). The previous broad scan plus EFP-specific metadata heuristics didn't reliably catch Twitter's lazy-rendered card, leaving a duplicate native preview below the EFP card.
The post's rootNode (per twitter selector at packages/mask/content-script/site-adaptors/twitter.com/utils/selector.ts:186) is the tweetText/tweetPhoto/div[lang] — the card.wrapper is its sibling inside [data-testid=tweet], not a descendant. Climb up to the tweet element before querying so the native EFP card is actually found.
The native EFP detection was failing because Twitter wraps the link in t.co (no href match) and the card.wrapper's textContent only holds 'brantly.eth' — the 'efp.app' reference lives in aria-label on the inner anchor and in the 'From efp.app' footer that is a sibling of card.wrapper. Detect via aria-label so isEFPCard returns true, and hide the parent that's aria-labelledby the card so the footer is hidden along with the wrapper.
[data-testid="tweet"] is sometimes on a nested div (not the article) in this version of Twitter, so closest() can land on an element that doesn't contain card.wrapper. Search from article.parentElement (the timeline section / detail view container) instead — that covers both the timeline layout (card inside article) and the detail layout (card in a sibling subtree). Falls back to document.body when no article ancestor is found.
Twitter's postsContentSelector matches [data-testid="card.wrapper"] directly for link-only tweets, so rootNode can BE the card.wrapper. The strict equality check was correct for that case but missed the defensive case where rootNode might end up nested inside the wrapper. contains() covers both.
article.parentElement is the entire timeline container, so the observer's textContent fallback in isEFPCard could hide a sibling tweet whose Twitter preview happens to mention efp.app/ethfollow.xyz (news article, embed of an EFP-related quote, etc.) even though no EFP plugin is rendering for that post. Use isFocusing to detect detail view, where the card can live in a sibling subtree of the article (per twitter's postsContentSelector at packages/mask/content-script/site-adaptors/twitter.com/utils/selector.ts:195), and only widen the search root there. Timeline view stays scoped to the article.
- Read rootNode/isFocusing via useContext(PostInfoContext) instead of the usePostInfoDetails proxy. The proxy returns plain values for fields like rootNode (no real hook is invoked under the hood) and react-compiler flags the property-access call as 'hook referenced as a normal value'. Reading from the context directly sidesteps the rule and is also one fewer indirection. - Add the 'u' flag to /\\s+/ (require-unicode-regexp). - Use optional chaining on labelledBy.split(...) per @typescript-eslint/prefer-optional-chain. Confirmed clean with 'pnpm exec eslint packages/plugins/EFP --no-cache'.
For tweets that are just an EFP link, Twitter's postsContentSelector matches data-testid=card.wrapper directly as the post's rootNode, and the plugin UI mounts in rootElement.afterShadow — a sibling of the card.wrapper, not a descendant. The previous guard skipped hiding any card that contained rootNode, leaving the native preview rendered alongside the EFP card. Replace the skip with a target choice: hide the card itself when the container would also contain rootNode (so we don't take an ancestor — which holds our afterShadow sibling — down with it), and keep hiding the full container otherwise (so the 'From efp.app' footer goes away with the wrapper).
- Wrap user-visible strings in <Trans> per repo convention (ProfileCard eyebrow/metrics/footer/link, ApplicationEntries name + description) - Dedup EFP host & reserved-path lists between constants.ts and helpers/url.ts - Dedup host-keyword literals in isEFPCard via EFP_HOST_KEYWORDS - Pass parsed EFPProfileLink from inspectors to Renderer (was parsed twice) - Drop completed TODO list from README
Replace the generic Icons.Web3Profile placeholder with the EFP brand logo (gold rounded square + arrow + plus mark) at all three call sites: the App entry tile, the post wrapper, and the og-image fallback inside ProfileCard.
Move fetchEFPProfile (and the EFPProfileResponse type) to a Worker module and expose it via PluginEFPRPC, mirroring the CyberConnect pattern. Network requests now run in the background context instead of the content script, sidestepping CORS preflight on the data.ethfollow.xyz origin and aligning with repo convention for external API calls.
X often renders link text without a scheme (efp.app/vitalik.eth). mentionedLinks() requires URL.canParse (i.e. a protocol), so those get dropped before parseEFPProfileLink can see them. Switch to rawMessage() + parseURLs(text, false), matching the DecryptedInspector in the same file and the rawMessage pattern used by NextID and ScamSniffer.
cspell tokenises PluginEFPRPC as Plugin / EFPRPC (consecutive caps stay in one block), and EFPRPC isn't in any default dictionary. Add it to ignoreWords in alphabetical order.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
New plugin (
@masknet/plugin-efp) for Ethereum Follow Protocol. Detectsefp.appandethfollow.xyzprofile/list links in Twitter posts (with optional?topEight=true) and renders an inline card with ENS name, description, follower/following counts, and a "View on EFP" link. Data comes fromdata.ethfollow.xyz/api/v1, with a fallback when the API is unreachable.Also hides the native Twitter card for tweets that link to EFP so we don't end up with two embeds for the same URL. Hide is scoped to the article in the timeline and widened by one level in the detail view; there's a guard for link-only tweets where the rootNode is the card itself.
External API calls go through a Worker +
PluginEFPRPC, mirroring the CyberConnect plugin. Addeddata.ethfollow.xyztoconnect-srcin the CSP. Dedicated EFP icon registered in@masknet/icons.No new dependencies.
Type of change
Previews
Checklist
If this PR depends on external APIs: