commit 73d24a736379342e9d7d48a9acbebff09a96b893 Author: Steev <1+steev@noreply@slpnetwork.de> Date: Thu Jul 2 17:17:46 2026 +0200 franke-arcade-sdk.js hinzugefügt diff --git a/franke-arcade-sdk.js b/franke-arcade-sdk.js new file mode 100644 index 0000000..ff732d2 --- /dev/null +++ b/franke-arcade-sdk.js @@ -0,0 +1,149 @@ +// 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