본문으로 건너뛰기

KPlay API

게임과 KPlay 플랫폼 간 통신을 위한 API입니다. 게임은 iframe 안에서 실행되며, KPlayAPI 클래스를 통해 크레딧 결제, 리더보드, 유저 정보 등을 사용할 수 있습니다.

KPlayAPI 클래스

아래 코드를 게임에 복사하세요. 모든 플랫폼 기능이 포함되어 있습니다.

class KPlayAPI {
constructor() {
this.user = { userId: null, name: null, credits: 0 };
this._pending = null;
this._onUserInfo = null;
this._onPurchaseResult = null;
this._onScoreSubmitted = null;
this._reqs = new Map();
this._reqSeq = 0;
this._storagePrefix = 'kplay:';
this.storage = {
getItem: (key) => this._storageGet(key),
setItem: (key, value) => this._storageSet(key, value),
removeItem: (key) => this._storageRemove(key),
keys: () => this._storageKeys(),
};

window.addEventListener('message', (e) => {
const { type, ...data } = e.data ?? {};
if (!type) return;

switch (type) {
case 'user-info':
this.user = { userId: data.userId, name: data.name, credits: data.credits ?? 0 };
this._onUserInfo?.(this.user);
// 로그인되면 로컬 캐시를 서버 데이터로 동기화
if (data.userId) this._syncStorageFromServer();
break;
case 'purchase-result':
if (data.success) this.user.credits = data.balance;
this._onPurchaseResult?.(data);
this._pending = null;
break;
case 'score-submitted':
this._onScoreSubmitted?.(data);
break;
case 'storage-load-result':
case 'storage-set-result':
case 'storage-remove-result': {
const req = this._reqs.get(data.requestId);
if (!req) break;
this._reqs.delete(data.requestId);
data.success ? req.resolve(data) : req.reject(new Error(data.error || 'storage error'));
break;
}
}
});
}

_request(type, payload) {
const requestId = `r${++this._reqSeq}`;
return new Promise((resolve, reject) => {
this._reqs.set(requestId, { resolve, reject });
window.parent.postMessage({ type, requestId, ...payload }, '*');
setTimeout(() => {
if (this._reqs.has(requestId)) {
this._reqs.delete(requestId);
reject(new Error('timeout'));
}
}, 10000);
});
}

_lsKey(key) { return this._storagePrefix + key; }

async _storageGet(key) {
const local = localStorage.getItem(this._lsKey(key));
return local; // 서버 값은 user-info 시점에 이미 로컬로 동기화됨
}

async _storageSet(key, value) {
localStorage.setItem(this._lsKey(key), String(value));
if (!this.user.userId) return; // 비로그인: 로컬만
try { await this._request('storage-set', { key, value: String(value) }); }
catch (e) { console.warn('[kplay] cloud save failed, kept local:', e.message); }
}

async _storageRemove(key) {
localStorage.removeItem(this._lsKey(key));
if (!this.user.userId) return;
try { await this._request('storage-remove', { key }); } catch {}
}

async _storageKeys() {
const keys = [];
const prefix = this._storagePrefix;
for (let i = 0; i < localStorage.length; i++) {
const k = localStorage.key(i);
if (k && k.startsWith(prefix)) keys.push(k.slice(prefix.length));
}
return keys;
}

async _syncStorageFromServer() {
try {
const res = await this._request('storage-load', {});
const data = res.data || {};
// 서버 우선: 서버 값으로 로컬 덮어쓰기
for (const [k, v] of Object.entries(data)) {
localStorage.setItem(this._lsKey(k), v);
}
// 로컬에만 있는 키는 서버로 업로드 (최초 로그인 마이그레이션)
for (let i = 0; i < localStorage.length; i++) {
const k = localStorage.key(i);
if (!k || !k.startsWith(this._storagePrefix)) continue;
const key = k.slice(this._storagePrefix.length);
if (!(key in data)) {
const value = localStorage.getItem(k);
if (value != null) {
this._request('storage-set', { key, value }).catch(() => {});
}
}
}
} catch (e) {
console.warn('[kplay] storage sync failed:', e.message);
}
}

/** 아이템 구매 요청 */
purchase(itemId, itemName, price) {
if (this._pending) return;
if (!this.user.userId) { this.login(); return; }
if (this.user.credits < price) { this.openCreditShop(); return; }
this._pending = { itemId, itemName, price };
window.parent.postMessage({ type: 'purchase-item', itemId, itemName, price }, '*');
}

/** 점수 제출 */
submitScore(score) {
window.parent.postMessage({ type: 'submit-score', score }, '*');
}

/** 크레딧 충전 모달 열기 */
openCreditShop() {
window.parent.postMessage({ type: 'credit-shop' }, '*');
}

/** 랭킹창 표시 */
showLeaderboard() {
window.parent.postMessage({ type: 'show-leaderboard' }, '*');
}

/** 커스텀 이벤트 로그 */
log(eventType, metadata) {
window.parent.postMessage({ type: 'analytics-event', eventType, metadata }, '*');
}

/** 로그인 요청 */
login() {
window.parent.postMessage({ type: 'login-required' }, '*');
}

/** @deprecated `login()` 사용 */
requestLogin() { this.login(); }

/**
* 전면 광고 (인터스티셜) 표시.
* 레벨 전환, 재시작 등 자연스러운 끊김 지점에서 호출하세요.
* @param {string} adType - 'start' | 'pause' | 'next' | 'browse' (기본 'next')
* @param {string} name - 분석용 이름 (선택)
* @returns {Promise<boolean>} 광고가 실제 노출됐으면 true
*/
showInterstitialAd(adType = 'next', name = 'interstitial') {
return new Promise((resolve) => {
const handler = (e) => {
if (e.data?.type !== 'interstitial-ad-result') return;
window.removeEventListener('message', handler);
resolve(e.data.shown);
};
window.addEventListener('message', handler);
window.parent.postMessage({ type: 'show-interstitial-ad', adType, name }, '*');
setTimeout(() => { window.removeEventListener('message', handler); resolve(false); }, 15000);
});
}

/**
* 보상형 광고 표시.
* 유저가 광고를 끝까지 시청하면 포인트가 지급됩니다.
* @returns {Promise<{success: boolean, points?: number, error?: string}>}
*/
showRewardedAd() {
return new Promise((resolve) => {
const handler = (e) => {
if (e.data?.type !== 'rewarded-ad-result') return;
window.removeEventListener('message', handler);
resolve(e.data);
};
window.addEventListener('message', handler);
window.parent.postMessage({ type: 'show-rewarded-ad' }, '*');
setTimeout(() => { window.removeEventListener('message', handler); resolve({ success: false, error: 'timeout' }); }, 30000);
});
}

/** 이벤트 리스너 등록 */
on(event, callback) {
if (event === 'userInfo') this._onUserInfo = callback;
if (event === 'purchaseResult') this._onPurchaseResult = callback;
if (event === 'scoreSubmitted') this._onScoreSubmitted = callback;
return this;
}
}

