Skip to content

게임 실행 흐름 ​

플레이어가 오퍼레이터 사이트에서 게임을 누른 순간부터 게임 화면이 뜨고, 나가고, 세션이 끝나기까지의 흐름입니다. 요청 · 응답 필드 상세는 POST /launch 를 보세요.

전체 순서 ​

플레이어 브라우저            오퍼레이터 서버                  플랫폼
      │  ① 게임 클릭              │                              │
      │  (빈 새 창을 먼저 연다)    │                              │
      │ ───── 실행 요청 ────────▶ │                              │
      │                          │ ② POST /operator/v1/launch   │
      │                          │ ───────────────────────────▶ │  플레이어 생성 · 갱신
      │                          │ ◀─────────────────────────── │  launch_url (60초 · 1회용)
      │ ◀──── launch_url ─────── │                              │
      │  ③ 새 창(또는 iframe)을 launch_url 로 이동                  │
      │ ─────────────────────────────────────────────────────▶ │  토큰 교환 → 플랫폼 세션
      │ ◀─── postMessage game.loaded ────────────────────────── │
      │                                                         │  ④ 로비 · 게임 진행
      │                          │ ◀──── 지갑 콜백 (balance · debit · credit · rollback)
      │ ◀─── postMessage balance.changed ────────────────────── │
      │  ⑤ 나가기 → postMessage game.closed, 창 닫힘             │
  1. 플레이어가 사이트에서 게임이나 테이블을 누르면, 브라우저는 오퍼레이터 백엔드에 실행을 요청합니다.
  2. 오퍼레이터 서버가 로그인 · 차단 여부 등을 확인한 뒤 POST /operator/v1/launch 를 서명해 부르고 launch_url 을 받습니다.
  3. 브라우저가 새 창(또는 iframe)을 launch_url 로 보냅니다. 게임 화면은 URL 의 토큰을 즉시 교환해 플랫폼 세션을 만들고 로비나 지정한 게임 · 테이블로 들어갑니다.
  4. 게임이 진행되는 동안 플랫폼은 오퍼레이터 지갑에 콜백을 보내고, 잔액이 바뀌면 사이트 창에 postMessage 로 알립니다.
  5. 플레이어가 나가면 게임 화면이 game.closed 를 보내고 창을 닫습니다.

자격증명은 서버에만

POST /launch 는 반드시 오퍼레이터 서버에서 부르세요. 브라우저에서 Operator API 를 직접 부르면 서명 비밀키가 노출됩니다.

실행 요청 ​

오퍼레이터 서버가 보내는 요청의 주요 필드입니다. 전체 필드 · 오류 코드는 POST /launch 에 있습니다.

json
{
  "player": {
    "external_id": "user_1001",
    "nickname": "Lucky7",
    "currency": "PHP",
    "country": "PH"
  },
  "game_code": "live_holdem",
  "table_id": "tbl_0192f3a1c4e87b2c9d0e1f2a3b4c5d70",
  "lang": "en",
  "device": "mobile",
  "return_url": "https://www.acme.example/",
  "operator_session_id": "c2f1e8a09b7d4e36"
}
필드동작
player.external_id오퍼레이터의 플레이어 ID. 처음 보는 값이면 플랫폼 플레이어가 새로 만들어집니다.
player.nickname게임 화면에 보이는 이름(2~16자). 실행할 때마다 이 값으로 갱신됩니다.
player.currency플레이어 통화. 처음 실행한 통화로 고정되며, 이후 다른 통화로 실행하면 409 CURRENCY_MISMATCH 입니다.
game_code · table_id둘 다 없으면 전체 로비, game_code 만 있으면 그 게임의 로비, table_id 까지 있으면 그 테이블로 바로 들어갑니다.
lang게임 화면 언어. 아래 언어 참고
devicedesktop(기본) 또는 mobile. 라이브 영상 화질 선택에 쓰입니다.
return_url게임 화면에서 "돌아가기"를 누를 때 이동할 주소이자, postMessage 대상 오리진. 비우면 등록된 기본 복귀 URL 을 씁니다.
operator_session_id오퍼레이터가 정하는 세션 식별자. 이 실행으로 생긴 세션의 지갑 콜백 본문에 그대로 실려 옵니다.

operator_session_id 에는 불투명한 ID 를

이 값은 플랫폼의 세션 기록과 지갑 콜백 본문에 남습니다. 사이트 로그인 쿠키나 인증 토큰 원문 대신, 세션을 가리키는 별도의 임의 ID 를 쓰세요.

응답:

json
{
  "launch_url": "{GAME_ORIGIN}/launch#token=lt_9c1e0b7d2f4a4c6e8a0b1c2d3e4f5a6b",
  "expires_at": "2026-10-19T03:21:07.512Z",
  "player_id": "pl_0192f3a1c4e87b2c9d0e1f2a3b4c5d6e"
}

launch_url 과 토큰 ​

