Inngest’te İki Kez Çözmeden CAPTCHA Nasıl Çözülür

inngest captcha - How to Solve CAPTCHAs in Inngest Without Solving Twice

Inngest’te bir captcha çözümünün tek bir step.run çağrısının içinde yaşaması ve token’ın aynı çağrının içinde kullanılması gerekir. Inngest fonksiyonunuzu her step sınırında baştan yeniden çalıştırır; bu yüzden bir step’in dışında kalan her şey her seferinde yeniden çalışır. Tamamlanan bir step ise hafızaya alınır, yani birinci step’te çözülen bir token, süresi dolduktan çok sonra dördüncü step’te değişmeden yeniden oynatılır. Her iki kural da çözücüden değil, yürütme modelinden kaynaklanır.

Neye ihtiyacınız var

  • Genellikle /api/inngest adresinde bir serve uç noktası olan bir Inngest uygulaması ve yerel çalıştırmalar için Inngest Dev Server.
  • Bir Windows makinesinde çalışan CapSkip ve fonksiyonlarınızı sunan uygulamaya kurulmuş Node istemcisi.
  • Fonksiyonun içine gömülmek yerine olay verisiyle gelen sitekey ve sayfa URL’si.
  • Uygulama, çözücünün kendi makinesi dışında bir yere dağıtıldığında Server modu; dağıtımların çoğu böyledir. Bu, bağlantı ayarları altındaki tek bir ayardır.
# npm install capskip
npm install inngest capskip

Adım 1: çözüm neden bir step’in içinde olmalı

Inngest fonksiyonunuzu bir kez çalıştırıp baştan sona yürümez. Fonksiyonu çalıştırır, ilk step’te durur, sonucu kaydeder ve ardından fonksiyonu, önceki yürütmenin durumu ekli olarak baştan yeniden çağırır. Kendi dokümantasyonu ikinci geçişi açıkça anlatır: step’in kodu çalıştırılmaz, bunun yerine SDK sonucu step.run’ın dönüş değerine enjekte eder.

Model bundan ibarettir ve geri kalan her şeyden daha önemli tek bir sonucu vardır. Bir step’in dışında duran kod hafızaya alınmaz, dolayısıyla her çağrılışta çalışır. Dört step’li bir fonksiyon handler’ınızı dört kez çağırır; yani step’lerin üstüne yazılmış bir çözüm, tek bir fonksiyon çalıştırması için dört kez çalışır.

// npm install capskip
// WRONG. This line runs once per step boundary, so a
// four-step function solves four CAPTCHAs for one run.
const result = await solver.recaptcha(sitekey, pageUrl);

await step.run("fetch-form", async () => { /* ... */ });
await step.run("submit", async () => { /* ... */ });

Inngest kuralı doğrudan belirtir: veritabanı çağrıları veya API çağrıları gibi belirlenimci olmayan her mantık bir step.run çağrısının içine yerleştirilmelidir. Çözüm de bir API çağrısıdır, dolayısıyla bir step.run içine aittir. Sayaçlı bir çözücüyle bu hata kendini fatura olarak gösterir. Yerel bir çözücüyle ise dört katı iş ve üçü çöpe giden dört token olarak gösterir.

Adım 2: çözümü ve gönderimi aynı step içinde yapın

İkinci kural daha az belirgindir ve sonradan can yakar. Bir step tamamlandığında dönüş değeri saklanır ve sonraki her çağrıda yeniden oynatılır. step.run’dan dönen tüm veriler JSON olarak serileştirilir ve durum, step kimliğine göre hafızaya alınır.

Yani bir çözüm step’inden dönen token, saklanan bir dizedir. Sonraki çağrıda ve ondan sonraki çağrıda birebir aynı şekilde geri gelir ve o zamana kadar dakikalarca eskimiş olabilir. Bir reCAPTCHA token’ı yaklaşık iki dakika geçerlidir. Çözümle gönderim arasında duran her şey bu pencereyi yer: bir uyku, yavaş bir istek ya da geri çekilmeyle birkaç kez yeniden denenen bir step. Bunların herhangi biri süreyi tamamen tüketebilir.

