Spaces:
Sleeping
Sleeping
| /** | |
| * Windsurf direct login — Firebase auth + Codeium registration. | |
| * Supports proxy tunneling and fingerprint randomization. | |
| */ | |
| import http from 'http'; | |
| import https from 'https'; | |
| import { log } from '../config.js'; | |
| const FIREBASE_API_KEY = 'AIzaSyDsOl-1XpT5err0Tcnx8FFod1H8gVGIycY'; | |
| const FIREBASE_AUTH_URL = `https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=${FIREBASE_API_KEY}`; | |
| const FIREBASE_REFRESH_URL = `https://securetoken.googleapis.com/v1/token?key=${FIREBASE_API_KEY}`; | |
| const CODEIUM_REGISTER_URL = 'https://api.codeium.com/register_user/'; | |
| // ─── Fingerprint randomization ──────────────────────────── | |
| const OS_VERSIONS = [ | |
| 'Windows NT 10.0; Win64; x64', | |
| 'Windows NT 10.0; WOW64', | |
| 'Macintosh; Intel Mac OS X 10_15_7', | |
| 'Macintosh; Intel Mac OS X 11_6_0', | |
| 'Macintosh; Intel Mac OS X 12_3_1', | |
| 'Macintosh; Intel Mac OS X 13_4_1', | |
| 'Macintosh; Intel Mac OS X 14_2_1', | |
| 'X11; Linux x86_64', | |
| 'X11; Ubuntu; Linux x86_64', | |
| ]; | |
| const CHROME_VERSIONS = [ | |
| '120.0.0.0', '121.0.0.0', '122.0.0.0', '123.0.0.0', '124.0.0.0', | |
| '125.0.0.0', '126.0.0.0', '127.0.0.0', '128.0.0.0', '129.0.0.0', | |
| '130.0.0.0', '131.0.0.0', '132.0.0.0', '133.0.0.0', '134.0.0.0', | |
| ]; | |
| const ACCEPT_LANGUAGES = [ | |
| 'en-US,en;q=0.9', 'en-GB,en;q=0.9', 'zh-TW,zh;q=0.9,en;q=0.8', | |
| 'zh-CN,zh;q=0.9,en;q=0.8', 'ja,en-US;q=0.9,en;q=0.8', | |
| 'ko,en-US;q=0.9,en;q=0.8', 'de,en-US;q=0.9,en;q=0.8', | |
| 'fr,en-US;q=0.9,en;q=0.8', 'es,en-US;q=0.9,en;q=0.8', | |
| 'pt-BR,pt;q=0.9,en;q=0.8', | |
| ]; | |
| function pick(arr) { return arr[Math.floor(Math.random() * arr.length)]; } | |
| function generateFingerprint() { | |
| const os = pick(OS_VERSIONS); | |
| const chromeVer = pick(CHROME_VERSIONS); | |
| const major = chromeVer.split('.')[0]; | |
| const ua = `Mozilla/5.0 (${os}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVer} Safari/537.36`; | |
| return { | |
| 'User-Agent': ua, | |
| 'Accept-Language': pick(ACCEPT_LANGUAGES), | |
| 'Accept': 'application/json, text/plain, */*', | |
| 'Accept-Encoding': 'identity', | |
| 'sec-ch-ua': `"Chromium";v="${major}", "Google Chrome";v="${major}", "Not-A.Brand";v="99"`, | |
| 'sec-ch-ua-mobile': '?0', | |
| 'sec-ch-ua-platform': os.includes('Windows') ? '"Windows"' : os.includes('Mac') ? '"macOS"' : '"Linux"', | |
| 'Sec-Fetch-Dest': 'empty', | |
| 'Sec-Fetch-Mode': 'cors', | |
| 'Sec-Fetch-Site': 'cross-site', | |
| 'Origin': 'https://windsurf.com', | |
| 'Referer': 'https://windsurf.com/', | |
| }; | |
| } | |
| // ─── Proxy tunnel (HTTP CONNECT) ────────────────────────── | |
| function createProxyTunnel(proxy, targetHost, targetPort) { | |
| return new Promise((resolve, reject) => { | |
| const proxyHost = proxy.host.replace(/:\d+$/, ''); | |
| const proxyPort = proxy.port || 8080; | |
| const authHeader = proxy.username | |
| ? `Proxy-Authorization: Basic ${Buffer.from(`${proxy.username}:${proxy.password || ''}`).toString('base64')}\r\n` | |
| : ''; | |
| const connectReq = http.request({ | |
| host: proxyHost, | |
| port: proxyPort, | |
| method: 'CONNECT', | |
| path: `${targetHost}:${targetPort}`, | |
| headers: { | |
| Host: `${targetHost}:${targetPort}`, | |
| ...(proxy.username ? { 'Proxy-Authorization': `Basic ${Buffer.from(`${proxy.username}:${proxy.password || ''}`).toString('base64')}` } : {}), | |
| }, | |
| }); | |
| connectReq.on('connect', (res, socket) => { | |
| if (res.statusCode === 200) { | |
| resolve(socket); | |
| } else { | |
| socket.destroy(); | |
| reject(new Error(`Proxy CONNECT failed: ${res.statusCode}`)); | |
| } | |
| }); | |
| connectReq.on('error', (err) => reject(new Error(`Proxy connection error: ${err.message}`))); | |
| connectReq.setTimeout(15000, () => { connectReq.destroy(); reject(new Error('Proxy connection timeout')); }); | |
| connectReq.end(); | |
| }); | |
| } | |
| // ─── HTTPS request with optional proxy ──────────────────── | |
| function httpsRequest(url, opts, postData, proxy) { | |
| return new Promise(async (resolve, reject) => { | |
| const parsed = new URL(url); | |
| const requestOpts = { | |
| hostname: parsed.hostname, | |
| port: 443, | |
| path: parsed.pathname + parsed.search, | |
| method: opts.method || 'POST', | |
| headers: opts.headers || {}, | |
| }; | |
| const handleResponse = (res) => { | |
| const bufs = []; | |
| res.on('data', d => bufs.push(d)); | |
| res.on('end', () => { | |
| const raw = Buffer.concat(bufs).toString('utf8'); | |
| try { | |
| resolve({ status: res.statusCode, data: JSON.parse(raw) }); | |
| } catch { | |
| reject(new Error(`Parse error (status ${res.statusCode}, encoding ${res.headers['content-encoding'] || 'identity'}): ${raw.slice(0, 200)}`)); | |
| } | |
| }); | |
| res.on('error', reject); | |
| }; | |
| try { | |
| let req; | |
| if (proxy && proxy.host) { | |
| const socket = await createProxyTunnel(proxy, parsed.hostname, 443); | |
| requestOpts.socket = socket; | |
| requestOpts.agent = false; | |
| req = https.request(requestOpts, handleResponse); | |
| } else { | |
| req = https.request(requestOpts, handleResponse); | |
| } | |
| req.on('error', (err) => reject(new Error(`Request error: ${err.message}`))); | |
| req.setTimeout(30000, () => { req.destroy(); reject(new Error('Request timeout')); }); | |
| if (postData) req.write(postData); | |
| req.end(); | |
| } catch (err) { | |
| reject(err); | |
| } | |
| }); | |
| } | |
| // ─── Login flow ─────────────────────────────────────────── | |
| /** | |
| * Full Windsurf login: Firebase auth → Codeium register → API key. | |
| * @param {string} email | |
| * @param {string} password | |
| * @param {object} [proxy] - { host, port, username, password } | |
| * @returns {{ apiKey, name, email, idToken }} | |
| */ | |
| export async function windsurfLogin(email, password, proxy = null) { | |
| const fingerprint = generateFingerprint(); | |
| log.info(`Windsurf login: ${email} fp=${fingerprint['User-Agent'].slice(0, 40)}... proxy=${proxy?.host || 'none'}`); | |
| // Step 1: Firebase sign in | |
| const firebaseBody = JSON.stringify({ | |
| email, | |
| password, | |
| returnSecureToken: true, | |
| }); | |
| const fbHeaders = { | |
| ...fingerprint, | |
| 'Content-Type': 'application/json', | |
| 'Content-Length': Buffer.byteLength(firebaseBody), | |
| }; | |
| const fbRes = await httpsRequest(FIREBASE_AUTH_URL, { method: 'POST', headers: fbHeaders }, firebaseBody, proxy); | |
| if (fbRes.data.error) { | |
| const msg = fbRes.data.error.message || 'Unknown Firebase error'; | |
| const oauthHint = '若你用 Google/GitHub 注册的 Windsurf 账号 此处密码登录不适用 请用页面顶部的 Google / GitHub 登录按钮 或访问 https://windsurf.com/show-auth-token 复制 Auth Token 后在「账号管理」页手动添加'; | |
| const friendly = { | |
| 'EMAIL_NOT_FOUND': `该邮箱未注册邮箱密码登录方式(${oauthHint})`, | |
| 'INVALID_PASSWORD': `密码错误(${oauthHint})`, | |
| 'INVALID_LOGIN_CREDENTIALS': `邮箱或密码错误(${oauthHint})`, | |
| 'USER_DISABLED': '账号已被停用', | |
| 'TOO_MANY_ATTEMPTS_TRY_LATER': '尝试太多次 请稍后再试', | |
| 'INVALID_EMAIL': '邮箱格式错误', | |
| }[msg] || msg; | |
| const err = new Error(`Firebase 登入失败: ${friendly}`); | |
| err.firebaseCode = msg; | |
| err.isAuthFail = ['EMAIL_NOT_FOUND', 'INVALID_PASSWORD', 'INVALID_LOGIN_CREDENTIALS'].includes(msg); | |
| throw err; | |
| } | |
| const idToken = fbRes.data.idToken; | |
| if (!idToken) throw new Error('Firebase 回應缺少 idToken'); | |
| log.info(`Firebase login OK: ${email}, UID=${fbRes.data.localId}`); | |
| // Step 2: Register with Codeium to get API key | |
| const regBody = JSON.stringify({ firebase_id_token: idToken }); | |
| const regHeaders = { | |
| ...fingerprint, | |
| 'Content-Type': 'application/json', | |
| 'Content-Length': Buffer.byteLength(regBody), | |
| }; | |
| const regRes = await httpsRequest(CODEIUM_REGISTER_URL, { method: 'POST', headers: regHeaders }, regBody, proxy); | |
| if (regRes.status >= 400 || !regRes.data.api_key) { | |
| throw new Error(`Codeium 註冊失敗: ${JSON.stringify(regRes.data).slice(0, 200)}`); | |
| } | |
| log.info(`Codeium register OK: ${email} → key=${regRes.data.api_key.slice(0, 12)}...`); | |
| return { | |
| apiKey: regRes.data.api_key, | |
| name: regRes.data.name || email, | |
| email, | |
| idToken, | |
| refreshToken: fbRes.data.refreshToken || '', | |
| apiServerUrl: regRes.data.api_server_url || '', | |
| }; | |
| } | |
| /** | |
| * Refresh a Firebase ID token using a stored refresh token. | |
| * Returns a new { idToken, refreshToken, expiresIn } or throws. | |
| * | |
| * @param {string} refreshToken | |
| * @param {object} [proxy] | |
| * @returns {Promise<{idToken: string, refreshToken: string, expiresIn: number}>} | |
| */ | |
| export async function refreshFirebaseToken(refreshToken, proxy = null) { | |
| if (!refreshToken) throw new Error('No refresh token available'); | |
| const postBody = `grant_type=refresh_token&refresh_token=${encodeURIComponent(refreshToken)}`; | |
| const headers = { | |
| 'Content-Type': 'application/x-www-form-urlencoded', | |
| 'Content-Length': Buffer.byteLength(postBody), | |
| 'Referer': 'https://windsurf.com/', | |
| 'Origin': 'https://windsurf.com', | |
| 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/130.0.0.0 Safari/537.36', | |
| }; | |
| const res = await httpsRequest(FIREBASE_REFRESH_URL, { method: 'POST', headers }, postBody, proxy); | |
| if (res.data?.error) { | |
| const msg = res.data.error.message || res.data.error.code || 'Unknown error'; | |
| throw new Error(`Firebase token refresh failed: ${msg}`); | |
| } | |
| const newIdToken = res.data?.id_token || res.data?.idToken; | |
| const newRefreshToken = res.data?.refresh_token || res.data?.refreshToken || refreshToken; | |
| const expiresIn = parseInt(res.data?.expires_in || res.data?.expiresIn || '3600', 10); | |
| if (!newIdToken) { | |
| throw new Error(`Firebase token refresh: no idToken in response: ${JSON.stringify(res.data).slice(0, 200)}`); | |
| } | |
| log.info(`Firebase token refreshed, expires in ${expiresIn}s`); | |
| return { idToken: newIdToken, refreshToken: newRefreshToken, expiresIn }; | |
| } | |
| /** | |
| * Re-register with Codeium using a refreshed Firebase token. | |
| * Returns a fresh API key (may be the same key if unchanged). | |
| * | |
| * @param {string} idToken - fresh Firebase ID token | |
| * @param {object} [proxy] | |
| * @returns {Promise<{apiKey: string, name: string}>} | |
| */ | |
| export async function reRegisterWithCodeium(idToken, proxy = null) { | |
| const fingerprint = generateFingerprint(); | |
| const regBody = JSON.stringify({ firebase_id_token: idToken }); | |
| const regHeaders = { | |
| ...fingerprint, | |
| 'Content-Type': 'application/json', | |
| 'Content-Length': Buffer.byteLength(regBody), | |
| }; | |
| const regRes = await httpsRequest(CODEIUM_REGISTER_URL, { method: 'POST', headers: regHeaders }, regBody, proxy); | |
| if (regRes.status >= 400 || !regRes.data.api_key) { | |
| throw new Error(`Codeium re-registration failed: ${JSON.stringify(regRes.data).slice(0, 200)}`); | |
| } | |
| return { | |
| apiKey: regRes.data.api_key, | |
| name: regRes.data.name || '', | |
| }; | |
| } | |