사용 예시

const kplay = new KPlayAPI();

// 유저 정보 수신
kplay.on('userInfo', (user) => {
console.log(`${user.name} (크레딧: ${user.credits})`);
});

// 구매 결과 처리
kplay.on('purchaseResult', (result) => {
if (result.success) {
grantItem(result.transactionId); // 게임 내 아이템 지급
} else {
showToast('구매 실패: ' + result.error);
}
});

// 아이템 구매 (로그인/잔액 체크 자동)
kplay.purchase('boost-x2', '2배 부스트', 50);

// 로그인 버튼 클릭 시
kplay.login();

// 점수 제출
kplay.submitScore(15000);

// 랭킹창 열기
kplay.showLeaderboard();

// 커스텀 이벤트 로그
kplay.log('level_complete', { level: 3, time: 42 });
kplay.log('item_used', { itemId: 'potion-hp' });

// 클라우드 스토리지 (localStorage 호환 인터페이스)
// 로그인 상태면 서버에 저장되어 기기 전환해도 유지됨
// 비로그인 상태면 localStorage에만 저장
await kplay.storage.setItem('highscore', '9500');
const score = await kplay.storage.getItem('highscore');
await kplay.storage.removeItem('old-key');
const allKeys = await kplay.storage.keys();

// 전면 광고 (레벨 전환, 재시작 시)
const shown = await kplay.showInterstitialAd('next', 'level-clear');
if (shown) console.log('광고 노출됨');

// 보상형 광고 (유저가 선택해서 시청)
const adResult = await kplay.showRewardedAd();
if (adResult.success) {
console.log(`보상 ${adResult.points} 포인트 지급!`);
}

스토리지 (클라우드 세이브)

kplay.storagelocalStorage와 동일한 key/value 인터페이스입니다. 로그인 상태에선 서버에 저장되어 기기 전환해도 데이터가 유지됩니다. 기존 localStorage.setItem('score', '100') 코드를 await kplay.storage.setItem('score', '100')로 바꾸기만 하면 됩니다.

동작

  • 로그인: 서버 + localStorage 동시 저장 (로컬은 오프라인/지연 캐시)
  • 비로그인: localStorage만 저장
  • 로그인 순간: 서버 값을 우선 로컬에 덮어쓰고, 로컬에만 있던 키는 서버로 업로드 (최초 마이그레이션)

제한

  • key 길이 ≤ 128자
  • value 크기 ≤ 10KB
  • 게임당 총 100 keys / 100KB

API 메서드

메서드설명
purchase(itemId, itemName, price)크레딧으로 아이템 구매. 미로그인 시 로그인 유도, 잔액 부족 시 충전 모달 자동 처리
submitScore(score)리더보드에 점수 제출
showLeaderboard()랭킹창 표시
log(eventType, metadata?)커스텀 이벤트 로그. Analytics에서 확인 가능
openCreditShop()크레딧 충전 모달 열기
login()로그인 요청 (Google OAuth 리다이렉트)
showInterstitialAd(adType?, name?)전면 광고 표시. adType: 'start' | 'pause' | 'next' | 'browse'. 광고 노출 여부 반환
showRewardedAd()보상형 광고 표시. 시청 완료 시 포인트 지급
storage.setItem(key, value)클라우드 세이브: 값 저장 (로그인 시 서버 동기화)
storage.getItem(key)클라우드 세이브: 값 조회
storage.removeItem(key)클라우드 세이브: 값 삭제
storage.keys()클라우드 세이브: 저장된 키 목록

이벤트

이벤트콜백 데이터설명
userInfo{ userId, name, credits }게임 로드 시 자동 수신
purchaseResult{ success, transactionId?, balance?, error? }구매 결과
scoreSubmitted{ success, rank? }점수 제출 결과

크레딧 시스템

가격
100 크레딧$0.99
500 크레딧$3.99
1,000 크레딧$6.99

서버사이드 웹훅

백엔드가 있는 게임은 웹훅 연동으로 결제를 서버에서 검증할 수 있습니다.