Skip to content

Commit

Permalink
fix: save cookie to session json
Browse files Browse the repository at this point in the history
  • Loading branch information
luoling8192 committed Mar 3, 2025
1 parent 4c1040f commit 499760a
Show file tree
Hide file tree
Showing 9 changed files with 495 additions and 99 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,5 @@ coverage/
*.mp3

**/temp/

*.session.json
39 changes: 30 additions & 9 deletions services/twitter-services/docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,13 @@ Using listhen for optimized development experience, including automatic browser

#### 5.2.1 Authentication Service (Auth Service)

Handles Twitter session detection and maintenance. Uses a manual login approach where the service opens the Twitter login page and waits for the user to complete the authentication process. After successful login, the service can save cookies for future sessions, allowing cookie-based authentication in subsequent uses.
Handles Twitter session detection and maintenance. Features a multi-stage authentication approach:

1. **Session File Loading**: First attempts to load saved sessions from disk using the SessionManager
2. **Existing Session Detection**: Checks if the browser already has a valid Twitter session
3. **Manual Login Process**: If no existing session is found, opens the Twitter login page for user authentication

After successful login through any method, the service automatically saves the session cookies to file for future use. The SessionManager handles the serialization and persistence of authentication data, reducing the need for repeated manual logins.

#### 5.2.2 Timeline Service (Timeline Service)

Expand All @@ -108,10 +114,21 @@ Extracts structured data from HTML.

Controls request frequency to avoid triggering Twitter limits.

#### 5.3.3 Session Manager

Manages authentication session data, providing methods to:

- Save session cookies to local files
- Load previous sessions during startup
- Delete invalid or expired sessions
- Validate session age and integrity

## 6. Data Flow

1. **Request Flow**: Application Layer → Adapter → Core Service → Browser Adapter Layer → BrowserBase API → Twitter
2. **Response Flow**: Twitter → BrowserBase API → Browser Adapter Layer → Core Service → Data Parsing → Adapter → Application Layer
3. **Authentication Flow**:
- Load Session → Check Existing Session → Manual Login → Session Validation → Session Storage

## 7. Configuration System

