Files
cannaiq/backend/src/platforms/treez/queries.ts
Kelly 023cfc127f fix(preflight): Apply stored fingerprint to task browser
- Add WorkerFingerprint interface with timezone, city, state, ip, locale
- Store fingerprint in TaskWorker after preflight passes
- Pass fingerprint through TaskContext to handlers
- Apply timezone via CDP and locale via Accept-Language header
- Ensures browser fingerprint matches proxy IP location

This fixes anti-detect detection where timezone/locale mismatch
with proxy IP was getting blocked by Cloudflare.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-13 16:40:52 -07:00

133 lines
3.4 KiB
TypeScript

/**
* Treez High-Level Query Functions
*
* Wraps the low-level client methods with business logic
* for common operations like product fetching.
*/
import {
startSession,
endSession,
fetchAllProducts,
buildMenuUrl,
TreezProductRaw,
TreezStoreInfo,
} from './client';
// ============================================================
// PRODUCT OPERATIONS
// ============================================================
export interface FetchProductsResult {
store: TreezStoreInfo;
products: TreezProductRaw[];
totalCaptured: number;
scrollCount: number;
}
/**
* Fetch all products from a Treez store
*
* @param storeId - Treez store ID (slug like "best")
* @returns Products and store data captured from the page
*/
export async function fetchProductsByStoreId(storeId: string): Promise<FetchProductsResult> {
try {
await startSession(storeId);
const { products, storeInfo, scrollCount } = await fetchAllProducts(storeId);
return {
store: storeInfo,
products,
totalCaptured: products.length,
scrollCount,
};
} finally {
await endSession();
}
}
/**
* Fetch products from a Treez menu URL
* Extracts store ID from URL and fetches products
*
* @param menuUrl - Full Treez menu URL
* @returns Products and store data
*/
export async function fetchProductsFromUrl(menuUrl: string): Promise<FetchProductsResult> {
const storeId = extractStoreIdFromUrl(menuUrl);
if (!storeId) {
throw new Error(`Could not extract store ID from URL: ${menuUrl}`);
}
return fetchProductsByStoreId(storeId);
}
// ============================================================
// STORE OPERATIONS
// ============================================================
/**
* Extract store ID from a Treez URL
*
* Supports formats:
* - https://best.treez.io/onlinemenu/
* - https://shop.bestdispensary.com/ (resolves to best.treez.io)
*
* @param url - Treez menu URL
* @returns Store ID or null if not found
*/
export function extractStoreIdFromUrl(url: string): string | null {
// Pattern 1: {storeId}.treez.io
const treezMatch = url.match(/https?:\/\/([^.]+)\.treez\.io/i);
if (treezMatch) {
return treezMatch[1];
}
// Pattern 2: Custom domain - would need to follow redirect
// For now, return null and let the caller handle it
return null;
}
/**
* Validate that a store ID exists and is accessible
*
* @param storeId - Treez store ID
* @returns True if store is accessible
*/
export async function validateStoreId(storeId: string): Promise<boolean> {
try {
await startSession(storeId);
const { page } = (await import('./client')).getCurrentSession()!;
const url = buildMenuUrl(storeId);
await page.goto(url, {
waitUntil: 'domcontentloaded',
timeout: 30000,
});
// Check if we got a valid page (not 404)
const title = await page.title();
const is404 = title.toLowerCase().includes('404') || title.toLowerCase().includes('not found');
return !is404;
} catch {
return false;
} finally {
await endSession();
}
}
// ============================================================
// UTILITY FUNCTIONS
// ============================================================
/**
* Get the direct Treez menu URL for a store
*/
export function getMenuUrl(storeId: string, customerType: 'ADULT' | 'MEDICAL' = 'ADULT'): string {
return buildMenuUrl(storeId, customerType);
}