Funway SDK
Build HTML5 games for Funway Boutique with full portal integration. Cloud saves, leaderboards, daily challenges, and more.
Introduction
The Funway SDK enables your HTML5 games to integrate seamlessly with the Funway Boutique portal. When your game runs inside the portal, it gains access to powerful features like cloud saves, global leaderboards, daily challenges, and ad monetization.
Cloud Saves
Automatic save synchronization across devices for logged-in users.
Leaderboards
Global scoreboards with daily, weekly, and all-time rankings.
Daily Challenges
Engage players with daily puzzles and streak tracking.
Social Sharing
Easy share functionality with emoji grid support.
Quick Start
Get your game integrated in under 5 minutes with these simple steps.
1. Include the SDK
<script src="https://funway.boutique/static/sdk/funway-sdk.js"></script>
2. Initialize the SDK
// Initialize when your game loads
FunwaySDK.init({ debug: true });
// Signal when game is ready to play
window.addEventListener('load', () => {
FunwaySDK.gameReady();
});
3. Use SDK Features
// Save game progress
await FunwaySDK.saveGame({ level: 5, score: 1000 });
// Load saved progress
const save = await FunwaySDK.loadGame();
// Submit score to leaderboard
await FunwaySDK.submitScore(1500);
// Complete daily challenge
await FunwaySDK.completeDailyChallenge();
Your game is now integrated with Funway Boutique. The SDK handles all communication with the portal automatically.
Installation
CDN (Recommended)
Include the SDK directly from our CDN for automatic updates:
<script src="https://funway.boutique/static/sdk/funway-sdk.js"></script>
Local Copy
Download and include locally for offline development:
<script src="./funway-sdk.js"></script>
TypeScript Support
Download the TypeScript definitions for full IntelliSense support:
/// <reference path="funway-sdk.d.ts" />
interface MySaveData {
level: number;
score: number;
}
const save = await FunwaySDK.loadGame<MySaveData>();
Core Methods
init(options?)
Initialize the SDK. Call this before using any other SDK features.
FunwaySDK.init({
debug: true, // Enable console logging
autoReady: false, // Auto-call gameReady()
localStorage: true // Enable localStorage fallback
});
| Option | Type | Default | Description |
|---|---|---|---|
debug |
boolean | false | Enable debug logging to console |
autoReady |
boolean | false | Automatically call gameReady() after init |
localStorage |
boolean | true | Enable localStorage fallback for standalone mode |
gameReady()
Signal that your game is fully loaded and ready to play. The portal will hide the loading screen when this is called.
// Call when all assets are loaded
window.addEventListener('load', () => {
FunwaySDK.gameReady();
});
Always call gameReady() after your game is fully loaded. The loading screen won't disappear until this is called (or after a 5-second timeout).
Save System
The SDK provides automatic save management. For guest users, saves go to localStorage. For authenticated users, saves sync to the cloud.
saveGame(data)
Save the current game state. Data must be JSON-serializable.
const gameState = {
level: 5,
score: 2500,
inventory: ['sword', 'shield'],
position: { x: 100, y: 200 }
};
const result = await FunwaySDK.saveGame(gameState);
console.log(result.success); // true
loadGame()
Load the previously saved game state. Returns null if no save exists.
const save = await FunwaySDK.loadGame();
if (save) {
// Restore game state
player.level = save.level;
player.score = save.score;
player.inventory = save.inventory;
} else {
// Start new game
startNewGame();
}
clearSave()
Delete the saved game data. Useful when the player completes the game or wants to restart.
await FunwaySDK.clearSave();
console.log('Save data cleared');
Leaderboards
Submit scores and retrieve leaderboard rankings. Scores are only saved to the global leaderboard for authenticated users.
submitScore(score, metadata?)
Submit a score to the leaderboard.
// Simple score submission
await FunwaySDK.submitScore(1500);
// With metadata (for daily challenges)
await FunwaySDK.submitScore(1500, { daily: true });
getLeaderboard(options?)
Retrieve leaderboard entries.
const leaderboard = await FunwaySDK.getLeaderboard({
period: 'daily', // 'daily', 'weekly', 'monthly', 'alltime'
limit: 10 // Max entries to return
});
leaderboard.entries.forEach((entry, i) => {
console.log(`${i+1}. ${entry.username}: ${entry.score}`);
});
| Property | Type | Description |
|---|---|---|
entries |
Array | List of leaderboard entries |
entries[].rank |
number | Position on leaderboard (1-indexed) |
entries[].username |
string | Player's display name |
entries[].score |
number | Player's score |
Daily Challenges
Track daily challenge completions and maintain player streaks.
completeDailyChallenge()
Mark today's daily challenge as complete. Updates the player's streak.
const result = await FunwaySDK.completeDailyChallenge();
console.log(`Streak: ${result.streak} days!`);
getStreak()
Get the player's current streak information.
const streakInfo = await FunwaySDK.getStreak();
console.log(`Current streak: ${streakInfo.streak}`);
console.log(`Last completed: ${streakInfo.lastCompleted}`);
hasCompletedToday()
Check if the player has already completed today's challenge.
const completed = await FunwaySDK.hasCompletedToday();
if (completed) {
showMessage('Come back tomorrow!');
} else {
startDailyChallenge();
}
Ads Integration
The portal controls all ad display. Your game simply requests ads, and the portal decides whether to show them based on frequency caps, user preferences, and Pro subscription status.
showInterstitial()
Request an interstitial ad. The portal decides whether to actually display it.
// Good times to show interstitials:
// - Between levels
// - After game over
// - When returning to main menu
const result = await FunwaySDK.showInterstitial();
if (result.shown) {
console.log('Ad was displayed');
} else {
console.log(`Ad skipped: ${result.reason}`);
}
showRewardedAd(rewardType, amount?)
Request a rewarded ad. Only grant the reward if rewarded === true.
// Request a rewarded ad for a hint
const result = await FunwaySDK.showRewardedAd('hint', 1);
// IMPORTANT: Only grant reward if user watched the ad
if (result.rewarded) {
grantHint();
showMessage('Hint unlocked!');
} else {
showMessage('Watch the full ad to earn your reward');
}
Always check result.rewarded === true before granting rewards. Users can close ads early, and Pro users may have ads disabled entirely.
Events
Listen to portal events to respond to user actions like tab switching and mute toggles.
on(event, callback)
Subscribe to a portal event. Returns an unsubscribe function.
// Pause game when user leaves
FunwaySDK.on('pause', () => {
game.pause();
showPauseMenu();
});
// Resume game when user returns
FunwaySDK.on('resume', () => {
// Note: Don't auto-resume - let user click to continue
});
// Handle mute toggle
FunwaySDK.on('mute', (muted) => {
audio.setMuted(muted);
});
// Unsubscribe when needed
const unsubscribe = FunwaySDK.on('pause', handler);
unsubscribe(); // Remove listener
| Event | Callback Args | Description |
|---|---|---|
pause |
none | User switched tabs or opened menu |
resume |
none | User returned to game |
mute |
boolean | User toggled mute (true = muted) |
focus |
none | Game iframe gained focus |
blur |
none | Game iframe lost focus |
User Info
Get information about the current user's authentication state and subscription.
getUser()
const user = await FunwaySDK.getUser();
if (user.authenticated) {
console.log(`Welcome, ${user.username}!`);
if (user.isPro) {
console.log('Pro user - no ads!');
}
} else {
console.log('Playing as guest');
}
Helper Methods
// Check authentication
const isLoggedIn = await FunwaySDK.isAuthenticated();
// Check Pro status
const isPro = await FunwaySDK.isPro();
// Check if running in portal
const inPortal = FunwaySDK.isInPortal();
Full Tutorial: Building a Memory Game
Let's build a complete memory matching game with full SDK integration.
Project Structure
Create a simple HTML file with the SDK included:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Memory Match</title>
<script src="https://funway.boutique/static/sdk/funway-sdk.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: system-ui, sans-serif;
background: #0f0f0f;
color: #fff;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.game-board {
display: grid;
grid-template-columns: repeat(4, 80px);
gap: 10px;
}
.card {
width: 80px;
height: 80px;
background: #2a2a2a;
border: none;
border-radius: 8px;
font-size: 2rem;
cursor: pointer;
transition: transform 0.2s, background 0.2s;
}
.card:hover { background: #3a3a3a; }
.card.flipped { background: #6366f1; }
.card.matched { background: #22c55e; }
</style>
</head>
<body>
<div class="game-board" id="board"></div>
<script src="game.js"></script>
</body>
</html>
Initialize SDK & Set Up Game State
// game.js
(async function() {
'use strict';
// Game state
const state = {
cards: [],
flipped: [],
matched: 0,
moves: 0,
isMuted: false
};
// Initialize SDK
await FunwaySDK.init({ debug: true });
// Listen for portal events
FunwaySDK.on('mute', (muted) => {
state.isMuted = muted;
});
FunwaySDK.on('pause', () => {
// Optional: show pause overlay
});
// Try to load saved game
const save = await FunwaySDK.loadGame();
if (save && save.cards) {
state.cards = save.cards;
state.matched = save.matched;
state.moves = save.moves;
} else {
initNewGame();
}
renderBoard();
// Signal ready to portal
FunwaySDK.gameReady();
})();
Game Logic with Auto-Save
function initNewGame() {
const emojis = ['🍭', '🍒', '🍓', '🍎', '🍊', '🍋', '🍌', '🍇'];
state.cards = [...emojis, ...emojis]
.sort(() => Math.random() - 0.5)
.map((emoji, i) => ({ id: i, emoji, isFlipped: false, isMatched: false }));
state.matched = 0;
state.moves = 0;
state.flipped = [];
}
async function flipCard(index) {
const card = state.cards[index];
if (card.isFlipped || card.isMatched || state.flipped.length >= 2) return;
card.isFlipped = true;
state.flipped.push(index);
renderBoard();
if (state.flipped.length === 2) {
state.moves++;
const [first, second] = state.flipped;
if (state.cards[first].emoji === state.cards[second].emoji) {
// Match!
state.cards[first].isMatched = true;
state.cards[second].isMatched = true;
state.matched++;
state.flipped = [];
if (state.matched === 8) {
await handleWin();
}
} else {
// No match - flip back
setTimeout(() => {
state.cards[first].isFlipped = false;
state.cards[second].isFlipped = false;
state.flipped = [];
renderBoard();
}, 1000);
}
// Auto-save after each move
await FunwaySDK.saveGame({
cards: state.cards,
matched: state.matched,
moves: state.moves
});
}
}
Handle Win with Score & Daily Challenge
async function handleWin() {
// Calculate score (fewer moves = higher score)
const score = Math.max(1000 - (state.moves - 8) * 50, 100);
// Submit score
await FunwaySDK.submitScore(score);
// Complete daily challenge
const daily = await FunwaySDK.completeDailyChallenge();
console.log(`Streak: ${daily.streak} days!`);
// Clear save data
await FunwaySDK.clearSave();
// Show win screen with share option
showWinScreen(score, daily.streak);
}
async function shareResult() {
const grid = generateResultGrid();
const text = FunwaySDK.generateShareText(grid, {
title: 'Memory Match',
attempts: state.moves,
maxAttempts: 20
});
await FunwaySDK.share({ text });
}
See a fully working example at /static/sdk/example-game/ or check the game examples repository.
Standalone Mode
The SDK automatically detects when running outside the portal and provides localStorage fallbacks for all features. This lets you develop and test locally without the portal.
How It Works
- Saves: Stored in localStorage with key
funway_[game]_save - Scores: Stored locally, top 10 kept
- Daily Challenges: Tracked locally by date
- Ads: Interstitials are skipped; rewarded ads auto-grant rewards
- User: Returns
{ authenticated: false, username: 'Guest' }
// Check if running in portal or standalone
if (FunwaySDK.isInPortal()) {
console.log('Running in Funway Boutique portal');
} else {
console.log('Running standalone (localStorage mode)');
}
Best Practices
Performance
- Keep total game size under 500KB for fast loading
- Use efficient assets (WebP images, compressed audio)
- Minimize external dependencies
- Target 60fps gameplay on mobile devices
Save Frequently
- Save after each meaningful action (level complete, item collected)
- Don't save on every frame - batch updates
- Clear saves when game is completed
Handle Events Properly
- Always pause game logic on
pauseevent - Don't auto-resume - let users click to continue
- Respect mute state for all audio
Mobile First
- Design for 320px minimum width
- Use touch-friendly controls (44px minimum tap targets)
- Support both touch and mouse input
- Test on actual mobile devices
Accessibility
- Provide keyboard controls where possible
- Use sufficient color contrast
- Don't rely solely on color to convey information
- Support screen readers for UI elements
Game Submission
Ready to submit your game to Funway Boutique? Here's what you need.
Requirements
- File format: ZIP archive containing all game files
- Entry point:
index.htmlin the root of the ZIP - Size limit: 500KB maximum (uncompressed)
- Dependencies: No external scripts except the Funway SDK
- Responsive: Must work on 320px - 1920px screens
- Touch support: Must work with touch input
ZIP Structure
my-game.zip
├── index.html (required)
├── game.js
├── style.css
├── assets/
│ ├── sprites.png
│ └── sounds/
└── README.md (optional)
Pre-Submission Checklist
- ✅ SDK included and initialized
- ✅
gameReady()called after loading - ✅ Save/load working correctly
- ✅ Score submission working
- ✅ Pause/mute events handled
- ✅ Works on mobile (test on real device)
- ✅ No external API calls or dependencies
- ✅ Under 500KB total size
Submit Your Game
Send your game ZIP to developers@funway.boutique with:
- Game title and description
- Category (puzzle, word, math, memory, logic)
- 512x512 thumbnail image
- Your contact information
We review all submissions within 5 business days. We'll test for SDK integration, mobile compatibility, and gameplay quality before publishing.
Social Features
Enable players to share their achievements and results.
share(options)
Share a game result using the native share dialog (or clipboard fallback).
generateShareText(grid, options?)
Generate a Wordle-style emoji grid for sharing puzzle results.