Nahlásit problém
Jedno tlačítko ve vaší aplikaci: člověk napíše, co se pokazilo, případně označí kde, a poslední minuty se přiloží samy. Hlášení dorazí do vašeho Inboxu jako úkol, nebo počká na admina, když za nahlašujícího nikdo neručí.
Co nahlašující uvidí
Lidé ve vaší aplikaci uvidí v rohu stránky velké tlačítko Nahlásit problém — nebo vůbec nic, pokud widget otevíráte vlastním ovládacím prvkem, třeba položkou v menu Nápověda. Mluví česky nebo anglicky: jazykem prohlížeče, pokud ho vaše aplikace nenastaví.
- Nejdřív popis. Malá karta se zeptá Co se pokazilo?. Jedna věta a Odeslat je celé hlášení. Karta říká, kolik posledních minut se přikládá, a vypíše, co všechno je přiloženo.
- Ukažte nám kde, pokud chcete. Ukažte nám kde zmrazí stránku pod průhlednou vrstvou a nahlašující problém označí kruhem, obdélníkem, šipkou, nebo skrytím, které zakryje soukromou část obrazovky. Značky se číslují 1, 2, 3… a každá může mít poznámku. Dokud neexistuje první značka, drží se vedle ukazatele nápověda, která gesto ukazuje: klikněte, podržte a veďte kolem problému. Z klávesnice Enter značku začne, šipky ji posouvají, Shift se šipkami mění její velikost a Enter ji umístí.
- Vlastní klávesová zkratka. Výchozí žádná není. V nastavení karty (⚙) si ji nahlašující nahraje tím, že ji stiskne — musí obsahovat Ctrl, Alt nebo ⌘, Esc nahrávání zruší — a od té chvíle otevře kartu kdekoli ve vaší aplikaci. Pamatuje si ji ten prohlížeč.
- Po odeslání karta poděkuje a nabídne Zobrazit hlášení, pokud vaše instalace provozuje portál pro nahlašující. Když se odeslání nepovede, žádné slovo se neztratí; Zkusit znovu pokračuje v tomtéž hlášení, nepošle druhé.
Nahlašující, kterého vaše aplikace identifikovala, si může zaškrtnout i Pošlete mi e-mail, až to bude opravené.
Instalace
Komponenta je hlásicí podoba balíčku @vitrinka/web. Každá instalace vitrinky ji servíruje jako jeden soubor, takže stačí script tag:
<script type="module" src="https://vitrinka.example.com/vendor/report-widget-1.mjs"></script>
<vitrinka-report key="vkb_…" locale="cs"></vitrinka-report>Soubor servíruje bez cache ta instalace, která hlášení přijímá (na hostované službě https://app.vitrinka.ai/vendor/report-widget-1.mjs), takže se to, co widget zaznamenává, nikdy nerozejde s tím, co server očekává. base má výchozí hodnotu origin, ze kterého skript přišel. Soubor je ES modul: při načtení definuje element, pro stránku, která ho chce ovládat skriptem, exportuje mountReport a identify, a rrweb načítá ze stejné instalace jen tehdy, když projekt stránku nahrává.
V aplikaci s buildem nainstalujte balíček a připojte ho. rrweb, peer závislost, se načte jen tehdy, když projekt stránku nahrává:
bun add @vitrinka/web rrwebimport { mountReport } from "@vitrinka/web/report";
const report = mountReport({ key: "vkb_…", base: "https://vitrinka.example.com", locale: "cs" });
report.open();V Reactu ho připojte jednou a předávejte mu jazyk aplikace a token nahlašujícího, jak se mění. Přepnutí jazyka kartu překreslí, aniž by se ztratil rozepsaný text nebo značky:
import { useEffect, useRef } from "react";
import { mountReport, type ReportHandle } from "@vitrinka/web/report";
export function ReportAProblem({ locale, token }: { locale: string; token?: string }) {
const report = useRef<ReportHandle | null>(null);
useEffect(() => {
report.current = mountReport({ key: "vkb_…", base: "https://vitrinka.example.com" });
return () => report.current?.unmount();
}, []);
useEffect(() => report.current?.setLocale(locale), [locale]);
useEffect(() => report.current?.identify(token ? { token } : null), [token]);
return null;
}Stránka běží s jedním záznamem. Je-li připojená pilulka rekordéru z @vitrinka/web, záznam patří jí a Nahlásit problém pak posílá hlášení bez něj — na stránku připojte jen jednu podobu, vybranou podle režimu vaší aplikace.
Volby
| Volba | Atribut | Co dělá |
|---|---|---|
key | key | Klíč vkb_ pro hlášení z daného projektu. Povinný. |
base | base | Origin vitrinky, kam hlášení odcházejí; u script tagu je výchozí ten, odkud byl skript servírovaný. |
button | button | false skryje plovoucí tlačítko, takže kartu otevírá jen váš vlastní ovládací prvek. |
openSelector | open-selector | CSS selektor vašich vlastních prvků, které kartu otevírají. |
locale | locale | cs nebo en. Bez nastavení jazyk prohlížeče, a není-li ani jedním z nich, angličtina. Za běhu ho změníte přes setLocale() nebo atribut. |
capture | — | Zúží profil záznamu projektu — níže. |
identity | — | Kdo hlásí, stejně jako identify() — níže. |
Handle i element nabízejí open(), close(), identify() a setLocale(); handle má navíc unmount().
Klíč pro hlášení
Klíč vydáte v Nastavení → Workspace → Tokens jako klíč pro hlášení z jednoho projektu, spolu s originy, ze kterých smí hlášení přicházet: prázdný seznam nepustí žádný prohlížeč, * pustí jakýkoli. Klíč je veřejný. Načte veřejnou konfiguraci projektu a posílá hlášení do toho jednoho projektu; žádné hlášení nikdy nepřečte zpátky. Počet požadavků je omezený pro každý klíč a odesílaná data procházejí maskováním citlivých údajů.
Kdo hlásí
Vaše aplikace widgetu řekne, kdo hlásí, jedním ze tří způsobů — a to rozhodne, jestli se hlášení k týmu dostane hned.
| Aplikace předá | Co se stane |
|---|---|
| Token podepsaný vaším serverem | Nahlašující je ten, koho token uvádí. Hlášení jde rovnou do Inboxu týmu a nahlašující ho sleduje na portálu. |
| E-mail bez podpisu | Hlášení se váže k té adrese a čeká na admina. Pro tu adresu není nic čitelné, dokud se její majitel nepřihlásí na portál. |
| Nic | Anonymně: hlášení čeká na admina. |
report.identify({ token });
report.identify({ email, name });
report.identify(null);Identita žije jen v paměti, proto ji předávejte při každém načtení stránky. Funkce identify exportovaná z @vitrinka/web/report určí nahlašujícího pro všechny widgety na stránce; vlastní identify() konkrétního widgetu má přednost.
Podpisový klíč hostitele
Admin projektu ho vydá v Nastavení → Projects na záložce projektu Report a problem v části Host signing key. Ukáže se jen jednou, začíná vhk_ a patří mezi serverová tajemství vaší aplikace — nikdy do prohlížeče. Nové vydání ho nahradí a starý klíč okamžitě přestane platit. revoke zastaví všechna přihlášení podepsaná hostitelem; dokud nevydáte nový klíč, hlášení z vaší aplikace čekají na kontrolu. Nahrazení i zrušení klíče zároveň odhlásí z portálu každého nahlašujícího, kterého klíč přihlásil — klíč se mění proto, že mohl uniknout. Nahlašující přihlášení kódem nebo odkazem z e-mailu zůstávají přihlášení. Vydání i zrušení se auditují jako report.host_key i s počtem ukončených relací portálu.
Podpis tokenu nahlašujícího
Token je kompaktní JWS podepsaný HS256 nad těmito údaji:
| Údaj | Hodnota |
|---|---|
sub | Identifikátor uživatele ve vaší aplikaci. |
email | Jeho adresa, jedna prostá schránka. |
name | Nepovinné: jak ho uvidí tým a portál. |
iat, exp | Sekundy od epochy; exp nejvýš 24 hodin po iat. Vydávejte jeden na každou relaci stránky. |
aud | <workspace>/<project> — workspace a projekt, ke kterým klíč patří; ukáže se při jeho vydání. |
Tajemstvím pro HMAC je celý klíč včetně vhk_. Token, který neprojde ověřením, hlášení nikdy nezahodí: hlášení počká na kontrolu a odpověď na první volání widgetu nese identityError, které řekne, co bylo špatně — audience, platnost, podpis jiným klíčem.
V Node:
import { createHmac } from "node:crypto";
const b64 = (v: string | Buffer) => Buffer.from(v).toString("base64url");
export function reporterToken(user: { id: string; email: string; name?: string }): string {
const now = Math.floor(Date.now() / 1000);
const head = b64(JSON.stringify({ alg: "HS256", typ: "JWT" }));
const body = b64(JSON.stringify({ sub: user.id, email: user.email, name: user.name, iat: now, exp: now + 3600, aud: "acme/shop" }));
const sig = b64(createHmac("sha256", process.env.VITRINKA_REPORT_HOST_KEY!).update(`${head}.${body}`).digest());
return `${head}.${body}.${sig}`;
}V Go:
package reporter
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"time"
)
// Token signs a reporter's identity with the project's host signing key.
func Token(key, sub, email, name string) (string, error) {
now := time.Now().Unix()
claims, err := json.Marshal(map[string]any{
"sub": sub, "email": email, "name": name,
"iat": now, "exp": now + 3600, "aud": "acme/shop",
})
if err != nil {
return "", err
}
enc := base64.RawURLEncoding
body := enc.EncodeToString([]byte(`{"alg":"HS256","typ":"JWT"}`)) + "." + enc.EncodeToString(claims)
mac := hmac.New(sha256.New, []byte(key))
mac.Write([]byte(body))
return body + "." + enc.EncodeToString(mac.Sum(nil)), nil
}Stejný token nahlašujícího přihlásí i na portál — přihlášení.
Profil záznamu
Co widget uchovává před nahlášením, určuje profil záznamu projektu. Admin projektu ho nastaví na téže záložce Report a problem a widget si ho při načtení přečte se zbytkem konfigurace z GET /api/v1/report-config.
| Nastavení | Výchozí | Co určuje |
|---|---|---|
| Úložiště | úložiště prohlížeče | browser zapisuje maskovaný záznam i do IndexedDB prohlížeče, takže minuty před nahlášením přežijí načítání stránek mezitím; memory ho drží jen v otevřené kartě. |
| Okno | 300 sekund | Jak daleko do minulosti záznam sahá: 10 sekund až 30 minut. |
| Velikost | 12 MiB | Strop záznamu, 1 až 64 MiB. Za ním odpadá nejstarší část a záznam se označí jako zkrácený. |
| Třídy | interakce, konzole, síť, chyby | interactions — maskovaný záznam stránky; errors — nezachycené chyby, console.error a selhané požadavky; console — všechny zprávy konzole; network — všechny požadavky; full — všechny čtyři. |
Vaše aplikace může profil jen zúžit — paměť místo úložiště prohlížeče, kratší okno, nižší strop, méně tříd — nikdy ho nerozšíří:
mountReport({ key: "vkb_…", capture: { storage: "memory", windowSec: 120 } });- Maskuje se před uložením. Prohlížeč drží přesně to, co by se odeslalo, zamaskované podle profilu maskování projektu, který se nastavuje v hlavičce jeho panelu Reports:
strict(výchozí) maskuje všechen text i každou hodnotu vstupu,balancedhodnoty vstupů,fullnic. Značka skrytí zakryje svou oblast v každém snímku dřív, než cokoli opustí prohlížeč. - Uložený záznam vyprší. Uložená část z prohlížeče zmizí, když vypadne z okna, když se odešle hlášení z dané karty, a při nejbližším načtení stránky po uplynutí své doby. Přepnutí projektu na paměť ji smaže.
- Co nahlašující píše, zůstává v paměti — popis ani poznámky se nikdy neukládají.
- Z požadavků jen metadata — metoda, cesta, stav a doba trvání, nikdy hlavička ani tělo.
Co se stane potom
- Založeno hned. Hlášení, za které vaše aplikace ručí, nebo hlášení napsané na portálu, se okamžitě stane úkolem: bugem, který čeká v projektu, nahoře v Inboxu týmu. Na pozadí se z jeho záznamu stane session s přehráním, na nástěnku chyb projektu přibude karta a každá značka kromě skrytí se stane anotací na obrazovce, kterou nahlašující označil, propojenou s úkolem. Se zapnutou Eve dorazí už roztříděné — třídění Eve.
- Drženo ke kontrole. Anonymní hlášení, nebo hlášení s e-mailem, za který nikdo neručí, jde do karantény. Jeho záznam je odložený a dostanou se k němu jen admini workspace, takže nic z něj se nedostane na nástěnku, do vyhledávání ani k AI dřív, než se na něj podívá člověk.
Karanténa a kontrola
Admini workspace kontrolují zadržená hlášení v modulu QA pod Bug reports.
| Akce | Kdo | Co se stane |
|---|---|---|
| Prohlédnout | admin workspace | Co bylo napsáno, značky, konzole, požadavky a přehrání. |
| Upravit citlivý obsah | admin workspace | Odstranit vybrané záznamy nebo přehrání před uvolněním. |
| Schválit | admin workspace | Ze záznamu vznikne session, hlášení přistane na nástěnce chyb projektu a do Inboxu dorazí jako úkol — stejnou cestou jako hlášení, za které aplikace ručí. |
| Zamítnout | admin workspace | Hlášení se uzavře a nic se neuvolní. |
Se zapnutým přepínačem auto-ticket projektu otevře schválení i tiket přes konektor projektu.
Testovací běhy a QA záznam
QA plán funkce jako cesty, které se dají projít, a do něj složené výsledky vašeho test runneru — verdikty, snímky a draft chyby za každý neúspěšný případ — aniž by vitrinka kdy vaše testy spouštěla.
Portál pro nahlašující
Kde lidé mimo tým sledují, co nahlásili: přihlášení bez hesla, přehled novinek, příběh každého hlášení od Přijato po Hotovo — a nic z cizích hlášení.