Skip to content

feat: hybrid follow engine — LinkedIn WebView interaction, session management & deep-link fallback#177

Open
Itzzavdheshh wants to merge 6 commits into
Dev-Card:mainfrom
Itzzavdheshh:card
Open

feat: hybrid follow engine — LinkedIn WebView interaction, session management & deep-link fallback#177
Itzzavdheshh wants to merge 6 commits into
Dev-Card:mainfrom
Itzzavdheshh:card

Conversation

@Itzzavdheshh
Copy link
Copy Markdown

Summary

Implements the complete Layer 2 WebView Interaction Engine as specified in Product Doc Section 6.9. LinkedIn's API blocks programmatic connection requests — this PR routes LinkedIn connects through a secure in-app WebView with session cookie sharing, JS-injected Connect button highlighting, invite success detection, and a 10s deep-link fallback. Users tap "Connect on LinkedIn" in DevCardViewScreen, see the real LinkedIn profile inside the app with their session active, tap the native button, and receive a success toast — all without leaving DevCard or violating LinkedIn's API terms.

Closes #36


Type of Change

  • Bug fix
  • New feature
  • Refactor (no functional change)
  • UI / Design change
  • Tests only
  • Documentation
  • Infrastructure / DevOps
  • Security

What Changed

apps/backend/src/routes/follow.ts

  • Added LinkedIn webview strategy handler — when followStrategy === 'webview', returns { strategy: 'webview', url } instead of an error; strictly guarded to platform === 'linkedin' only

apps/mobile/src/navigation/MainTabs.tsx

  • WebViewConnect route params updated with strict TypeScript types — url and platformName now required; profileUrl, displayName, username kept optional for backward compatibility

apps/mobile/src/screens/DevCardViewScreen.tsx

  • handlePlatformAction case 'webview' now POSTs to backend; on strategy === 'webview' response navigates to WebViewScreen with resolved URL
  • Graceful fallback: if backend is down or errors, computes URL locally via handleWebViewConnect(link) — zero broken flows

apps/mobile/src/screens/WebViewScreen.tsx

  • sharedCookiesEnabled={true} + thirdPartyCookiesEnabled={true} — LinkedIn session carries over from system browser
  • Instruction banner: "You are viewing this profile in DevCard — tap Connect on LinkedIn to send your request"
  • JS injection: locates native Connect button, smooth-scrolls into center view, applies dashed blue border — no auto-click, user must tap manually (platform compliance)
  • Dual-guard success detection: listens for DOM text changes (Pending, Invitation sent) and URL transitions; send-invite stage explicitly excluded to prevent premature triggers
  • isSuccessHandled ref guard — success toast (1.2s) + modal dismiss fire exactly once; duplicate events impossible
  • 10s timeout + onError + onHttpError handlers all route to getDeepLinkUrl → native LinkedIn app → system browser fallback chain

apps/backend/README.md

  • Follow Engine architecture section added — all 4 layers documented with Mermaid decision-tree flow diagram

How to Test

npm install
cd apps/backend && npm run dev
cd apps/mobile && npx expo start
  1. Open any DevCard with a LinkedIn link → tap "Connect on LinkedIn" → verify WebViewScreen opens with correct LinkedIn profile
  2. Verify instruction banner visible above WebView; verify LinkedIn session active (no login prompt if authenticated in system browser)
  3. Verify "Connect" button is scrolled into view with dashed blue highlight
  4. Tap native Connect button → verify success toast shows ~1.2s → modal auto-dismisses
  5. Tap Connect on an already-connected profile → verify no duplicate toast fires
  6. Kill backend server → tap Connect → verify WebView still opens with locally computed URL
  7. Open WebView → wait 10s without loading → verify native LinkedIn app launches (or system browser)
  8. Test GitHub connect and Discord copy flows → verify completely unaffected
  9. Run npx tsc --noEmit → verify zero TypeScript errors
  10. Run git diff --check → verify zero trailing whitespace

