-
-
Notifications
You must be signed in to change notification settings - Fork 72
Open
Description
The README mentions the caveat about LiveSvelte sending the whole state as JSON.
But the secrets worth keeping are typically in the assigns, and not in templates. I think the get_props method should simply filter any assigns whose name begins with, say, secret_ and therefore not send them to the client at all.
I made a simple word-guessing game:
defmodule ExampleWeb.LiveSigil do
use ExampleWeb, :live_view
def render(assigns) do
~V"""
<script>
import { onMount } from 'svelte';
// props sent from the server
let { shown_word, leaderboard, live } = $props();
// Client-Side State using Runes
let name = $state('');
let user_guess = $state(''); // e.g., "ACB"
let status_message = $state(''); // Feedback from the server
let error_message = $state(''); // Client-side validation errors
onMount(() => {
live.handleEvent("feedback", (payload) => {
// This will be called when the server pushes feedback
status_message = payload.message;
});
});
function handleSubmit(){
// --- Client-Side Validation ---
// Assignments to $state variables automatically trigger updates.
error_message = '';
status_message = '';
if (name.trim() === '') {
error_message = 'Please enter your name.';
return;
}
if (user_guess.length != shown_word.length) {
error_message = 'Word length is not correct.';
return;
}
if (user_guess.split('').sort().join('') != shown_word.split('').sort().join('')) {
error_message = 'Letters are not correct.';
return;
}
// `pushEvent` sends the data to the Phoenix LiveView.
live.pushEvent("submit_answer", {name: name, attempt: user_guess});
}
</script>
<h1 class="text-2xl font-bold mb-4">Sorting Guessing Game</h1>
<p class="prose">The server has put these letters <b>{shown_word}</b> in some random order which you need to guess. Correct guess earns you 10 points.</p>
<!-- Guess Word -->
<div class="space-y-4 mb-6">
<div class="flex items-center space-x-4 bg-gray-50 p-3 rounded-lg">
<input
id="name"
type="text"
class="w-48 p-2 border border-gray-300 rounded-lg shadow-sm focus:ring-indigo-500 focus:border-indigo-500"
placeholder="Your name"
bind:value={name}
/>
<input
type="text"
min="1"
max="{shown_word.length}"
class="w-48 p-2 text-center border border-gray-300 rounded-lg shadow-sm focus:ring-indigo-500 focus:border-indigo-500"
placeholder="Your guess"
bind:value={user_guess}
/>
<button
class="w-full sm:w-auto px-6 py-3 bg-indigo-600 text-white font-bold rounded-lg shadow-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors duration-200"
onclick={handleSubmit}
>
Submit Answer
</button>
</div>
</div>
<!-- Status/Error Messages -->
{#if status_message}
<div class="mt-4 p-3 rounded-lg text-center" class:bg-green-100={status_message.includes('Correct')} class:text-green-800={status_message.includes('Correct')} class:bg-red-100={!status_message.includes('Correct')} class:text-red-800={!status_message.includes('Correct')}>
{status_message}
</div>
{/if}
{#if error_message}
<div class="mt-4 p-3 rounded-lg bg-yellow-100 text-yellow-800 text-center">
{error_message}
</div>
{/if}
<!-- Leaderboard Section -->
<div class="bg-white p-6 sm:p-8 rounded-2xl shadow-lg">
<h2 class="text-2xl font-bold text-gray-800 mb-4">Leaderboard</h2>
<div class="space-y-3">
{#if Object.keys(leaderboard).length > 0}
<!-- Sort leaderboard by score descending -->
{#each Object.entries(leaderboard).sort((a, b) => b[1] - a[1]) as [name, score]}
<div class="flex justify-between items-center bg-gray-50 p-3 rounded-lg">
<span class="font-medium text-gray-700">{name}</span>
<span class="font-bold text-indigo-600">{score} pts</span>
</div>
{/each}
{:else}
<p class="text-gray-500">No scores yet. Be the first!</p>
{/if}
</div>
</div>
"""
end
@doc """
Mounts the LiveView, initializing the server-side state.
"""
def mount(_params, _session, socket) do
# On mount, we shuffle the items and set the initial state.
new_secret_word = Enum.shuffle(String.split("ABC","", trim: true)) |> Enum.join
socket =
assign(socket,
secret_word: new_secret_word,
shown_word: "ABC",
leaderboard: %{}
)
{:ok, socket}
end
@doc """
Handles the `submit_answer` event pushed from the Svelte client.
"""
def handle_event("submit_answer", %{"name" => name, "attempt" => attempt}, socket) do
# All game logic happens securely on the server.
if socket.assigns.secret_word == attempt do
# Correct Answer
new_leaderboard = Map.update(socket.assigns.leaderboard, name, 10, &(&1 + 10))
# Push a feedback event to the client.
socket = push_event(socket, "feedback", %{message: "Correct! You earned 10 points."})
# Reset the game for a new round
new_secret_word = Enum.shuffle(String.split("ABC","", trim: true)) |> Enum.join
new_socket =
assign(socket,
secret_word: new_secret_word,
shown_word: "ABC",
leaderboard: new_leaderboard
)
{:noreply, new_socket}
else
# Incorrect Answer
socket = push_event(socket, "feedback", %{message: "Not quite, try again!"})
{:noreply, socket}
end
end
end
As we can see in the browser devtools, all assigns are sent, including secret_word:

gevera
Metadata
Metadata
Assignees
Labels
No labels