// WRONG. The token is memoized here and replayed later,
// by which time it has almost certainly expired.
const token = await step.run("solve", () =>
  solver.recaptcha(sitekey, pageUrl).then((r) => r.code)
);
await step.sleep("settle", "5m");
await step.run("submit", () => postForm(token));

Onları bir arada tutun. Tek bir step hem çözer hem gönderir ve yalnızca fonksiyonun geri kalanının ihtiyaç duyduğu şeyi döndürür; bu da neredeyse hiçbir zaman token’ın kendisi değildir.

// RIGHT. The token is born and spent inside one step,
// so nothing expired is ever replayed.
const outcome = await step.run("solve-and-submit", async () => {
  const { code } = await solver.recaptcha(sitekey, pageUrl);
  const res = await postForm(pageUrl, code);
  return { status: res.status, id: res.id };
});

Bu geçerlilik penceresi, kuyruk işlerini zincirleyenleri yakalayan pencereyle aynıdır ve bir kez okumaya değer: bir reCAPTCHA token’ı ne kadar süre geçerli kalır.

Adım 3: yeniden denemeler ve hangi hatalar buna değer

Inngest, bir fonksiyonu veya bir step’i ilk denemeye ek olarak dört kez yeniden dener ve her step.run kendi bağımsız yeniden deneme sayacına sahiptir. Yeniden denemeler sarsıntılı üstel geri çekilme kullanır. retries seçeneğini sıfır ile yirmi arasında herhangi bir değere ayarlayabilirsiniz.

Çözüm ve gönderimi birleştiren bir step için bu varsayılanlar doğruya yakındır, çünkü yeniden denenen bir step kendi kodunu baştan çalıştırır ve dolayısıyla yeniden çözüm yapar. Devralınacak bayat bir token yoktur. Asıl ayarlanmaya değer olan, hangi hataların yeniden deneme alacağıdır.

Hangi istisnaNe anlama geldiğiYeniden denemeye değer mi?
NetworkExceptionCapSkip’e ulaşılamadı ya da yeniden başlıyorEvet. Yeniden denemeler tam da bunun için
TimeoutExceptionYoklama, istemcinin kendi üst sınırını aştıBelki bir kez. Dört kez nadiren değer
ApiExceptionAPI bir hata kodu döndürdüHata koduna bağlı. Genellikle hayır
ValidationExceptionParametreler yanlıştı ve yine yanlış olacakHayır. NonRetriableError fırlatın
import { NonRetriableError } from "inngest";
import { ValidationException } from "capskip";

const outcome = await step.run("solve-and-submit", async () => {
  try {
    const { code } = await solver.recaptcha(sitekey, pageUrl);
    return await postForm(pageUrl, code);
  } catch (err) {
    // A bad sitekey will be bad on all five attempts.
    if (err instanceof ValidationException) {
      throw new NonRetriableError(err.message);
    }
    throw err;   // everything else gets the normal backoff
  }
});

NonRetriableError, kalan yeniden denemeleri atlar ve fırlatıldığı step’i başarısız kılar; şanssız değil de hatalı biçimlendirilmiş bir istek için istediğiniz de budur. Çözücü size bozuk olduğunu değil meşgul olduğunu söylediyse, RetryAfterError varsayılan eğriyi kullanmak yerine gecikmeyi kendinizin belirlemesine izin verir.

Adım 4: fonksiyonun tamamı

Yukarıdakilerin hepsi tek dosyada. İstemcinin nerede oluşturulduğuna dikkat edin: handler’ın dışında, yani çağrı başına değil süreç başına bir kez oluşturuluyor ve çalıştırmaya özgü hiçbir durum tutmuyor.

// npm install capskip
import { Inngest } from "inngest";
import { CapSkip } from "capskip";

export const inngest = new Inngest({ id: "signup-worker" });

// CAPSKIP_HOST is 127.0.0.1 locally and the solver machine
// once this app is deployed anywhere else.
const solver = new CapSkip({
  host: process.env.CAPSKIP_HOST ?? "127.0.0.1",
  port: 8080,
});