Checklist

  • My code follows the project's coding style (pnpm -r run lint passes)
  • TypeScript compiles without errors (pnpm -r run typecheck)
  • I have added or updated tests for the changes I made
  • All tests pass locally (pnpm -r run test)
  • I have updated documentation where necessary — apps/backend/README.md Follow Engine section added
  • No new console.log or debug statements left in the code
  • Breaking changes are documented in this PR description
  • LinkedIn guard strictly scoped — platform === 'linkedin' only; zero impact on GitHub, GitLab, Discord flows
  • No auto-click injected — platform compliance maintained; user taps native button manually
  • isSuccessHandled ref guard active — success toast + dismiss fire exactly once
  • All WebView failure paths covered — timeout, onError, onHttpError all route to deep-link fallback
  • Backend offline fallback implemented — local URL computation fires on any API failure

Screenshots / Recordings

1. WebViewScreen — LinkedIn profile loaded with instruction banner
image


2. JS injection — Connect button scrolled into view + dashed blue highlight
image

3. Success toast — invite detected, native banner shown 1.2s then dismissed
image


4. 10s timeout — native LinkedIn app launched via deep link
image)


5. Backend offline — WebView still opens with local fallback URL
image


6. npx tsc --noEmit — zero TypeScript errors
image


7. apps/backend/README.md — Follow Engine section with Mermaid diagram
image


Additional Context

  • Why WebView and not API? LinkedIn's public API explicitly prohibits programmatic connection requests. This WebView approach is the only compliant path — the user performs the action themselves inside a real browser session.
  • Why no auto-click? Auto-clicking the Connect button would violate LinkedIn's Terms of Service. The JS injection only scrolls and highlights — the user must tap.
  • Trade-off — session dependency: If the user has never logged into LinkedIn on their device, they will see a login screen inside the WebView. This is expected and unavoidable without storing credentials (which would be a far worse trade-off).
  • Future work: The isSuccessHandled guard and dual-detection (DOM + URL) could be extended to other platforms that move to a webview strategy in future. The 4-layer architecture in follow.ts is designed for this extensibility.

🙌 Contribution Note

Hi **@ShantKhatri ** 👋

This PR delivers the complete Layer 2 WebView Follow Engine from Product Doc Section 6.9 — every acceptance criterion met, every failure path covered, and full platform compliance maintained.

  • LinkedIn limitation solved cleanly — WebView routes around the API block; no ToS violations
  • End-to-end flow — backend strategy → dispatch → WebView with session → highlighted button → success detection → toast + dismiss
  • Zero broken flows — backend down, WebView timeout, load error — every path has recovery
  • Platform compliant — no auto-click; user taps LinkedIn's native button
  • 5 files, surgical changes — GitHub, GitLab, Discord flows completely untouched
  • TypeScript cleantsc --noEmit passes, WebViewConnect params fully typed

Happy to address any feedback! 🚀


🏷️ Labels

#GSSOC #feature #linkedin #webview #follow-engine #react-native #session-management #js-injection #deep-link #mobile #typescript #platform-compliance #advanced


Submitted as part of Open Source Contribution —GSSoC(GirlScript Summer of Code)

@Itzzavdheshh
Copy link
Copy Markdown
Author

Hi **@ShantKhatri ** 👋

This PR delivers the complete Layer 2 WebView Follow Engine from Product Doc Section 6.9 — every acceptance criterion met, every failure path covered, and full platform compliance maintained.

  • LinkedIn limitation solved cleanly — WebView routes around the API block; no ToS violations
  • End-to-end flow — backend strategy → dispatch → WebView with session → highlighted button → success detection → toast + dismiss
  • Zero broken flows — backend down, WebView timeout, load error — every path has recovery
  • Platform compliant — no auto-click; user taps LinkedIn's native button
  • 5 files, surgical changes — GitHub, GitLab, Discord flows completely untouched
  • TypeScript cleantsc --noEmit passes, WebViewConnect params fully typed

Happy to address any feedback! 🚀

@ShantKhatri
Copy link
Copy Markdown
Contributor

Hi **@ShantKhatri ** 👋

This PR delivers the complete Layer 2 WebView Follow Engine from Product Doc Section 6.9 — every acceptance criterion met, every failure path covered, and full platform compliance maintained.

  • LinkedIn limitation solved cleanly — WebView routes around the API block; no ToS violations
  • End-to-end flow — backend strategy → dispatch → WebView with session → highlighted button → success detection → toast + dismiss
  • Zero broken flows — backend down, WebView timeout, load error — every path has recovery
  • Platform compliant — no auto-click; user taps LinkedIn's native button
  • 5 files, surgical changes — GitHub, GitLab, Discord flows completely untouched
  • TypeScript cleantsc --noEmit passes, WebViewConnect params fully typed

