Referenz
samy.js, samy.css, Session-Token
Alles für die Seite deiner App: die Brücke, das Stylesheet und das Token.
Zum Nachschlagen für die Seite deiner App: die Brücke samy.js, das Session-Token und das Stylesheet samy.css.
samy.js
<script src="https://packlets.app/embed/samy.js"></script>Danach gibt es das Objekt samy:
| Aufruf | Ergebnis |
|---|---|
samy.inSamy | true, wenn die Seite in der Samy-App läuft, sonst false. Kein Aufruf, sondern ein Wert. |
samy.launch | Startdaten, sofort beim Laden da: { v, packlet_id, platform, theme }. Kein Aufruf, sondern ein Wert. |
samy.theme | Samys Farben (siehe Farben). Kein Aufruf, sondern ein Wert. |
samy.ready() | Meldet, dass die Seite fertig ist. Samy blendet dann den Platzhalter aus. Nur nötig mit data-ready="manual". |
await samy.token() | Das Session-Token als Text. Direkt nach dem Laden kommt es ohne Wartezeit aus den Startdaten. |
await samy.fetch(url, init) | Wie fetch(), setzt aber Authorization: Bearer <Token> mit einem frischen Token. Gibt die normale Antwort zurück. |
await samy.info() | { app: "samy", version: 2, packlet_id: "…", platform: "iOS", theme: {…} }. platform ist iOS oder android. |
samy.toast("Gespeichert") | Kurze Meldung in der App. Der Text wird nach 140 Zeichen abgeschnitten. |
samy.open("https://…") | Öffnet die Adresse im Browser des Handys. Nur http(s). |
samy.haptic("success") | Vibration: tap (Standard), selection, success oder error. |
await samy.storage.get(key) | Gespeicherter Text oder null. Siehe Speicher. |
await samy.storage.set(key, text) | Speichert Text (höchstens 16 KB). |
await samy.storage.remove(key) | Löscht einen Eintrag. |
await samy.storage.keys() | Alle Schlüssel dieser App. |
Alle Aufrufe außer samy.inSamy geben ein Promise zurück.
- Außerhalb von Samy (zum Beispiel im Browser) schlagen die Aufrufe sofort fehl. Frag deshalb zuerst
samy.inSamyab. - Zeitlimit: Antwortet die App nicht innerhalb von 15 Sekunden, schlägt der Aufruf fehl.
- Nur deine Adresse: Antworten bekommen nur Seiten mit derselben Herkunft (
https, Host und Port) wie die Adresse, die du alspagegesetzt hast. - Nur die Hauptseite: Jeder Aufruf trägt einen geheimen Brücken-Schlüssel aus den Startdaten. Aufrufe aus iframes fremder Adressen haben ihn nicht, Samy ignoriert sie. Mehr unter iframes und fremde Inhalte.
- Läuft die Seite in Samy, trägt
<html>die Klassein-samy. Ist das Skript geladen, feuert aufwindowdas Ereignissamy:ready. - Wieder geöffnet: Samy hält die zuletzt geöffneten Seiten im Speicher (bis zu vier). Kommt jemand zurück, lädt die Seite nicht neu. Stattdessen feuert auf
windowdas Ereignissamy:activated. Das ist der richtige Moment, veraltete Daten aufzufrischen. Der Knopf oben rechts in der App lädt die Seite bei Bedarf ganz neu.
Laden und Platzhalter
Bis deine Seite fertig ist, zeigt Samy Avatar und Name der App. Normalerweise verschwindet der Platzhalter, sobald die Seite geladen ist. Lädt deine Seite danach noch Daten nach, bindest du das Skript mit data-ready="manual" ein und rufst selbst samy.ready(), wenn alles steht. Kommt nach sechs Sekunden kein ready(), blendet Samy den Platzhalter trotzdem aus.
<script src="https://packlets.app/embed/samy.js" data-ready="manual"></script>
<script>
loadEverything().then(() => samy.ready());
</script>Startdaten
Samy hängt beim Laden Startdaten an die Adresse, hinter #samy= (Base64url-kodiertes JSON). Der Teil hinter # geht nie an deinen Server. samy.js liest ihn sofort aus, stellt ihn als samy.launch bereit und entfernt ihn wieder aus der Adresse. Das Session-Token aus den Startdaten liefert samy.token() in den ersten 45 Sekunden ohne Umweg über die App. So weiß deine Seite schon beim ersten Aufruf deines Servers, wer sie öffnet (wie initData bei Telegram).
Den Brücken-Schlüssel aus den Startdaten legt samy.js im sessionStorage deiner Adresse ab. So funktioniert samy auch auf weiteren Seiten, zu denen du innerhalb deiner Adresse verlinkst. Du musst dafür nichts tun.
Farben
samy.theme enthält Samys Farben: bg, surface, text, text_secondary, text_tertiary, field, cta, on_cta, danger und scheme (light). samy.js setzt sie zusätzlich als CSS-Variablen auf <html>, zum Beispiel --samy-bg, --samy-text, --samy-cta oder --samy-text-secondary. Damit passt deine Seite ohne eigene Farbwerte zur App.
Abstände
In Samy füllt deine Seite die ganze Fläche unter Samys schmaler Kopfzeile bis zum unteren Rand (Vollbild). Die Kopfzeile bleibt immer gleich und verdeckt nichts. Unten setzt Samy die CSS-Variable --samy-inset-bottom (Home-Leiste des iPhones) und gibt <html> diesen Abstand als Innenabstand; --samy-inset-top gibt es weiter, ist aber 0. Normalerweise musst du nichts tun.
- Willst du die volle Höhe ohne Scrollen füllen, rechne die Abstände heraus:
min-height: calc(100vh - var(--samy-inset-top) - var(--samy-inset-bottom)). - Willst du alles selbst regeln, schreib
<html data-samy-insets="manual">und nutz die Variablen an passender Stelle.
Speicher
samy.storage speichert kleine Texte auf dem Gerät, getrennt nach Konto und App. Teilen sich zwei Konten ein Handy, sieht keins die Einträge des anderen. Das unterscheidet ihn vom normalen localStorage der Seite. Bis zu 100 Einträge je App, je höchstens 16 KB. Für Objekte nimm JSON.stringify und JSON.parse. Meldet sich jemand ab, bleiben die Einträge auf dem Gerät. Löscht er das Konto, sind sie wertlos, weil niemand sonst sie lesen kann.
Session-Token
Ein JWT, das sagt, wer die Seite gerade in Samy öffnet.
| Algorithmus | HS256 |
| Geheimnis | SHA-256-Hash deines Schlüssels als Hex-Text in Kleinbuchstaben. Dasselbe wie bei der Webhook-Signatur. |
| Gültigkeit | 60 Sekunden |
| Feld | Inhalt |
|---|---|
iss | https://api.packlets.app |
aud | ID deines Packlets. Dieselbe wie packlet_id in jedem Webhook-Ereignis. |
sub | ID des Nutzers in Samy. Bleibt für denselben Nutzer immer gleich. |
email | E-Mail-Adresse des Nutzers. |
name | Name des Nutzers, kann fehlen. |
iat, exp | Ausgestellt und gültig bis, als Unix-Zeit. |
jti | Eindeutige Kennung dieses Tokens. |
Prüf auf deinem Server immer drei Dinge: die Signatur, exp und dass aud dein Packlet ist. Erzeugst du in der App einen neuen Schlüssel, sind alle Tokens sofort ungültig.
Token von Samy prüfen lassen
Ohne eigene JWT-Bibliothek fragt dein Server Samy. Dieser Aufruf braucht keinen Schlüssel.
POST https://api.packlets.app/v1/session-token/verify
Content-Type: application/json| Feld | Typ | Pflicht | Bedeutung |
|---|---|---|---|
token | Text | ja | Das Token aus dem Header Authorization der Anfrage an deinen Server. |
Antwort bei einem gültigen Token:
{
"valid": true,
"packlet_id": "9d2f7c10-3a5b-4c8e-9f21-6b7d8e9f0a12",
"page_url": "https://deine-seite.example/app",
"user": { "id": "4f1c…", "email": "[email protected]", "name": "Du" },
"expires_at": "2026-11-10T08:16:00.000Z"
}Sonst Status 401:
{ "valid": false, "error": "invalid_token", "message": "Das Token ist ungültig oder abgelaufen." }Achtung: Vergleich packlet_id mit der ID deines Packlets. Sonst nimmt dein Server auch Tokens an, die für ein fremdes Packlet ausgestellt wurden. Bedient dein Server viele Packlets (zum Beispiel eine App für alle Nutzer), prüf stattdessen page_url: Das ist die Seite, die das Packlet gerade zeigt.
Für den Aufruf gilt das Rate-Limit, gezählt je Adresse deines Servers.
samy.css
<link rel="stylesheet" href="https://packlets.app/embed/samy.css">Das Stylesheet bringt die Schrift Lato, die Farben der App und diese Bausteine:
| Klasse | Wofür |
|---|---|
samy | Rahmen der Seite: 16 Pixel Innenabstand, Bausteine untereinander mit 12 Pixel Abstand, mindestens so hoch wie der Bildschirm. |
samy-title, samy-big | Überschrift (20 px) und große Zahl (44 px). |
samy-sub, samy-label, samy-note | Grauer Nebentext, Abschnitts-Label in Großbuchstaben, kleiner Hinweis. |
samy-row, samy-spread, samy-grow, samy-push | Nebeneinander; an die Ränder verteilen; Platz füllen; nach unten schieben. |
samy-button | Knopf wie in der App, 44 px hoch. Dazu is-secondary (hell) und is-small (32 px). |
samy-tabs | Umschalter zwischen Ansichten. Der aktive Tab trägt aria-selected="true". |
samy-list | Liste mit feinen Trennlinien, Zeilen mindestens 44 px hoch. |
samy-table | Tabelle. Zahlenspalten mit is-num rechtsbündig. |
samy-field | Eingabefeld. |
samy-chip, samy-dot | Kleines Kennzeichen; Status-Punkt grün, mit is-warn rot. |
samy-bars | Kleines Balkendiagramm aus <i style="height: 40%">. Der aktive Balken trägt is-active. |
Farben und Maße als Variablen:
| Variable | Bedeutung |
|---|---|
--samy-bg | Hintergrund |
--samy-text, --samy-text-2, --samy-text-3 | Haupttext, Nebentext, noch hellerer Text |
--samy-icon | Symbole |
--samy-cta, --samy-on-cta | Dunkelblau der Knöpfe und die Schrift darauf |
--samy-field | Hellgrau für Felder und helle Knöpfe |
--samy-selected, --samy-bar | Ausgewählter Tab, Balken im Diagramm |
--samy-divider | Trennlinien |
--samy-ok, --samy-warn | Grün und Rot für Status |
--samy-radius | Eckenradius |
Text lässt sich auf der Seite nicht markieren (user-select: none), wie in einer App. Eine fertige Beispielseite liegt unter /embed/beispiel.html.