Trigger.dev बैकग्राउंड टास्क में कैप्चा कैसे हल करें

Trigger.dev में कैप्चा सॉल्व पहली ही डिप्लॉय पर फेल होता है, और वजह कोड से जुड़ी हुई नहीं होती। Trigger.dev आपके ऐप्लिकेशन को कॉल नहीं करता। वह आपके टास्क को एक Docker इमेज में बनाता है और अपनी ही मशीनों पर चलाता है, इसलिए टास्क के अंदर 127.0.0.1 वही कंटेनर है, वह मशीन नहीं जिस पर आपका सॉल्वर है। Server mode इसे एक ही सेटिंग से ठीक कर देता है। दूसरी सही करने वाली चीज़ है maxDuration, क्योंकि सॉल्वर को पोल करने में लगा पूरा समय उसी बजट में गिना जाता है।
आपको क्या चाहिए
- एक Trigger.dev प्रोजेक्ट, जिसमें SDK इंस्टॉल हो और रूट पर trigger.config.ts मौजूद हो।
- CapSkip किसी Windows मशीन पर चल रहा हो, और Node क्लाइंट उसी प्रोजेक्ट में जोड़ा गया हो ताकि वह डिप्लॉय होने वाली इमेज में भी पहुँचे।
- sitekey और पेज URL हार्डकोड होने के बजाय टास्क के payload पर आएँ, ताकि एक ही टास्क हर फ़ॉर्म के काम आए।
- Server mode, और सॉल्वर के लिए एक ऐसा पता जहाँ तक पहुँचा जा सके। Trigger.dev Cloud पर यह वैकल्पिक नहीं है, और वजह स्टेप 1 में है।
# npm install capskip npm install @trigger.dev/sdk capskip
स्टेप 1: टास्क असल में चलता कहाँ है, और उसके लिए कौन सा मोड चाहिए
ज़्यादातर प्लेटफ़ॉर्म गाइड इस बात को आख़िर के लिए छोड़ सकते हैं। यह गाइड नहीं छोड़ सकती, क्योंकि इसी से तय होता है कि बाकी कुछ चलेगा या नहीं। Trigger.dev अपने डिप्लॉय को खुद साफ़ शब्दों में बताता है: कोड को एक Docker इमेज में पैक करके आपके Trigger.dev इंस्टेंस पर डिप्लॉय किया जाता है, और हर रन उनके ही संभाले हुए एक अलग-थलग एनवायरनमेंट में चलता है। आपका टास्क वहाँ नहीं चल रहा जहाँ आपका एडिटर है।
इसलिए run फ़ंक्शन के अंदर loopback उसी टास्क कंटेनर तक पहुँचता है। वहाँ पोर्ट 8080 पर कुछ भी सुन नहीं रहा होता, और नतीजा होता है connection refused, जो हर कोशिश पर NetworkException बनकर सामने आता है।
कनेक्शन के दो मोड हैं। Local mode 127.0.0.1 से बँधता है और सिर्फ़ उसी डिवाइस को जवाब देता है, जो तब सही है जब आपका ऑटोमेशन और सॉल्वर एक ही मशीन पर हों। Server mode आपके नेटवर्क पते या पब्लिक IP से बँधता है, ताकि कोई दूसरी मशीन, कोई VPS या कोई होस्टेड प्लेटफ़ॉर्म उसी Windows मशीन तक API के ज़रिए पहुँच सके। Server mode सिर्फ़ यह बदलता है कि सॉल्वर किस पते पर सुनता है। हार्डवेयर अब भी आपका ही है और इस पर अब भी प्रति-हल कोई शुल्क नहीं लगता। ये दोनों मोड यहाँ मिलते हैं: कनेक्शन सेटिंग्स.
| आप Trigger.dev कैसे चलाते हैं | कौन-सा कनेक्शन मोड |
|---|---|
| dev CLI, उसी CapSkip मशीन पर | Local mode। 127.0.0.1 सचमुच सही है |
| सेल्फ़ होस्टेड, आपके अपने नेटवर्क पर | सॉल्वर के LAN पते के साथ Server mode |
| Trigger.dev Cloud | स्टैटिक पब्लिक IP और एक फ़ायरवॉल नियम के साथ Server mode |
तीसरी पंक्ति के लिए एक स्थिर पब्लिक IP का इंतज़ाम कर लेना ठीक रहता है, ताकि चलती हुई डिप्लॉयमेंट के नीचे से पता न बदल जाए। पते को सोर्स कोड में रखने के बजाय किसी एनवायरनमेंट वेरिएबल में रखें, क्योंकि dev CLI और डिप्लॉय किए गए टास्क को अलग-अलग वैल्यू चाहिए।
// npm install capskip
import { CapSkip } from "capskip";
// 127.0.0.1 while the dev CLI runs it on your machine,
// the solver's reachable address once it is deployed.
export const solver = new CapSkip({
host: process.env.CAPSKIP_HOST ?? "127.0.0.1",
port: Number(process.env.CAPSKIP_PORT ?? 8080),
recaptchaTimeout: 120,
});स्टेप 2: maxDuration को सॉल्व का समय भी समेटना होगा
Trigger.dev हर रन को maxDuration के हिसाब से सेकंड में मापता है, और दस्तावेज़ों में इसका न्यूनतम 5 बताया गया है। जो समय इसमें नहीं गिना जाता, उसे साफ़ नाम लेकर बताया गया है: wait.for, triggerAndWait और batchTriggerAndWait में बीता समय नहीं गिना जाता। await किया गया HTTP अनुरोध उस सूची में नहीं है, इसलिए क्लाइंट सॉल्वर को पोल करने में जितने सेकंड लगाता है, वे सब पूरे गिने जाते हैं।
यह इसलिए मायने रखता है क्योंकि सॉल्व करने में ज़्यादातर समय इंतज़ार का ही होता है। reCAPTCHA v2 का एक सॉल्व आम तौर पर 15 से 45 सेकंड लेता है, और भरा हुआ क्यू इसे और आगे खींच सकता है। maxDuration तय करते वक़्त सॉल्व को भी बजट में रखें, सिर्फ़ बाकी टास्क को नहीं।
// trigger.config.ts sets the project-wide floor.
import { defineConfig } from "@trigger.dev/sdk";
export default defineConfig({
project: "proj_YOUR_PROJECT_REF",
maxDuration: 60,
});
// A solving task overrides it. 60s is not enough on its own:
// the solve alone can use most of that budget.
export const solveAndSubmit = task({
id: "solve-and-submit",
maxDuration: 300,
run: async (payload) => { /* ... */ },
});क्लाइंट की अपनी सीमा इससे नीचे रखें, ताकि क्लाइंट पहले हार माने और ऐसा कुछ फेंके जिसे आप पढ़ सकें। Node क्लाइंट में reCAPTCHA, Turnstile और GeeTest के लिए डिफ़ॉल्ट 300 सेकंड है, और इमेज कैप्चा के लिए 120 सेकंड। maxDuration 300 के नीचे reCAPTCHA का क्लाइंट टाइमआउट घटाकर 120 करने से इतनी जगह बच जाती है कि टास्क टोकन के साथ जो करना है वह कर सके।
एक जाल का नाम लेना ज़रूरी है। चूँकि wait.for maxDuration में नहीं गिना जाता, यह टास्क रोकने का मुफ़्त तरीका लगता है। टोकन के लिए यह मुफ़्त नहीं है। reCAPTCHA टोकन असल घड़ी के करीब दो मिनट तक ही चलता है, और प्लेटफ़ॉर्म की घड़ी अलग है, टोकन की घड़ी अलग। सॉल्व और उसका इस्तेमाल कोड के एक ही हिस्से में रखें, और reCAPTCHA token कितनी देर चलता है यह एक बार पढ़ लें, उसके बाद ही इसके इर्द गिर्द डिज़ाइन तय करें।
चरण 3: retries, और किन errors को इसका हक़ है
टास्क डिफ़ॉल्ट रूप से तीन बार दोबारा कोशिश करते हैं, और उसका exponential backoff आप factor, minTimeoutInMs, maxTimeoutInMs और randomize से तय करते हैं। CLI से बना हुआ कॉन्फ़िग DEV एनवायरनमेंट में retry बंद रखता है, इसीलिए जो टास्क production में दोबारा कोशिश करता है वह आपकी मशीन पर तुरंत फेल होता दिखता है।
दोबारा चलाया गया टास्क पूरे run फ़ंक्शन को फिर से चलाता है, इसलिए वह दोबारा सॉल्व भी करता है। विरासत में कोई बासी टोकन नहीं मिलता, और इसी वजह से डिफ़ॉल्ट ठीक बैठते हैं। तय करने लायक बात यह है कि किन नाकामियों को दोबारा कोशिश मिलनी ही चाहिए।
| कौन-सा exception | इसका क्या मतलब है | retry के लायक़? |
|---|---|---|
| NetworkException | CapSkip तक पहुँच नहीं बनी, या वह रीस्टार्ट हो रहा है | हाँ। retries इसी के लिए हैं |
| TimeoutException | Polling client की अपनी अधिकतम सीमा से आगे निकल गई | शायद एक बार। तीन बार तो कम ही ठीक बैठता है |
| ApiException | API ने कोई error कोड लौटाया | एरर कोड पर निर्भर है। आमतौर पर नहीं |
| ValidationException | पैरामीटर ग़लत थे और फिर से ग़लत ही रहेंगे | नहीं। AbortTaskRunError फेंकें |
AbortTaskRunError उस कोशिश को फेल करके retry बंद कर देता है, और गलत बने अनुरोध के साथ यही होना चाहिए। गलत sitekey तीसरी कोशिश में सही नहीं हो जाएगा, और 90 सेकंड की तीन कोशिशों का मतलब है साढ़े चार मिनट सिर्फ़ यही साबित करने में।
import { task, AbortTaskRunError } from "@trigger.dev/sdk";
import { ValidationException } from "capskip";
try {
const { code } = await solver.recaptcha(sitekey, pageUrl);
return await postForm(pageUrl, code);
} catch (err) {
// Wrong parameters will be wrong on all three attempts.
if (err instanceof ValidationException) {
throw new AbortTaskRunError(err.message);
}
throw err; // everything else takes the normal backoff
}स्टेप 4: पूरा टास्क
ऊपर की सारी बातें एक ही फ़ाइल में। क्लाइंट मॉड्यूल स्कोप पर बनाया जाता है, ताकि वह हर रन पर नहीं बल्कि हर कंटेनर पर एक बार बने, और वह किसी रन की अपनी state नहीं रखता।
// npm install capskip
import { task, AbortTaskRunError } from "@trigger.dev/sdk";
import { CapSkip, ValidationException } from "capskip";
const solver = new CapSkip({
host: process.env.CAPSKIP_HOST ?? "127.0.0.1",
port: 8080,
recaptchaTimeout: 120,
});
export const submitSignup = task({
id: "submit-signup",
maxDuration: 300,
retry: { maxAttempts: 3, minTimeoutInMs: 2000 },
queue: { concurrencyLimit: 10 },
run: async (payload, { ctx }) => {
const { sitekey, pageUrl, email } = payload;
try {
// Solve and submit together. The token is short lived.
const { code } = await solver.recaptcha(sitekey, pageUrl);
const res = await postSignup(pageUrl, email, code);
return { status: res.status, runId: ctx.run.id };
} catch (err) {
if (err instanceof ValidationException) {
throw new AbortTaskRunError(err.message);
}
throw err;
}
},
});वह कॉल reCAPTCHA v2 है। बाक़ी प्रकार भी उसी आकार के हैं: invisible या enterprise को 1 सेट करके भेजिए, या version को किसी action के साथ v3 सेट कीजिए, या उसकी जगह turnstile या geetest कॉल कीजिए। पूरा surface यहाँ है: Node.js कैप्चा सॉल्वर पेज.
स्टेप 5: कॉनकरेंसी, और असली सीमा कहाँ है
queue विकल्प तय करता है कि किसी टास्क के कितने रन एक साथ चल सकते हैं। प्रति-हल शुल्क लेने वाले सॉल्वर के साथ वह नंबर असल में ख़र्च पर लगाम होता है, और लोग इसीलिए इसे कम रखते हैं। यहाँ यह एक मशीन की क्षमता का सवाल है, इसलिए इसे उतना रखें जितना सॉल्वर और लक्ष्य साइट झेल सकें, न कि उतना जितना आपकी जेब झेल सके।
असल में दो ही चीज़ें इसकी सीमा तय करती हैं: CapSkip चलाने वाली Windows मशीन, और जिस साइट पर आप सबमिट कर रहे हैं वह rate limiting शुरू करने से पहले कितनी तेज़ी से अनुरोध स्वीकार करती है। दूसरी वजह आमतौर पर ज़्यादा कसी हुई होती है। कुछ भी किसी बैलेंस के पीछे कतार में नहीं लगता, और महीने के आख़िर में कुछ भी फेल नहीं होता।
export const submitSignup = task({
id: "submit-signup",
// Sized for the solver machine and the target site,
// not for a credit balance.
queue: { concurrencyLimit: 10 },
maxDuration: 300,
run: async (payload) => { /* ... */ },
});आम errors और उनका मतलब
| आप जो देखते हैं | कारण | फिक्स |
|---|---|---|
| dev CLI के साथ चलता है, डिप्लॉय होते ही NetworkException | डिप्लॉय किया गया टास्क एक कंटेनर है, इसलिए loopback वही कंटेनर है | Server mode चालू करें, और डिप्लॉय वाले एनवायरनमेंट में CAPSKIP_HOST सेट करें |
| सॉल्व के बीच में ही रन रोक दिया जाता है | maxDuration सॉल्व में लगने वाले समय से छोटा है | इसे टास्क पर, क्लाइंट के अपने टाइमआउट से ऊपर बढ़ाएँ |
| टास्क DEV में तुरंत फेल होता है पर production में दोबारा कोशिश करता है | बना हुआ कॉन्फ़िग DEV में retry बंद रखता है | यह अपेक्षित है। retry का व्यवहार डिप्लॉय वाले एनवायरनमेंट में जाँचें |
| तीन कोशिशें, एक जैसी नाकामी, कई मिनट बरबाद | पैरामीटर की ग़लती को अस्थायी मानकर चलाया जा रहा है | ValidationException पर AbortTaskRunError फेंकें |
| wait.for के बाद टोकन अस्वीकार हो जाता है | इंतज़ार maxDuration के लिए मुफ़्त है, टोकन के लिए नहीं | इंतज़ार के बाद, सबमिट करने से ठीक पहले सॉल्व करें |
| किसी ApiException के अंदर ERROR_WRONG_USER_KEY | डिप्लॉय वाले एनवायरनमेंट में CAPSKIP_API_KEY सेट नहीं है | इसे Trigger.dev के एनवायरनमेंट वेरिएबल में सेट करें, फिर दोबारा डिप्लॉय करें |
| हाथ से लिखे पोल से CAPCHA_NOT_READY | नतीजा पूरा होने से पहले ही पढ़ लिया गया | client को poll करने दें। वह ख़ुद ही धीमा होता जाता है |
यह आख़िरी रिस्पॉन्स वैसे ही लिखा जाता है जैसा दिखता है, और उसमें गायब अक्षर हमारी तरफ़ की गलती नहीं है, क्योंकि API सचमुच इसी वर्तनी में जवाब लौटाता है। इसकी पूरी व्याख्या यहाँ है: CAPCHA_NOT_READY गाइड.
FAQ
क्या Trigger.dev Cloud का टास्क मेरी डेस्क पर रखे सॉल्वर तक पहुँच सकता है?
हाँ, Server mode के साथ। टास्क उस कंटेनर में चलता है जिसे Trigger.dev संभालता है, इसलिए वहाँ loopback वही कंटेनर है। CapSkip को कनेक्शन सेटिंग्स में जाकर अपने पब्लिक IP से बाँधें, उसके आगे ऐसा फ़ायरवॉल नियम लगाएँ जो सिर्फ़ उम्मीद वाले पतों को आने दे, और Trigger.dev के एनवायरनमेंट वेरिएबल में CAPSKIP_HOST सेट करें। स्थिर पब्लिक IP लेने की सलाह है, ताकि पता आपके नीचे से खिसके नहीं।
क्या Trigger.dev को सेल्फ़ होस्ट करने से इसमें कुछ बदलता है?
इससे पता बदलता है, मॉडल नहीं। सेल्फ़ होस्टेड रन भी आपका कोड इंस्टेंस पर कंटेनरों में ही चलाते हैं, आपके ऐप को कॉल नहीं करते, इसलिए loopback अब भी कंटेनर ही है। फ़र्क़ यह है कि इंस्टेंस आमतौर पर आपके अपने नेटवर्क पर होता है, इसलिए Server mode पब्लिक पते के बजाय LAN पते का इस्तेमाल कर सकता है, और किसी फ़ायरवॉल नियम को इंटरनेट की तरफ़ मुँह करने की ज़रूरत नहीं पड़ती।
क्या सॉल्व को अपना अलग टास्क बनाना चाहिए, जिसे बाकी टास्क कॉल करें?
आमतौर पर नहीं। अलग करने का मतलब है कि टोकन एक टास्क की सीमा पार करता है और payload में तब तक पड़ा रहता है जब तक पैरेंट दोबारा शुरू नहीं होता, और एक्सपायर हो चुका टोकन ख़र्च करने का यही सबसे तेज़ रास्ता है। सॉल्व और टोकन का इस्तेमाल करने वाली चीज़, दोनों एक ही run फ़ंक्शन में रखें, और क्रेडेंशियल के बजाय नतीजा लौटाएँ। अलग टास्क सिर्फ़ तभी समझ आता है जब वह जो लौटाए वह टोकन हो ही नहीं।
Inngest में यही काम करने से यह कितना अलग है?
डिप्लॉयमेंट मॉडल बिल्कुल उलटा है, और इससे पूरा जवाब बदल जाता है। Inngest आपके ऐप्लिकेशन को HTTP पर कॉल करता है, इसलिए आपका कोड वहीं चलता है जहाँ आपने उसे डिप्लॉय किया, और कनेक्शन मोड आपकी अपनी होस्टिंग का सवाल बन जाता है। Trigger.dev आपका कोड अपनी मशीनों पर चलाता है, इसलिए क्लाउड वाले विकल्प में Server mode का फ़ैसला आपके लिए पहले ही हो चुका होता है। Inngest वाला तरीका, और वहाँ सॉल्व को एक ही step के अंदर क्यों रहना चाहिए, यह सब यहाँ है: Inngest गाइड.
संक्षेप में
CapSkip को Server mode में चलाएँ और Trigger.dev के एनवायरनमेंट में CAPSKIP_HOST सेट करें, क्योंकि डिप्लॉय किया गया टास्क एक कंटेनर है और वहाँ loopback वही कंटेनर है। टास्क को ऐसा maxDuration दें जो सॉल्व का समय भी समेट ले, क्योंकि किसी HTTP endpoint को पोल करना उन छूट वाले इंतज़ारों में नहीं आता। क्लाइंट का टाइमआउट उससे नीचे रखें। तीन डिफ़ॉल्ट retry को चलने दें, पर पैरामीटर की गलतियों पर AbortTaskRunError फेंकें। सॉल्व और सबमिट एक ही run फ़ंक्शन में करें, किसी इंतज़ार या टास्क सीमा के आर पार कभी नहीं।
- क्लाइंट के पीछे मौजूद कच्चे एंडपॉइंट यहाँ दस्तावेज़ित हैं: CapSkip API डॉक्यूमेंटेशन.
- checkbox चैलेंज को ख़ुद यहाँ समझाया गया है: reCAPTCHA v2 सॉल्वर पेज.
कॉनकरेंसी लिमिट चुनने से पहले यह तौल लेना ठीक रहेगा। CapSkip है एक कैप्चा सॉल्वर जो उसी हार्डवेयर पर चलता है जो पहले से आपका है, इसलिए आप जो नंबर चुनते हैं वह बजट का नहीं बल्कि क्षमता का फ़ैसला है।