Expand Down Expand Up @@ -158,6 +175,8 @@ interface Config {
}
```

The system no longer relies on the `TWITTER_COOKIES` environment variable, as cookies are now managed through the session management system.

## 8. Development and Testing

### 8.1 Development Environment Setup
Expand All @@ -168,7 +187,7 @@ npm install

# Set environment variables
cp .env.example .env
# Edit .env to add BrowserBase API key and Twitter credentials
# Edit .env to add BrowserBase API key and Twitter credentials (optional)

# Development mode startup
npm run dev # Standard mode
Expand Down Expand Up @@ -196,15 +215,17 @@ async function main() {
// Create Twitter service
const twitter = new TwitterService(browser)

// Initiate manual login and wait for user to complete authentication
// Initiate login process - will try:
// 1. Load existing session file
// 2. Check for existing browser session
// 3. Finally fall back to manual login if needed
const loggedIn = await twitter.login({})

if (loggedIn) {
console.log('Login successful')

// Export cookies for future use (optional)
const cookies = await twitter.exportCookies()
console.log('Cookies saved for future use:', Object.keys(cookies).length)
// Session cookies are automatically saved to file after successful login
// No need to manually export cookies

// Get timeline
const tweets = await twitter.getTimeline({ count: 10 })
Expand All @@ -230,7 +251,7 @@ async function startAiriModule() {

const twitter = new TwitterService(browser)

// Create Airi adapter (no credentials needed for manual login)
// Create Airi adapter
const airiAdapter = new AiriAdapter(twitter, {
url: process.env.AIRI_URL,
token: process.env.AIRI_TOKEN
Expand Down Expand Up @@ -292,8 +313,8 @@ For example, adding "Get Tweets from a Specific User" functionality:
- **Automated Testing**: Write unit tests and integration tests
- **Monitoring & Alerts**: Monitor service status and Twitter access limitations
- **Selector Updates**: Regularly validate and update selector configurations
- **Session Management**: Optimize session management to improve stability
- **Cookie Management**: Implement secure storage for saved session cookies
- **Session Management**: Use the built-in session management system to improve stability and reduce manual login requirements. Consider implementing session rotation and validation.
- **Cookie Management**: The system now automatically manages cookie storage via the SessionManager, but consider adding encrypted storage for production environments.

## 12. Project Roadmap

Expand Down
15 changes: 15 additions & 0 deletions services/twitter-services/src/adapters/browser-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,21 @@ export interface BrowserAdapter {
sameSite?: 'Strict' | 'Lax' | 'None'
}>>

/**
* Set cookies in the browser context
* This can set HTTP_ONLY cookies that can't be set via document.cookie
*/
setCookies: (cookies: Array<{
name: string
value: string
domain?: string
path?: string
expires?: number
httpOnly?: boolean
secure?: boolean
sameSite?: 'Strict' | 'Lax' | 'None'
}>) => Promise<void>

/**
* Get screenshot
*/
Expand Down
26 changes: 26 additions & 0 deletions services/twitter-services/src/adapters/browserbase-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,32 @@ export class StagehandBrowserAdapter implements BrowserAdapter {
return this.client.getScreenshot()
}

async getAllCookies(): Promise<Array<{
name: string
value: string
domain?: string
path?: string
expires?: number
httpOnly?: boolean
secure?: boolean
sameSite?: 'Strict' | 'Lax' | 'None'
}>> {
return this.client.getAllCookies()
}

async setCookies(cookies: Array<{
name: string
value: string
domain?: string
path?: string
expires?: number
httpOnly?: boolean
secure?: boolean
sameSite?: 'Strict' | 'Lax' | 'None'
}>): Promise<void> {
return this.client.setCookies(cookies)
}

async close(): Promise<void> {
await this.client.closeSession()
}
Expand Down
51 changes: 51 additions & 0 deletions services/twitter-services/src/browser/browserbase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,57 @@ export class StagehandClient {
return await this.page!.screenshot() as Buffer
}

/**
* Get all cookies from browser context
* This includes HTTP_ONLY cookies that can't be accessed via document.cookie
*/
async getAllCookies(): Promise<Array<{
name: string
value: string
domain?: string
path?: string
expires?: number
httpOnly?: boolean
secure?: boolean
sameSite?: 'Strict' | 'Lax' | 'None'
}>> {
this.ensurePageExists()
const context = this.page!.context()
return await context.cookies()
}

/**
* Set cookies in browser context
* This can set HTTP_ONLY cookies that can't be set via document.cookie
*/
async setCookies(cookies: Array<{
name: string
value: string
domain?: string
path?: string
expires?: number
httpOnly?: boolean
secure?: boolean
sameSite?: 'Strict' | 'Lax' | 'None'
}>): Promise<void> {
this.ensurePageExists()
const context = this.page!.context()
// Format cookies correctly for Playwright
const formattedCookies = cookies.map((cookie) => {
// Ensure domain is set for Twitter
if (!cookie.domain) {
cookie.domain = '.twitter.com'
}
// Ensure path is set
if (!cookie.path) {
cookie.path = '/'
}
return cookie
})

await context.addCookies(formattedCookies)
}

/**
* Close session
*/
Expand Down
26 changes: 3 additions & 23 deletions services/twitter-services/src/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,28 +47,8 @@ export interface Config {
* Default configuration
*/
export function getDefaultConfig(): Config {
// Parse cookies from environment variable if provided
let cookiesFromEnv: Record<string, string> | undefined

if (process.env.TWITTER_COOKIES) {
// Try to parse as JSON first
try {
cookiesFromEnv = JSON.parse(process.env.TWITTER_COOKIES)
}
catch {
// If JSON parsing fails, treat as document.cookie format string
cookiesFromEnv = process.env.TWITTER_COOKIES
.split(';')
.map(v => v.split('='))
.reduce((acc, v) => {
// Skip empty values
if (v.length < 2)
return acc
acc[decodeURIComponent(v[0].trim())] = decodeURIComponent(v[1].trim())
return acc
}, {} as Record<string, string>)
}
}
// No longer parse cookies from environment variable
// The auth service will load cookies from session file instead

return {
browser: {
Expand All @@ -87,7 +67,7 @@ export function getDefaultConfig(): Config {
credentials: {
username: process.env.TWITTER_USERNAME || '',
password: process.env.TWITTER_PASSWORD || '',
cookies: cookiesFromEnv,
// Don't include cookies here, they will be loaded from session file
},
defaultOptions: {
timeline: {
Expand Down
Loading

0 comments on commit 499760a

Please sign in to comment.