Caputchin
연동 가이드

프런트엔드 연동 예제

위젯은 표준 커스텀 요소라, 설치할 래퍼 없이 모든 프레임워크에서 동작합니다. 모양은 늘 같습니다: 패키지를 한 번 로드하고(클라이언트 측 연동), 폼 안에 <caputchin-widget sitekey="cpt_pub_...">을 렌더링하고, 방문자가 챌린지를 풀 때 그것이 주입하는 숨겨진 caputchin-token 필드를 읽습니다. 게임을 원하면 <caputchin-game ... game="caputchin/games/leaf-memory">로 바꾸세요.

아래 예제들은 각 프레임워크의 문법과 그 하나의 커스텀 요소 특이점에서만 다릅니다.

순수 HTML

주입된 필드는 평범한 폼 POST에 실려 가니, 따로 배선할 것이 없습니다:

<script src="https://cdn.jsdelivr.net/npm/@caputchin/widget@3/dist/widget.js"></script>

<form action="/contact" method="POST">
  <input name="email" type="email" required />
  <caputchin-widget sitekey="cpt_pub_..."></caputchin-widget>
  <button type="submit">Send</button>
</form>

React

시작할 때 한 번 임포트하고, 제출 시 FormData에서 필드를 읽으세요:

import "@caputchin/widget";

export function ContactForm() {
  async function handleSubmit(e) {
    e.preventDefault();
    const data = new FormData(e.currentTarget);
    await fetch("/api/contact", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ email: data.get("email"), token: data.get("caputchin-token") }),
    });
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="email" type="email" required />
      <caputchin-widget sitekey="cpt_pub_..." />
      <button type="submit">Send</button>
    </form>
  );
}

React와 함께 TypeScript를 쓴다면, JSX가 두 요소를 받아들이도록 한 번 선언하세요:

// caputchin.d.ts
import type { DetailedHTMLProps, HTMLAttributes } from "react";
declare module "react" {
  namespace JSX {
    interface IntrinsicElements {
      "caputchin-widget": DetailedHTMLProps<HTMLAttributes<HTMLElement> & { sitekey: string }, HTMLElement>;
      "caputchin-game": DetailedHTMLProps<HTMLAttributes<HTMLElement> & { sitekey: string; game?: string; games?: string }, HTMLElement>;
    }
  }
}

declare module "react" 형태는 React의 JSX에 한정됩니다. 순수 TypeScript에서 요소는 그냥 HTMLElement이며 선언이 필요 없고, 아래 다른 프레임워크들은 커스텀 요소를 저마다의 방식으로 타입 지정하니, 이 블록은 React 프로젝트에서만 필요합니다.

Vue

Vue 컴파일러에게 caputchin- 태그가 커스텀 요소임을 알려, 그것들을 컴포넌트로 해석하려 들지 않게 하세요:

// vite.config.js
export default {
  plugins: [vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith("caputchin-") } } })],
};
<script setup>
import "@caputchin/widget";
function onSubmit(e) {
  const data = new FormData(e.target);
  // send data.get("caputchin-token") to your backend
}
</script>

<template>
  <form @submit.prevent="onSubmit">
    <input name="email" type="email" required />
    <caputchin-widget sitekey="cpt_pub_..."></caputchin-widget>
    <button type="submit">Send</button>
  </form>
</template>

Svelte

Svelte는 커스텀 요소를 있는 그대로 렌더링하니, 설정이 필요 없습니다:

<script>
  import "@caputchin/widget";
  function onSubmit(e) {
    const data = new FormData(e.currentTarget);
    // send data.get("caputchin-token") to your backend
  }
</script>

<form on:submit|preventDefault={onSubmit}>
  <input name="email" type="email" required />
  <caputchin-widget sitekey="cpt_pub_..."></caputchin-widget>
  <button type="submit">Send</button>
</form>

Angular

모듈 또는 standalone 컴포넌트에 CUSTOM_ELEMENTS_SCHEMA를 더해 Angular가 알 수 없는 태그를 허용하게 하고, 패키지를 한 번 임포트하세요(main.ts에서):

import { Component, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
import "@caputchin/widget";

@Component({
  selector: "contact-form",
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  template: `
    <form (submit)="onSubmit($event)">
      <input name="email" type="email" required />
      <caputchin-widget sitekey="cpt_pub_..."></caputchin-widget>
      <button type="submit">Send</button>
    </form>
  `,
})
export class ContactFormComponent {
  onSubmit(e: Event) {
    const data = new FormData(e.target as HTMLFormElement);
    // send data.get("caputchin-token") to your backend
  }
}

Next.js

같은 React 코드가 동작합니다. 커스텀 요소는 서버 렌더링 동안 빈 태그로 렌더링되고 패키지가 로드되면 클라이언트에서 업그레이드되니, 클라이언트 컴포넌트에서 @caputchin/widget을 임포트하는 것(또는 next/script로 CDN 스크립트를 로드하는 것) 외에 특별한 처리가 필요 없습니다.

함께 보기

이 페이지에서