항목내용
형식{GAME_ORIGIN}/launch#token=lt_…
유효 시간발급 후 60초 (expires_at)
사용 횟수1회. 한 번 열면 토큰은 소멸합니다.
위치URL fragment(# 뒤). 브라우저가 서버로 보내지 않으므로 웹 서버 로그 · Referer 에 남지 않고, 게임 화면은 읽자마자 주소창에서 지웁니다.
  • launch_url 은 받은 그대로 쓰세요. 쿼리를 덧붙이거나 fragment 를 바꾸지 마세요.
  • 플레이어가 누를 때마다 새로 발급하세요. 미리 만들어 두거나 캐시해 두면 60초가 지나 쓸 수 없습니다.
  • 이미 쓴 토큰이나 만료된 토큰으로 열면 게임 화면에 만료 안내와 돌아가기 버튼이 나옵니다.
  • 토큰 교환은 플랫폼과 플레이어 브라우저 사이에서 이뤄집니다. 오퍼레이터는 플랫폼 세션 토큰을 다룰 필요가 없습니다.

창 열기 ​

새 창 (권장) ​

브라우저 팝업 차단을 피하려면 클릭 처리기 안에서 동기적으로 빈 창을 먼저 열고, 서버에서 launch_url 을 받은 뒤 그 창을 이동시킵니다.

js
async function openGame(gameCode, tableId) {
  // 1) 클릭 이벤트 안에서 바로 연다 (await 뒤에 열면 팝업 차단에 걸린다)
  const mobile = matchMedia('(max-width: 768px)').matches;
  const features = mobile
    ? undefined // 모바일은 새 탭
    : `popup,left=${screen.availLeft ?? 0},top=${screen.availTop ?? 0},width=${screen.availWidth},height=${screen.availHeight}`;
  const win = window.open('about:blank', `game_${gameCode}`, features); // noopener 를 주지 않는다

  // 2) 오퍼레이터 백엔드가 POST /operator/v1/launch 를 부르고 launch_url 을 돌려준다
  const res = await fetch('/api/launch', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ game_code: gameCode, table_id: tableId, device: mobile ? 'mobile' : 'desktop' }),
  });
  if (!res.ok) {
    win?.close();
    return; // 오류 안내
  }
  const { launch_url } = await res.json();

  // 3) 빈 창을 게임으로 보낸다
  if (win) win.location.href = launch_url;
  else location.href = launch_url; // 팝업이 막혔으면 현재 창에서
}
  • noopener · noreferrer 를 쓰지 마세요. 게임 화면은 window.opener 로 사이트 창에 postMessage 를 보내고, 모바일에서는 opener 가 있을 때 첫 터치에 전체화면으로 바꿉니다. opener 가 없으면 이벤트가 오지 않습니다.
  • 창 이름을 게임별로 정하면(game_<game_code>) 같은 게임을 다시 눌렀을 때 새 창이 늘지 않고 기존 창이 재사용됩니다.
  • 창이 닫혔는지 주기적으로(win.closed) 확인해 사이트의 잔액 표시를 새로 고치면 좋습니다.

iframe ​

사이트 안에 iframe 으로 넣을 수도 있습니다.

html
<iframe src="LAUNCH_URL" allow="autoplay; fullscreen" style="border:0;width:100%;height:100%"></iframe>
  • iframe 을 넣을 사이트 도메인을 온보딩 때 임베드 도메인(embed_origins) 으로 등록해야 합니다. 게임 화면은 등록된 도메인 안에서만 iframe 으로 열리고(CSP frame-ancestors), 등록되지 않은 도메인에서는 브라우저가 표시를 막습니다.
  • iframe 에서는 게임 화면이 window.parent 로 postMessage 를 보냅니다.
  • iframe 에서는 게임 화면이 스스로 창을 닫거나 이동하지 않습니다. game.closed 를 받으면 오퍼레이터가 iframe 을 닫거나 숨겨야 합니다.
  • iframe 안에서는 모바일 자동 전체화면을 하지 않습니다.
  • return_url 은 iframe 을 넣은 페이지의 오리진으로 주세요. postMessage 대상 오리진이 이 값으로 정해집니다.

나가기 · return_url ​

게임 화면의 나가기("사이트로 돌아가기")는 아래처럼 동작합니다. 어느 경우든 먼저 game.closed 를 보냅니다.

게임 화면이 열린 방식동작
iframegame.closed 만 보냅니다. iframe 정리는 오퍼레이터가 합니다.
새 창 (opener 있음)창을 닫습니다.
opener 없는 창 · 탭return_url 로 이동합니다.

세션 만료 화면, 이미 쓴 토큰 화면의 "돌아가기" 버튼도 return_url 로 갑니다. return_url 도 기본 복귀 URL 도 없으면 postMessage 이벤트가 전혀 오지 않으므로, 둘 중 하나는 꼭 정하세요.

postMessage 이벤트 ​