Happy to address any feedback! 🚀

Great work done @Itzzavdheshh , I really appreciate this. This PR will take some time to review, as I wanna look into this personally. This was the part which we need to really focus on. Can you please add just a small demo video, showing the feature, so that I can have idea how functionality working for this?

@Harxhit Harxhit added the gssoc:approved Required label for every approved PR. Gives the base +50 points and enables contribution tracking. label May 19, 2026
Comment thread apps/backend/src/plugins/prisma.ts Outdated
Comment thread apps/backend/src/routes/auth.ts Outdated
Comment thread apps/backend/src/routes/auth.ts Outdated
Comment thread apps/backend/src/routes/connect.ts
Comment thread apps/mobile/src/components/Skeleton.tsx Outdated
Comment thread apps/mobile/src/navigation/MainTabs.tsx Outdated
Comment thread apps/backend/src/__tests__/profiles.test.ts Outdated
Comment thread apps/backend/src/routes/connect.ts Outdated
@Harxhit
Copy link
Copy Markdown
Collaborator

Harxhit commented May 19, 2026

@Itzzavdheshh Could please join our discord channel from README I would like to discuss something with you.

- Backend: followRoutes returns webview strategy for LinkedIn/Twitter platforms
- Backend: POST /api/follow/:platform/:targetUsername/log for telemetry
- Backend: DELETE /api/follow/:platform/:targetUsername/log to reset Done state
- Backend: public profile now returns followed:true for previously connected links
- Backend: auth improvements — encode mobile redirect URI in OAuth state
- Mobile: WebViewScreen — full LinkedIn JS injection engine with polling,
  MutationObserver, visibilitychange, popstate, and injectedJSBeforeContentLoaded
- Mobile: DevCardViewScreen — premium UI, emoji icons, brand-colored buttons,
  Done tile with long-press reset, GitHub browser fallback
- Mobile: HomeScreen — username search bar to view any DevCard profile
- Mobile: App.tsx — hash fragment token extraction for OAuth deep links
- Mobile: config.ts — auto-detects LAN IP via Expo Constants for Expo Go
- Mobile: Expo migration — index.js, metro.config.js, babel.config.js, app.json
- Tests: new follow.test.ts cases for webview strategy and log endpoint
- Docs: README updated with telemetry and fallback overlay details
- Config: docker-compose port 5433, .env.example LAN IP placeholders
- prisma.ts: replace authenticate:any with proper typed signature
  (request: FastifyRequest, reply: FastifyReply) => Promise<void>
- auth.ts: replace err as any with instanceof Error check in both
  GitHub and Google OAuth catch blocks for type-safe error handling
- Skeleton.tsx: replace width/height as any with DimensionValue type
  from react-native to preserve TypeScript safety
- connect.ts: replace err as any with instanceof Error check in
  GitHub connect catch block (same pattern as auth.ts fix)
- MainTabs.tsx: extract WebViewConnect params into standalone exported
  type WebViewConnectParams for reusability and future maintainability
- profiles.test.ts: replace mockPrisma as any with Pick<PrismaClient,'user'>
  and unknown cast to preserve TypeScript safety in tests
- prisma.ts: merged authenticate type declaration — kept upstream's
  method signature style with our FastifyRequest/FastifyReply imports
- pnpm-lock.yaml: kept card branch version (includes Expo packages)
@Itzzavdheshh
Copy link
Copy Markdown
Author

HI @ShantKhatri & @Harxhit ...please review , i did some changes ....Happy to address any feedback! 🚀

@ShantKhatri ---> i hope this work : https://drive.google.com/file/d/1J6VaEbFByZMBLIb7LSN-zOuOkNxGU9pQ/view?usp=drivesdk

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gssoc:approved Required label for every approved PR. Gives the base +50 points and enables contribution tracking.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

backend + mobile: implement full WebView Follow Engine for LinkedIn in-app connect

3 participants