Skip to content

Commit

Permalink
feat: linkedin pipe
Browse files Browse the repository at this point in the history
* readme

* route for basic chrome stuff

* check if user logged in

* route to navigate to a page

* linked-in-pipe-WIP

* gitignore

* remove .chrome-profile from git tracking

* removing files
  • Loading branch information
m13v authored Dec 4, 2024
1 parent 7d7eeff commit 6b9a108
Show file tree
Hide file tree
Showing 52 changed files with 22,036 additions and 0 deletions.
3 changes: 3 additions & 0 deletions examples/typescript/linkedin_ai_assistant/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": ["next/core-web-vitals", "next/typescript"]
}
61 changes: 61 additions & 0 deletions examples/typescript/linkedin_ai_assistant/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel
.next

# typescript
*.tsbuildinfo
next-env.d.ts

# chrome profile
.chrome-profile/
*.db
*.db-journal
leveldb/
Local State
Last Version
LOCK
CURRENT
LOG
LOG.old
MANIFEST*

# chrome specific
*.log
*.journal
Preferences
Session Storage/
Local Storage/
IndexedDB/
WebStorage/
Cookies*
History*
34 changes: 34 additions & 0 deletions examples/typescript/linkedin_ai_assistant/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).

## Getting Started

First, run the development server:

```bash
bun i

bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!

## Deploy on screenpipe

The easiest way to deploy your Next.js pipe is to use the [screenpipe Platform](https://screenpi.pe).

Check out our [pipe deployment documentation](https://docs.screenpi.pe/docs/plugins) for more details.


Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { NextResponse } from 'next/server';
import { setupBrowser, getActiveBrowser } from '@/lib/browser_setup';

export async function POST(request: Request) {
try {
const { wsUrl } = await request.json();
console.log('checking linkedin login status');

await setupBrowser(wsUrl);
const { page } = getActiveBrowser();

if (!page) {
throw new Error('no active browser session');
}

// Set default navigation timeout
page.setDefaultNavigationTimeout(30000);

// Navigate to LinkedIn and wait for the page to load
await page.goto('https://www.linkedin.com', { waitUntil: 'networkidle2' });

// Remove any existing event listeners to prevent hanging
page.removeAllListeners();

// Check for login status
const isLoggedIn = await Promise.race([
page.waitForSelector('nav.global-nav', { timeout: 10000 }).then(() => true),
page.waitForSelector('[data-tracking-control-name="guest_homepage-basic_sign-in-button"]', { timeout: 10000 }).then(() => false),
new Promise<boolean>((resolve, reject) => {
setTimeout(() => reject('timeout'), 15000);
})
]);

console.log('login status:', isLoggedIn ? 'logged in' : 'logged out');

// Close the page to free resources
await page.close();

return NextResponse.json({
success: true,
isLoggedIn
});

} catch (error) {
console.error('failed to check login status:', error);

// Close the page in case of error
const { page } = getActiveBrowser();
if (page) {
await page.close();
}

return NextResponse.json(
{ success: false, error: String(error) },
{ status: 500 }
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { NextResponse } from 'next/server';
import { getActiveBrowser, setupBrowser } from '@/lib/browser_setup';
import type { Page } from 'puppeteer-core';

async function navigateToPage(page: Page, url: string) {
try {
console.log('starting navigation');

// Set longer timeout and handle common errors
await page.setDefaultNavigationTimeout(60000);

// Enable request interception to handle potential issues
await page.setRequestInterception(true);

// Define request interception handler
const requestHandler = (request: any) => {
if (['image', 'font'].includes(request.resourceType())) {
request.abort(); // Abort unnecessary requests to speed up navigation
} else {
request.continue(); // Continue processing other requests
}
};

// Attach the request handler
page.on('request', requestHandler);

// Navigate to the target URL
const response = await page.goto(url, {
waitUntil: 'domcontentloaded', // Use 'domcontentloaded' to prevent potential hanging with 'networkidle0'
timeout: 60000
});

// Remove the request handler after navigation completes
page.off('request', requestHandler);

// Return navigation results
return {
status: response?.status() || 0,
finalUrl: page.url()
};

} catch (error) {
console.error('navigation error:', error);
throw error; // Rethrow error to be caught in the POST handler
}
}

export async function POST(request: Request) {
try {
const { url, wsUrl } = await request.json();
console.log('attempting to navigate to:', url);

// Setup the browser connection using the provided WebSocket URL
await setupBrowser(wsUrl);

const { page } = getActiveBrowser();
if (!page) {
throw new Error('no active browser session');
}

// Perform the navigation
const result = await navigateToPage(page, url);

// Return a successful response with navigation details
return NextResponse.json({
success: true,
status: result.status,
finalUrl: result.finalUrl
});

} catch (error) {
console.error('failed to navigate:', error);
// Return an error response with details
return NextResponse.json({
success: false,
error: 'failed to navigate',
details: error instanceof Error ? error.message : String(error)
}, { status: 500 });
}
}
45 changes: 45 additions & 0 deletions examples/typescript/linkedin_ai_assistant/app/api/chrome/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { NextResponse } from 'next/server';
import { exec } from 'child_process';
import { promisify } from 'util';
import { quitBrowser } from '@/lib/browser_setup';
const execPromise = promisify(exec);

export async function POST() {
try {
console.log('attempting to launch chrome');

const chromePath = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
const chromeLaunchCommand = `"${chromePath}" --remote-debugging-port=9222 --restore-last-session`;
const chromeProcess = exec(chromeLaunchCommand, { detached: true, stdio: 'ignore' });

chromeProcess.unref();

console.log('chrome launch initiated');
return NextResponse.json({ success: true });
} catch (error) {
console.error('failed to launch chrome:', error);
return NextResponse.json({ success: false, error: String(error) }, { status: 500 });
}
}

export async function DELETE() {
try {
await quitChrome();
await quitBrowser();
console.log('chrome process terminated');
return NextResponse.json({ success: true });
} catch (error) {
console.error('failed to kill chrome:', error);
return NextResponse.json({ success: false, error: String(error) }, { status: 500 });
}
}

async function quitChrome() {
const killCommand = `pkill -f -- "Google Chrome"`;
try {
await execPromise(killCommand);
console.log('chrome killed');
} catch (error) {
console.log('no chrome process found to kill');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { NextResponse } from 'next/server';
import fetch from 'node-fetch';

export async function GET() {
try {
const response = await fetch('http://127.0.0.1:9222/json/version');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();

return NextResponse.json({
wsUrl: data.webSocketDebuggerUrl,
status: 'connected'
});

} catch {
return NextResponse.json({ status: 'not_connected' }, { status: 200 });
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
78 changes: 78 additions & 0 deletions examples/typescript/linkedin_ai_assistant/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

body {
font-family: Arial, Helvetica, sans-serif;
}

@layer utilities {
.text-balance {
text-wrap: balance;
}
}

@layer base {
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--primary: 0 0% 9%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 0% 3.9%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.5rem;
}
.dark {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--ring: 0 0% 83.1%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
}

@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
Loading

0 comments on commit 6b9a108

Please sign in to comment.