Events
Lil Snack embeds communicate with parent pages via postMessage events. Partners can listen to these events to track game interactions and respond accordingly.
Game Lifecycle Events
These events fire during various stages of game interaction.
lil-snack-game-ready
Fired when a game has loaded and is ready for interaction.
Parameters:
- createdAt - ISO timestamp of when the event occurred
- messageType - "lil-snack-game-ready"
- gameType - Type of game (e.g., "prompt", "swap", "path")
- gameId - Unique identifier for the game
- dayId - Unique identifier for the day
lil-snack-game-visit
Fired when a user visits/views a game.
Parameters:
- createdAt - ISO timestamp of when the event occurred
- messageType - "lil-snack-game-visit"
- gameType - Type of game
- gameId - Unique identifier for the game
- dayId - Unique identifier for the day
lil-snack-game-start
Fired when a user starts playing a game.
Parameters:
- createdAt - ISO timestamp of when the event occurred
- messageType - "lil-snack-game-start"
- gameType - Type of game
- gameId - Unique identifier for the game
- dayId - Unique identifier for the day
lil-snack-game-end
Fired when a user completes a game.
Parameters:
- createdAt - ISO timestamp of when the event occurred
- messageType - "lil-snack-game-end"
- gameType - Type of game
- gameId - Unique identifier for the game
- dayId - Unique identifier for the day
- win - Boolean indicating if the user won the game
Additional Events
lil-snack-view-how-to
Fired when a user views the "How to Play" instructions.
Parameters:
- createdAt - ISO timestamp of when the event occurred
- messageType - "lil-snack-view-how-to"
- gameType - Type of game
- gameId - Unique identifier for the game
- dayId - Unique identifier for the day
lil-snack-post-game-replay
Fired when a user chooses to replay a game after completion.
Parameters:
- createdAt - ISO timestamp of when the event occurred
- messageType - "lil-snack-post-game-replay"
- gameType - Type of game
- gameId - Unique identifier for the game
- dayId - Unique identifier for the day
Event Example
Below is an example of how to listen to and respond to Lil Snack game events:
useEffect(() => {
const embedDomain = "lilsnack.com";
const handleGameEvents = (event) => {
// Verify the event is from Lil Snack
if (!event.origin.includes(embedDomain)) return;
const { messageType, gameType, gameId, dayId, createdAt } = event.data;
switch (messageType) {
case "lil-snack-game-ready":
console.log("Game ready:", { gameType, gameId, dayId });
break;
case "lil-snack-game-visit":
console.log("Game visited:", { gameType, gameId, dayId });
break;
case "lil-snack-game-start":
console.log("Game started:", { gameType, gameId, dayId });
// Track analytics, update UI, etc.
break;
case "lil-snack-game-end":
console.log("Game ended:", {
gameType,
gameId,
dayId,
win: event.data.win
});
// Update user stats, show rewards, etc.
break;
case "lil-snack-view-how-to":
console.log("User viewed instructions:", { gameType });
break;
case "lil-snack-post-game-replay":
console.log("User replaying game:", { gameType, gameId });
break;
}
};
window.addEventListener("message", handleGameEvents);
// Cleanup listener on component unmount
return () => {
window.removeEventListener("message", handleGameEvents);
};
}, []);
Mobile Implementation Examples
iOS (Swift/SwiftUI)
import SwiftUI
import WebKit
struct LilSnackGameView: UIViewRepresentable {
let partnerId: String
let gameType: String
let onMessageReceived: ((String, [String: Any]) -> Void)?
func makeCoordinator() -> Coordinator {
Coordinator(onMessageReceived: onMessageReceived)
}
func makeUIView(context: Context) -> WKWebView {
let contentController = WKUserContentController()
// Inject JavaScript to forward postMessage events to Swift
let script = """
window.addEventListener('message', function(event) {
if (event.origin.includes('lilsnack.com')) {
window.webkit.messageHandlers.lilSnackHandler.postMessage(event.data);
}
});
"""
let userScript = WKUserScript(
source: script,
injectionTime: .atDocumentEnd,
forMainFrameOnly: false
)
contentController.addUserScript(userScript)
contentController.add(context.coordinator, name: "lilSnackHandler")
let config = WKWebViewConfiguration()
config.userContentController = contentController
config.allowsInlineMediaPlayback = true
config.mediaTypesRequiringUserActionForPlayback = []
return WKWebView(configuration: config)
}
func updateUIView(_ webView: WKWebView, context: Context) {
let urlString = "https://staging.lilsnack.com/embed-game-direct?partner=\(partnerId)>=\(gameType)"
if let url = URL(string: urlString) {
let request = URLRequest(url: url)
webView.load(request)
}
}
class Coordinator: NSObject, WKScriptMessageHandler {
let onMessageReceived: ((String, [String: Any]) -> Void)?
init(onMessageReceived: ((String, [String: Any]) -> Void)?) {
self.onMessageReceived = onMessageReceived
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
guard let messageBody = message.body as? [String: Any],
let messageType = messageBody["messageType"] as? String else { return }
onMessageReceived?(messageType, messageBody)
}
}
}
// Usage
LilSnackGameView(
partnerId: "YOUR_PARTNER_ID",
gameType: "prompt",
onMessageReceived: { messageType, data in
switch messageType {
case "lil-snack-game-ready":
print("Game ready")
case "lil-snack-game-start":
print("Game started")
case "lil-snack-game-end":
if let win = data["win"] as? Bool {
print("Game ended - Win: \(win)")
}
case "lil-snack-view-how-to":
print("User viewed instructions")
case "lil-snack-post-game-replay":
print("User replaying game")
default:
break
}
}
)
Android (Kotlin)
import android.webkit.JavascriptInterface
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import org.json.JSONObject
@Composable
fun LilSnackGameView(
partnerId: String,
gameType: String,
onMessageReceived: (String, JSONObject) -> Unit = { _, _ -> }
) {
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { context ->
WebView(context).apply {
webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
// Inject JavaScript to capture postMessage events
val script = """
window.addEventListener('message', function(event) {
if (event.origin.includes('lilsnack.com')) {
AndroidInterface.handleMessage(JSON.stringify(event.data));
}
});
"""
view?.evaluateJavascript(script, null)
}
}
settings.apply {
javaScriptEnabled = true
domStorageEnabled = true
allowFileAccess = true
allowContentAccess = true
mediaPlaybackRequiresUserGesture = false
}
// Add JavaScript interface
addJavascriptInterface(
object {
@JavascriptInterface
fun handleMessage(message: String) {
try {
val json = JSONObject(message)
val messageType = json.getString("messageType")
onMessageReceived(messageType, json)
} catch (e: Exception) {
e.printStackTrace()
}
}
},
"AndroidInterface"
)
val url = "https://staging.lilsnack.com/embed-game-direct?partner=$partnerId>=$gameType"
loadUrl(url)
}
}
)
}
// Usage
LilSnackGameView(
partnerId = "YOUR_PARTNER_ID",
gameType = "prompt",
onMessageReceived = { messageType, data ->
when (messageType) {
"lil-snack-game-ready" -> {
// Game loaded
println("Game ready")
}
"lil-snack-game-start" -> {
// Game started
println("Game started")
}
"lil-snack-game-end" -> {
val win = data.getBoolean("win")
println("Game ended - Win: $win")
}
"lil-snack-view-how-to" -> {
println("User viewed instructions")
}
"lil-snack-post-game-replay" -> {
println("User replaying game")
}
}
}
)
React Native
import React from 'react';
import { WebView } from 'react-native-webview';
import { StyleSheet, View } from 'react-native';
const LilSnackGameView = ({ partnerId, gameType, onMessageReceived }) => {
const gameUrl = `https://staging.lilsnack.com/embed-game-direct?partner=${partnerId}>=${gameType}`;
// JavaScript to inject into the WebView
const injectedJavaScript = `
window.addEventListener('message', function(event) {
if (event.origin.includes('lilsnack.com')) {
window.ReactNativeWebView.postMessage(JSON.stringify(event.data));
}
});
true; // Required for injection to work
`;
const handleMessage = (event) => {
try {
const data = JSON.parse(event.nativeEvent.data);
if (onMessageReceived) {
onMessageReceived(data.messageType, data);
}
} catch (error) {
console.error('Error parsing message:', error);
}
};
return (
<View style={styles.container}>
<WebView
source={{ uri: gameUrl }}
style={styles.webview}
injectedJavaScript={injectedJavaScript}
onMessage={handleMessage}
javaScriptEnabled={true}
domStorageEnabled={true}
allowsInlineMediaPlayback={true}
mediaPlaybackRequiresUserAction={false}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
webview: {
flex: 1,
},
});
// Usage
const App = () => {
return (
<LilSnackGameView
partnerId="YOUR_PARTNER_ID"
gameType="prompt"
onMessageReceived={(messageType, data) => {
switch (messageType) {
case 'lil-snack-game-ready':
console.log('Game ready');
break;
case 'lil-snack-game-start':
console.log('Game started');
break;
case 'lil-snack-game-end':
console.log('Game ended - Win:', data.win);
break;
case 'lil-snack-view-how-to':
console.log('User viewed instructions');
break;
case 'lil-snack-post-game-replay':
console.log('User replaying game');
break;
}
}}
/>
);
};
export default LilSnackGameView;