export const submitSignup = inngest.createFunction(
  { id: "submit-signup", retries: 4 },
  { event: "signup/requested" },
  async ({ event, step }) => {
    const { sitekey, pageUrl, email } = event.data;

    // One step. The token never leaves it.
    const outcome = await step.run("solve-and-submit", async () => {
      const { code } = await solver.recaptcha(sitekey, pageUrl);
      return postSignup(pageUrl, email, code);
    });

    await step.run("record", () => saveResult(email, outcome));
    return outcome;
  }
);

Bu çağrı reCAPTCHA v2 içindir. Diğer türler de aynı biçimdedir: invisible veya enterprise değerini 1 yapın, ya da version değerini bir action ile birlikte v3 yapın, ya da bunun yerine turnstile veya geetest çağırın. Tüm yüzey için bakın: Node.js CAPTCHA çözücü sayfası.

Adım 5: kodunuz gerçekte nerede çalışıyor ve bu hangi modu gerektiriyor

Inngest, bu bölümü belirleyen bir açıdan çoğu barındırılan otomasyon platformundan farklıdır. Fonksiyonlarınız Inngest’in altyapısında çalışmaz. Inngest, uygulamanızı genellikle /api/inngest adresindeki bir serve uç noktası üzerinden HTTP ile çağırır ve kodunuz kendi uygulamanızın içinde yürütülür. Yani “bu, 127.0.0.1 adresine ulaşabilir mi” sorusunun Inngest ile hiçbir ilgisi yoktur; tamamen nereye dağıttığınızla ilgilidir.

Uygulamanın dağıtıldığı yerHangi bağlantı modu
Yerelde, Dev Server ile, CapSkip makinesindeLocal modu. 127.0.0.1 gerçekten doğru
Kendi sunucunuzda veya ağınızdaki bir sanal makinedeÇözücünün LAN adresiyle Server modu
Vercel veya Lambda gibi sunucusuz bir sağlayıcıdaStatik bir genel IP ve bir güvenlik duvarı kuralı ile Server modu
Uygulamanın yanında bir container içinde, çözücü başka yerdeServer modu. Container içindeki loopback, container’ın kendisidir

İki bağlantı modu vardır. Local, 127.0.0.1 adresine bağlanır ve yalnızca o cihaza yanıt verir. Server, ağ adresinize veya genel IP adresinize bağlanır; böylece başka bir makine, bir container sunucusu ya da sunucusuz bir fonksiyon aynı Windows makinesine API üzerinden ulaşabilir. İkisi de şurada bulunur: bağlantı ayarları. Server modu yalnızca çözücünün hangi adreste dinlediğini değiştirir. Donanım yine sizindir ve çözüm yine sayaçsızdır.

Bütün gün tetiklenen bir fonksiyonu çalıştırmayı makul kılan da işte kullanım sayacının olmamasıdır.

Karıştırılması kolay bir nokta: Inngest Cloud’un da sizin serve uç noktanıza ulaşması gerekir ve bu, çözücüden ayrı bir ağ meselesidir. Inngest’in halihazırda çağırabildiği bir dağıtım, otomatik olarak sizin LAN’ınızı çağırabilen bir dağıtım değildir.

Sık görülen hatalar ve anlamları

GördüğünüzNedenDüzeltme
Tek bir fonksiyon çalıştırması için birden fazla çözüm kaydedildiÇözüm step.run dışında, bu yüzden her sınırda tekrarlanıyorOnu bir step’in içine taşıyın
Sorunsuz çözülen bir token’da gönderim başarısız oluyorHafızaya alınmış bir token, süresi dolduktan sonra yeniden oynatıldıÇözümü ve gönderimi aynı step içinde yapın
Bir step dört kez yeniden deniyor ve aynı şekilde başarısız oluyorBir parametre hatası geçici sanılıyorValidationException için NonRetriableError fırlatın
Dağıtımdan sonra her çalıştırmada NetworkExceptionUygulama, çözücünün makinesinden başka yere taşındıServer modu ve dağıtımda CAPSKIP_HOST ayarı
Bir step sonucunun biçimi dağıtımlar arasında değiştiStep çıktısı JSON olarak serileştirilir ve kimliğe göre eşleştirilirDönüş değeri değiştiğinde step kimliğini yeniden adlandırın
Elle yazılmış bir yoklamadan çıkan CAPCHA_NOT_READYSonuç, tamamlanmadan önce okunduYoklamayı istemciye bırakın. Kendiliğinden geri çekilir
Bir ApiException içinde ERROR_WRONG_USER_KEYCAPSKIP_API_KEY dağıtım ortamında ayarlanmamışUygulamanın çalıştığı yerde ayarlayın, sonra yeniden dağıtın

