Files
frankie-arcade.sdk/franke-arcade-sdk.js
T

150 lines
5.7 KiB
JavaScript

// FrankieSDK v1 — include this in your arcade game to talk to Frankie OS.
//
// ============================================================================
// GAME STRUCTURE
// ============================================================================
// Your game must be packaged as a .zip containing:
//
// my-game.zip
// ├── frankie-game.json ← required manifest (see format below)
// ├── index.html ← required entry point
// └── assets/ ← optional: JS, CSS, images, audio, etc.
//
// ============================================================================
// frankie-game.json FORMAT
// ============================================================================
// {
// "id": "my-game", // slug: lowercase letters, digits, hyphens (1-64 chars)
// "version": "1.0.0", // your game version
// "title": "My Game", // shown on the cartridge
// "tagline": "A one-line pitch", // shown under the title
// "icon": "🎮", // emoji shown on the cartridge
// "kind": "ARCADE", // badge: ARCADE | VS | LLM | PET
// "author": "Your Name", // shown in the admin panel
// "order": 50, // sort position in the arcade grid (lower = first)
// "sdk_version": 1 // must be 1; future versions may be incompatible
// }
//
// ============================================================================
// LIFECYCLE HOOKS (events received FROM Frankie)
// ============================================================================
// sdk.on('init', ({ userId, username, gameId, theme }) => { ... })
// Called once after the iframe loads. Initialize your game here.
// You MUST call sdk.emit('ready') when your game has finished loading.
//
// sdk.on('pause', () => { ... })
// Called when the arcade overlay loses focus (e.g. user switches tab).
// Pause timers, animations, and any ongoing audio.
//
// sdk.on('resume', () => { ... })
// Called when the overlay regains focus. Resume paused state.
//
// ============================================================================
// EVENTS EMITTED TO Frankie
// ============================================================================
// sdk.emit('ready')
// Tell Frankie your game is loaded and ready. Required after 'init'.
//
// sdk.emit('xp', { amount: number, reason: string })
// Award XP to the player. 'reason' is a short label shown in toasts.
//
// sdk.emit('win')
// Record a win for this game (increments win counter and awards XP).
//
// sdk.emit('score', { value: number })
// Submit a high score. Frankie keeps the all-time best per game.
//
// sdk.emit('close')
// Close the game and return to the arcade grid.
//
// ============================================================================
// LLM ACCESS (for kind: "LLM" games)
// ============================================================================
// const reply = await sdk.llm([
// { role: 'system', content: 'You are a trivia host.' },
// { role: 'user', content: 'Ask me a question.' }
// ])
// console.log(reply) // string — the model's response text
//
// ============================================================================
// EXAMPLE (vanilla JS)
// ============================================================================
// import sdk from './frankie-sdk.js'
//
// sdk.on('init', ({ username }) => {
// document.querySelector('#welcome').textContent = `Hello, ${username}!`
// sdk.emit('ready')
// })
//
// sdk.on('pause', () => clearInterval(gameLoop))
// sdk.on('resume', () => { gameLoop = setInterval(tick, 16) })
//
// function onPlayerWin() {
// sdk.emit('win')
// sdk.emit('xp', { amount: 50, reason: 'Victory' })
// sdk.emit('score', { value: playerScore })
// }
// ============================================================================
const FrankieSDK = (() => {
const _handlers = {}
const _llmResolvers = {}
// _handle dispatches an inbound postMessage event to the registered hook.
function _handle(type, payload) {
const hook = _handlers[type]
if (typeof hook === 'function') hook(payload)
}
// Boot: listen for messages from the Frankie parent frame.
window.addEventListener('message', (event) => {
const { type, payload, id } = event.data || {}
if (typeof type !== 'string' || !type.startsWith('FRANKIE_')) return
const hook = type.slice(8).toLowerCase() // FRANKIE_INIT → 'init'
// LLM responses are resolved as promises rather than dispatched as hooks.
if (hook === 'llm_response') {
const resolve = _llmResolvers[id]
if (typeof resolve === 'function') {
resolve(payload?.content ?? '')
delete _llmResolvers[id]
}
return
}
_handle(hook, payload)
})
return {
// on registers a lifecycle hook. Returns the SDK for chaining.
on(event, handler) {
_handlers[event] = handler
return this
},
// emit sends a typed event to the Frankie parent frame.
emit(event, payload = {}) {
window.parent.postMessage(
{ type: `FRANKIE_${event.toUpperCase()}`, payload },
'*'
)
},
// llm sends a chat-completion request to Frankie's LLM and returns a Promise
// that resolves with the model's reply as a string.
llm(messages) {
return new Promise((resolve) => {
const id = `llm_${Date.now()}_${Math.random().toString(36).slice(2)}`
_llmResolvers[id] = resolve
window.parent.postMessage(
{ type: 'FRANKIE_LLM_REQUEST', id, payload: { messages } },
'*'
)
})
}
}
})()
export default FrankieSDK