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

HTML
<script src="https://funway.boutique/static/sdk/funway-sdk.js"></script>

2. Initialize the SDK

JavaScript
// 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

JavaScript
// 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();
That's it!

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:

HTML
<script src="https://funway.boutique/static/sdk/funway-sdk.js"></script>

Local Copy

Download and include locally for offline development:

HTML
<script src="./funway-sdk.js"></script>

TypeScript Support

Download the TypeScript definitions for full IntelliSense support:

TypeScript
/// <reference path="funway-sdk.d.ts" />

interface MySaveData {
    level: number;
    score: number;
}

const save = await FunwaySDK.loadGame<MySaveData>();

Download TypeScript definitions

Core Methods

init(options?)

Initialize the SDK. Call this before using any other SDK features.

JavaScript
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.

JavaScript
// Call when all assets are loaded
window.addEventListener('load', () => {
    FunwaySDK.gameReady();
});
Important

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.

JavaScript
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.

JavaScript
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.

JavaScript
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.

JavaScript
// Simple score submission
await FunwaySDK.submitScore(1500);

// With metadata (for daily challenges)
await FunwaySDK.submitScore(1500, { daily: true });

getLeaderboard(options?)

Retrieve leaderboard entries.

JavaScript
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.

JavaScript
const result = await FunwaySDK.completeDailyChallenge();
console.log(`Streak: ${result.streak} days!`);

getStreak()

Get the player's current streak information.

JavaScript
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.

JavaScript
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.

JavaScript
// 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.

JavaScript
// 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');
}
Critical

Always check result.rewarded === true before granting rewards. Users can close ads early, and Pro users may have ads disabled entirely.

Social Features

Enable players to share their achievements and results.

share(options)

Share a game result using the native share dialog (or clipboard fallback).

JavaScript
await FunwaySDK.share({
    text: 'I scored 1500 points!',
    score: 1500,
    title: 'My Game Result'
});

generateShareText(grid, options?)

Generate a Wordle-style emoji grid for sharing puzzle results.

JavaScript
const grid = [
    ['🟩', '🟨', '⬛', '⬛', '🟩'],
    ['🟩', '🟩', '🟩', '🟩', '🟩']
];

const shareText = FunwaySDK.generateShareText(grid, {
    title: 'Word Puzzle 123',
    attempts: 2,
    maxAttempts: 6
});

// Output:
// Word Puzzle 123
// 2/6
//
// 🟩🟨⬛⬛🟩
// 🟩🟩🟩🟩🟩
//
// https://funway.boutique

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.

JavaScript
// 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()

JavaScript
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

JavaScript
// 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.

1

Project Structure

Create a simple HTML file with the SDK included:

HTML
<!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>
2

Initialize SDK & Set Up Game State

JavaScript
// 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();
})();
3

Game Logic with Auto-Save

JavaScript
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
        });
    }
}
4

Handle Win with Score & Daily Challenge

JavaScript
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 });
}
Complete Example

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' }
JavaScript
// 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 pause event
  • 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.html in 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

Text
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
Review Process

We review all submissions within 5 business days. We'll test for SDK integration, mobile compatibility, and gameplay quality before publishing.