Altıncı satır, istemciyi kullanmak yerine kendi yoklama döngüsünü yazanların ilk karşılaştığı satırdır. Bu yanıttaki yazım bizim tarafımızdaki bir hata değil, çünkü API gerçekten de onu bu şekilde döndürüyor. Ayrıntılı açıklaması şurada: CAPCHA_NOT_READY rehberi.

FAQ

Token’ı bir step’ten döndürüp sonra kullanabilir miyim?

Kullanabilirsiniz; testte çalışır, üretimde başarısız olur. Değer saklanır ve sonraki her çağrıda yeniden oynatılır; bu yüzden iki step arasına yavaş bir şey girdiği anda, fonksiyon beklerken süresi dolmuş bir token gönderiyor olursunuz. Çözümü ve token’ı tüketen şeyi tek bir step’te tutun ve kimlik bilgisini değil sonucu döndürün.

Yeniden deneme yeni bir CAPTCHA mı çözer, yoksa eskisini mi kullanır?

Yenisini çözer. Başarısız olan bir step hafızaya alınmaz; bu yüzden yeniden deneme, içindeki kodu baştan çalıştırır ve buna çözüm çağrısı da dahildir. Her step.run kendi bağımsız yeniden deneme sayacını tutar, dolayısıyla kararsız bir step diğerlerinin bütçesini harcamaz. Birleşik step’in yeniden denenmesinin güvenli, ayrılmış olanınkinin ise güvensiz olmasının nedeni tam olarak budur.

Uygulamam Vercel’de. Masamdaki bir çözücüye yine de ulaşabilir mi?

Evet, Server modu ile. Fonksiyon Vercel’in korumalı alanında çalışır, dolayısıyla oradaki loopback sizin makineniz değil o korumalı alandır. CapSkip’i bağlantı ayarları altından genel IP adresinize bağlayın, önüne yalnızca beklediğiniz trafiğe izin veren bir güvenlik duvarı kuralı koyun ve proje ortamında CAPSKIP_HOST değerini ayarlayın. Adresin ayağınızın altından kaymaması için statik bir genel IP önerilir.

Bunu Temporal’da yapmaktan farkı nedir?

İkisi de dayanıklı yürütmedir ve ikisi de farklı yollardan aynı kurala varır. Temporal, bir workflow’u sizin çalıştırdığınız bir worker içinde olay geçmişinden yeniden oynatır; yani kural belirlenimcilikten gelir. Inngest ise HTTP uç noktanızı yeniden çağırır ve hafızaya alınmış step sonuçlarını enjekte eder; yani kural, yeniden oynatma ile eskiyen bir token’ın birleşiminden gelir. Temporal sürümü şurada ele alınıyor: Temporal workflow rehberi.

Kısa özet

Çözümü step.run içine koyun, asla üstüne değil, çünkü bir step’in dışında kalan her şey her step sınırında yeniden çalışır. Çözümü ve token’ı harcayan şeyi aynı step içine koyun, çünkü tamamlanan bir step hafızaya alınıp yeniden oynatılır ve bir token yalnızca yaklaşık iki dakika yaşar. Varsayılan yeniden denemeleri olduğu gibi bırakın, ancak parametre hataları için NonRetriableError fırlatın. CAPSKIP_HOST değerini ortamdan alın ve uygulama çözücünün kendi makinesinde değilse CapSkip’i Server modunda çalıştırın.

Fonksiyona eşzamanlılık sınırı koymadan önce tartmaya değer bir nokta: CapSkip, captcha atlatma aracı olarak zaten sahip olduğunuz donanım üzerinde çalışır; yani aynı anda kaç çalıştırmanın çözüm yapabileceğinin tek tavanı bir bakiye değil, o makinedir.