게임 화면은 사이트 창(새 창이면 window.opener, iframe 이면 window.parent)에 아래 이벤트를 보냅니다.

  • 메시지 모양: { source: "games-platform", type, payload }
  • 대상 오리진(targetOrigin): return_url 의 오리진(실행 요청 값, 없으면 등록된 기본 복귀 URL). 사이트 창의 오리진이 이와 다르면 브라우저가 메시지를 전달하지 않습니다.
  • 사이트 → 게임 화면 방향의 메시지는 받지 않습니다.
type보내는 때payload
game.loaded토큰 교환이 끝나 게임 화면이 시작될 때{ "game_code": "live_holdem" } — 게임을 지정하지 않았으면 null
balance.changed지갑 거래로 플레이어 잔액이 바뀌었을 때{ "balance": 1250000, "currency": "PHP" } — 금액은 최소 단위 정수
game.closed플레이어가 나가기를 눌렀을 때{ "return_url": "https://www.acme.example/" }
session.expired세션이 만료 · 종료되어 만료 화면으로 바뀔 때{}

받는 쪽 예시:

js
const GAME_ORIGIN = '{GAME_ORIGIN}'; // 환경별로 발급한 게임 화면 오리진으로 바꾼다

window.addEventListener('message', (e) => {
  if (e.origin !== GAME_ORIGIN) return;              // 오리진 확인
  const m = e.data;
  if (!m || m.source !== 'games-platform') return;   // 출처 표시 확인
  switch (m.type) {
    case 'balance.changed':
      showBalance(m.payload.balance, m.payload.currency);
      break;
    case 'game.closed':
    case 'session.expired':
      refreshBalanceFromServer();                    // 지갑 원장 기준으로 다시 조회
      closeGameFrameIfAny();
      break;
  }
});
  • event.origin 을 게임 화면 오리진(환경별 주소)과 반드시 비교하세요.
  • balance.changed 는 화면 표시용입니다. 잔액의 원본은 오퍼레이터 지갑이므로 정산 · 출금 판단에는 쓰지 마세요.

언어 ​

항목내용
지원 언어ko en zh ja th vi
형식zh-CN, ja_JP, EN 처럼 지역 · 대소문자가 섞여도 앞부분으로 맞춥니다.
기본값없거나 지원하지 않는 언어면 ko
게임 안 변경플레이어가 게임 화면의 언어 버튼으로 바꿀 수 있습니다.

통화 ​

  • 플레이어 통화는 player.currency 로 정하고, 오퍼레이터에 등록된 사용 통화 안이어야 합니다(아니면 400 CURRENCY_NOT_SUPPORTED).
  • 한 플레이어(external_id)의 통화는 바꿀 수 없습니다. 여러 통화를 쓰는 사이트라면 통화별로 다른 external_id 를 쓰세요.
  • 플레이어는 자기 통화로 운영되는 테이블만 봅니다.

세션 ​

항목내용
만들어지는 때launch_url 을 열어 토큰을 교환할 때마다 세션 하나
최대 수명12시간. 그 뒤에는 만료 화면이 나오고 session.expired 를 보냅니다. 계속하려면 새로 실행해야 합니다.
유지게임 화면이 알아서 갱신합니다. 오퍼레이터가 따로 연장할 일은 없습니다.
강제 종료POST /players/{external_id}/logout — 그 플레이어의 모든 세션을 끊고, 앉아 있던 테이블에서 일으켜 세웁니다(진행 중인 판이 끝난 뒤 캐시아웃). block: true 면 이후 실행도 막습니다.

여러 창 · 여러 기기 ​

  • 실행할 때마다 독립된 세션이 생기고, 이전 세션은 그대로 유지됩니다. 사이트에서 로그아웃해도 게임 세션은 자동으로 끝나지 않으므로, 사이트 로그아웃 · 자기 배제 · 계정 정지 때는 POST /players/{external_id}/logout 을 함께 부르세요.
  • 중복 게임 설정(multi_table, 기본 꺼짐)이 꺼져 있으면, 한 플레이어는 창 · 기기를 합쳐 한 번에 테이블 하나에만 들어갈 수 있습니다. 다른 테이블에 이미 들어가 있으면 새 테이블 입장이 거절됩니다(MULTI_TABLE). 설정은 PUT /settings 로 바꿉니다.
  • 라이브 테이블은 앉거나 베팅하지 않고 보기만 하는 시간이 설정값(watch_limit_sec)을 넘으면 로비로 돌아갑니다. 설정은 PUT /settings 에 있습니다.

차단된 플레이어 ​

  • block: true 로 차단된 플레이어는 POST /launch 가 403 PLAYER_BLOCKED 로 거절됩니다.
  • 차단 전에 발급된 launch_url 을 열어도 교환 단계에서 거절되고 만료 화면이 나옵니다.
  • 차단을 풀려면 block: false 로 다시 호출합니다(이때도 열려 있던 세션은 끊깁니다).

문서 v1.0.0 · Operator API v1