Widget — Integracja techniczna
Przewodnik dla programistów: skrypt, payload init(), powiązanie i tworzenie firmy, cykl życia, uwierzytelnione przywrócenie (restore_id), CSP i weryfikacja.
Strony ścieżki: Przegląd · B2C · B2B.
Wydanie odniesienia: 26 października 2026. Przywrócenie między urządzeniami dochodzi do payloadu firmy. Zanim je włączysz, sprawdź, czy provisionRestoreId jest dostępne w Twojej instalacji.
Interfejs działa w iframe. Strona gospodarza nie potrzebuje Angulara, klienta Supabase ani dostępu do bazy Quoting24.
Wybór typu integracji
| Przypadek | Implementacja | Ciągłość |
|---|---|---|
| Strona publiczna, nieznany odwiedzający | Tylko skrypt. Formularz kontaktu | Sesja gościa w tej samej przeglądarce |
| Strona z danymi klienta | Skrypt i init() z profilem i firmą |
Wstępnie wypełniona sesja gościa i powiązanie firmy |
| Portal z osobistą historią między urządzeniami | Także backend dla external_user_id i restore_id |
Osobista historia na uprawnionym kanale widget |
Powiązanie firmy i przywrócenie historii to osobne operacje. external_id, company_name, imię, e-mail i telefon nie dają rozmów innej osoby. Użytkownik zalogowany na stronie pozostaje gościem widgetu, dopóki nie podasz ważnego poświadczenia przywrócenia.
1. Konfiguracja z Quoting24
W Ustawienia → Kanały otwórz kanał widget i pobierz:
- pełny URL skryptu i klucz publiczny
- dozwolone domeny, łącznie z testami i rozwojem lokalnym
- logo, kolor, pozycję, powitanie, wiadomość powitalną i godziny
Użyj URL i klucza tego samego środowiska. W przykładach zastąp HOST_WIDGET i KLUCZ_PUBLICZNY_WIDGET. Klucz w URL jest publiczny i wybiera kanał: to nie poświadczenie osobiste ani sekret integracji backendu.
Domeny to nazwy hostów (shop.example.com, localhost), bez https://, portu, ścieżki i prefiksu *.. example.com dopuszcza też swoje subdomeny. Pusta lista nie nakłada ograniczenia. Do testu lokalnego serwuj stronę przez HTTP, nie jako plik.
| Wartość | Gdzie ją trzymać | Cel |
|---|---|---|
| Klucz publiczny | URL skryptu | Wybiera kanał widget |
| Prywatny sekret integracji | Tylko sekrety backendu | Pozwala tworzyć i ponownie używać restore_id |
restore_id |
Prywatny magazyn na użytkownika; na stronie tylko przy sesji uwierzytelnionej | Wskazuje, którą historię przywrócić |
| Poświadczenie sesji gościa | localStorage strony, według klucza widgetu, zarządzane przez loader |
Ciągłość gościa w tej samej przeglądarce |
Przywrócenie wymaga integracji backendu, prywatnego endpointu i sekretu, osobno od kodu publicznego. Zobacz sekcję 6.
2. Instalacja
Pływający przycisk
Raz, przed </body>:
<script
src="https://HOST_WIDGET/assets/widget/script.js?key=KLUCZ_PUBLICZNY_WIDGET"
async
></script>
Przycisk pojawia się po załadowaniu ustawień graficznych. Nowy odwiedzający otwiera formularz z telefonem i opcjonalnym imieniem. Loader przechowuje poświadczenie gościa w localStorage, osobno dla klucza. Gdy pamięć jest zablokowana, ciągłość może ograniczyć się do bieżącej strony.
Loader udostępnia jedno window.Quoting24Widget. Drugi skrypt nie tworzy drugiego widgetu.
Czat osadzony
Kontener przed skryptem, jawna wysokość:
<section aria-labelledby="tytul-wsparcia">
<h2 id="tytul-wsparcia">Wsparcie</h2>
<div id="chat-wsparcia" style="width: 100%; height: 600px;"></div>
</section>
<script
src="https://HOST_WIDGET/assets/widget/script.js?key=KLUCZ_PUBLICZNY_WIDGET&mode=embedded&target=chat-wsparcia"
async
></script>
target to ID, bez #. Iframe wypełnia kontener. Nie ma pływającego przycisku. open(), close() i toggle() nie działają.
Czekanie na loader
Przy danych wstępnych użyj tego modułu zamiast statycznego znacznika, importowanego raz w głównym układzie. Przykłady to moduły JavaScript: bundler albo <script type="module" src="/assets/wsparcie.js"></script>.
// /assets/quoting24-loader.js
const widgetScriptUrl =
"https://HOST_WIDGET/assets/widget/script.js?key=KLUCZ_PUBLICZNY_WIDGET";
let widgetPromise;
export function loadWidget() {
if (window.Quoting24Widget) {
return Promise.resolve(window.Quoting24Widget);
}
if (widgetPromise) return widgetPromise;
widgetPromise = new Promise((resolve, reject) => {
const script = document.createElement("script");
script.id = "quoting24-widget-script";
script.src = widgetScriptUrl;
script.async = true;
script.onload = () => {
if (window.Quoting24Widget) resolve(window.Quoting24Widget);
else {
script.remove();
reject(new Error("API widgetu jest niedostępne"));
}
};
script.onerror = () => {
script.remove();
reject(new Error("Nie udało się załadować widgetu"));
};
document.body.appendChild(script);
}).catch((error) => {
widgetPromise = undefined;
throw error;
});
return widgetPromise;
}
import { loadWidget } from "/assets/quoting24-loader.js";
const widget = await loadWidget();
widget.init({
external_id: "52",
company_name: "Ricambi Stella S.r.l.",
name: "mario.rossi",
phone: "+393331234567",
email: "amministrazione@ricambi-stella.example",
});
W trybie osadzonym dodaj &mode=embedded&target=chat-wsparcia do URL i najpierw zamontuj kontener. Nie dubluj tego ładowania statycznym znacznikiem script.
loadWidget() zgłasza, że API jest dostępne. init() zwraca void: kolejkuje profil, gdy iframe nie jest gotowy, i nie czeka na sesję. Wynik sprawdzaj przez isReady() i hasSessionError().
3. Pola init()
Wszystkie wartości są łańcuchami. Pomiń nieznane. Nie wysyłaj null, liczbowych ID ani pustego e-maila. Gdy pole przekracza limity albo e-mail jest źle zbudowany, żądanie jest odrzucane.
| Pole | Limit i zachowanie |
|---|---|
external_id |
Najwyżej 100 znaków. Odniesienie firmy porównywane dokładnie z web_code, w organizacji widgetu. Spacje nie są usuwane |
company_name |
Najwyżej 250 znaków po usunięciu spacji na początku i na końcu. Tworzy brakującą firmę tylko razem z external_id. Pusty łańcuch albo same spacje nie włączają tworzenia |
name |
Najwyżej 250 znaków. Imię kontaktu; może je zaktualizować w bieżącej sesji |
email |
Ważny adres. Przy publicznej inicjalizacji jest zapisywany tylko przy nowej firmie. Nie jest zapisywany przy kontakcie ani przy firmie już istniejącej |
phone |
Najwyżej 80 znaków. Numer kontaktu; wolisz format międzynarodowy, na przykład +393331234567. Konflikt z istniejącym numerem nie scala kontaktów ani czatów |
external_user_id |
Opcjonalne w przypadku podstawowym. Stabilne ID osoby, niepuste, najwyżej 100 znaków po trim. Samo nie przywraca historii |
restore_id |
Prywatne poświadczenie, najwyżej 200 znaków, uzyskane z backendu. Wymaga odpowiadającego external_user_id. Użyj zwróconej wartości bez zmian |
Przykład, gdy strona zna konto. Nazwy pól w ERP mogą się różnić: odwzoruj odniesienie firmy na external_id, nazwę firmy na company_name, osobę na name, a e-mail konta na email.
widget.init({
external_id: "52",
company_name: "Ricambi Stella S.r.l.",
name: "mario.rossi",
phone: "+393331234567",
email: "amministrazione@ricambi-stella.example",
});
Etykiety Imię i Telefon z formularza wysyła się jako name i phone. E-mail to email. Nie wysyłaj Imię, Telefon ani Email.
Pierwsze wywołanie otwiera czat z imieniem i telefonem i nie szuka firmy. Drugie wystarcza z poprawnym e-mailem. W pełnym profilu powyżej phone to numer kontaktu, a email trafia do firmy tylko wtedy, gdy to wywołanie ją tworzy.
Trzymaj name i company_name osobno. Nie doklejaj nazwy firmy do nazwy użytkownika, żeby uzyskać powiązanie. Nie wysyłaj pola web_code: publiczna nazwa to external_id. Nie używaj wewnętrznego UUID Quoting24 ani companies.code zamiast wartości w web_code.
init(profile) zastępuje profil zapisany przez loader. Nie scala: przekaż pola, które mają zostać.
Nowy gość może wystartować z przynajmniej imieniem, telefonem, ważnym e-mailem albo external_user_id, albo z firmą znalezioną albo utworzoną przez external_id. init({}) i samo company_name nie zaczynają nowej sesji. Gdy wyślesz tylko nieznane external_id, bez company_name, widget prosi o dane przez formularz.
init({ restoreId: "..." }) nie przekazuje poświadczenia. Pole to restore_id.
4. Powiązanie albo utworzenie firmy
Przebieg startuje, gdy Quoting24 przetwarza inicjalizację, nawet bez wiadomości:
- Gdy dostaje niepuste
external_id, szuka firmy tej samej organizacji, którejweb_codejest równy tej wartości - Gdy ją znajdzie, wiąże kontakt. Nazwa i e-mail istniejącej firmy zostają
- Gdy jej nie znajdzie i dostaje też niepuste
company_name, tworzy firmę zname = company_name,web_code = external_idi e-mailem, jeśli jest, a potem wiąże kontakt - Gdy nie może znaleźć ani utworzyć firmy z braku danych, idzie dalej z dostępnym profilem. Gdy to nie wystarcza nowemu gościowi, prosi o formularz. Błędy bazy albo limit żądań kończą inicjalizację niepowodzeniem
| Wysłane dane | Sytuacja w Quoting24 | Wynik |
|---|---|---|
external_id: "52", company_name: "Ricambi Stella S.r.l." |
Istnieje web_code = "52" |
Używa istniejącej firmy; nie zmienia jej nazwy |
| Te same dane plus ważny e-mail | Nie istnieje web_code = "52" |
Tworzy firmę z kodem web 52, nazwą i e-mailem oraz wiąże kontakt |
external_id: "52", name: "mario.rossi" |
Brak dopasowania | Uruchamia gościa bez nowego powiązania firmy |
external_id: "52", bez innych danych |
Jest dopasowanie | Uruchamia powiązanego gościa, z domyślnym imieniem Visitatore |
external_id: "52", bez innych danych |
Brak dopasowania | Prosi o dane odwiedzającego |
company_name bez external_id, plus name |
Dowolna | Uruchamia odwiedzającego. Nie szuka i nie tworzy firmy |
Wyszukiwanie nie używa nazwy firmy, e-maila, telefonu, NIP ani companies.code. "052" i "52" to różne wartości. Jeśli najpierw importujesz kartotekę, wypełnij web_code tą samą wartością, którą strona wyśle w external_id.
Powiązanie dotyczy kontaktu. Dla już powiązanego kontaktu nowy ważny kod może zmienić firmę. Pominięcie kodu albo wysłanie nierozwiązanego nie kasuje poprzedniego powiązania. Przy zmianie osoby albo konta wykonaj reset(): nie ograniczaj się do zmiany profilu.
Limity
web_code nie ma ograniczenia unikalności. Przy kilku dopasowaniach wybierane jest pierwsze, według rosnącej daty utworzenia. Unikaj duplikatów: równoczesne żądania, które jeszcze nie znajdują firmy, mogą utworzyć więcej niż jedną. Przebieg nie gwarantuje atomowego utworzenia i nie zastępuje importu ani synchronizacji z ERP.
Tworzenie niejawne jest ograniczone do 20 prób na okno 60 sekund i adres IP, w procesie backendu obsługującym żądanie. Po przekroczeniu inicjalizacja kończy się TOO_MANY_REQUESTS. Ponów z oczekiwaniem. Unikaj ciągłych pętli init().
Dane przychodzą z przeglądarki i odwiedzający może je zmienić. Powiązanie nie dowodzi przynależności ani tożsamości i nie daje historii firmy. Wysyłaj company_name, gdy chcesz dopuścić to publiczne tworzenie. Istniejące firmy aktualizuj przez Quoting24 albo autoryzowaną integrację ERP.
5. Logowanie, wylogowanie i zmiana konta
Trzymaj jedną instancję w głównej strukturze SPA. Zmiana strony nie wymaga nowego skryptu ani resetu.
Przy zmianie osoby albo konta i przy wylogowaniu wywołaj reset() przed nowym profilem. Przy przeładowaniu z nadal aktywnym logowaniem użyj init() bez resetu, jak przy starcie: tak zostaje zapisana sesja gościa. Strona musi odróżnić przeładowanie od rzeczywistej zmiany.
Wywołanie przy zmianach uwierzytelnienia. profile to profil nowego użytkownika albo null przy wylogowaniu.
import { loadWidget } from "/assets/quoting24-loader.js";
let identityRevision = 0;
export async function applyWidgetIdentity(profile) {
const revision = ++identityRevision;
const snapshot = profile ? { ...profile } : null;
window.Quoting24Widget?.reset();
if (!snapshot) return;
const widget = await loadWidget();
if (revision !== identityRevision) return;
widget.reset();
widget.init(snapshot);
}
Obsłuż błąd ładowania w interfejsie strony, z ponowieniem. Jeśli profil pobierasz z backendu przed tą funkcją, unieważnij też te żądania przy zmianie użytkownika. W trakcie oczekiwania od razu wyczyść poprzedni profil przez applyWidgetIdentity(null).
Reset zapomina sesję gościa tej instancji, zapisane poświadczenie, profil i licznik nieprzeczytanych. To nie unieważnienie po stronie serwera i nie kończy sesji na innych urządzeniach. Zmiana uwierzytelnienia musi być zastosowana też w pozostałych otwartych kartach.
Z przywróceniem historii
Gdy profil zawiera restore_id, pobierz je z uwierzytelnionego endpointu strony po reset() i odrzuć odpowiedź, jeśli użytkownik się w międzyczasie zmienił. Przeładowanie z tym samym użytkownikiem używa już zapisanego poświadczenia i nie może wołać reset().
import { loadWidget } from "/assets/quoting24-loader.js";
let authRevision = 0;
let pendingRecovery;
// Tylko przy logowaniu, wylogowaniu i zmianie użytkownika.
// Nie wołaj przy przeładowaniu z tym samym użytkownikiem: reset() porzuciłby trwającą sesję.
export async function applyWidgetAuth(user) {
const revision = ++authRevision;
const expectedUserId = user ? String(user.id) : null;
pendingRecovery?.abort();
const controller = new AbortController();
pendingRecovery = controller;
window.Quoting24Widget?.reset();
try {
const widget = await loadWidget();
if (revision !== authRevision) return;
widget.reset();
if (expectedUserId === null) return;
const response = await fetch("/api/widget/session", {
method: "POST",
credentials: "same-origin",
cache: "no-store",
signal: controller.signal,
headers: { "Content-Type": "application/json" },
body: "{}",
});
if (!response.ok) throw new Error("Przywrócenie widgetu jest niedostępne");
const profile = await response.json();
if (revision !== authRevision) return;
if (profile.external_user_id !== expectedUserId || !profile.restore_id) {
throw new Error("Nieoczekiwana odpowiedź przywrócenia");
}
const payload = {
external_user_id: profile.external_user_id,
restore_id: profile.restore_id,
};
for (const key of ["external_id", "company_name", "name", "email", "phone"]) {
if (profile[key]) payload[key] = profile[key];
}
widget.init(payload);
} catch (error) {
if (controller.signal.aborted || revision !== authRevision) return;
console.warn("Czat wsparcia: przywrócenie nie powiodło się.", error);
}
}
Pętla kopiuje tylko pola obecne i niepuste. E-mail "" albo liczbowe ID nie mogą być wysłane.
API JavaScript
| Metoda | Użycie |
|---|---|
init(profile) |
Podaje albo zastępuje bieżący profil. Loader nie scala pól z poprzednim profilem |
open() / close() / toggle() |
Otwiera, zamyka albo przełącza pływający widget. Zamknięcie nie jest wylogowaniem. W trybie osadzonym nie działają |
isReady() |
Inicjalizacja sesji odwiedzającego się udała. Na formularzu kontaktu zostaje fałszem |
hasSessionError() |
Błąd sesji zgłoszony przez iframe |
reset() |
Czyści profil, sesję lokalną, zapisane poświadczenie gościa i licznik nieprzeczytanych |
dispose() |
Demontuje interfejs i nasłuchiwanie. Nie czyści zapisanego poświadczenia gościa ani globalnego obiektu API |
Możesz podłączyć przycisk strony do window.Quoting24Widget?.open(), gdy pływający przycisk się pojawi. Nie czekaj na isReady(), żeby nowy gość otworzył formularz. Nie ma publicznego zdarzenia gotowości. Te wskaźniki nie opisują całego stanu sieci.
Rozwiązane loadWidget() nie znaczy, że branding i sesja są gotowe.
Pełny demontaż
Gdy musisz usunąć widget, wykonaj dispose(). Gdy demontaż zbiega się z wylogowaniem, najpierw wykonaj reset(). Zanim załadujesz skrypt ponownie, usuń global i wyzeruj Promise loadera.
export function removeWidget() {
window.Quoting24Widget?.reset();
window.Quoting24Widget?.dispose();
delete window.Quoting24Widget;
document.getElementById("quoting24-widget-script")?.remove();
widgetPromise = undefined;
}
widgetPromise to ta zdefiniowana w module loadera. Demontaż wykonaj po zakończeniu ładowania. Nie rób tego równolegle z nowym loadWidget(). Przy zmianach uwierzytelnienia użyj applyWidgetIdentity() albo applyWidgetAuth(), które czekają na loader.
Lokalny reset nie unieważnia restore_id na serwerze.
6. Uwierzytelnione przywrócenie
Sesja gościa czyta swoją najnowszą rozmowę także po zamknięciu. Rozmowy widoczne tej samej osoby w innej przeglądarce wymagają razem external_user_id i restore_id.
widget.init({
external_id: "52",
company_name: "Ricambi Stella S.r.l.",
name: "Mario Rossi",
phone: "+393331234567",
email: "mario.rossi@ricambi-stella.example",
external_user_id: "1042",
restore_id: session.restore_id,
});
Przywrócenie nie scala starych sesji gościa i nie udostępnia historii współpracownikom tej samej firmy.
Endpoint w backendzie strony
Przykład do wdrożenia w portalu, nie URL Quoting24.
{
"external_user_id": "1042",
"external_id": "52",
"company_name": "Ricambi Stella S.r.l.",
"name": "Mario Rossi",
"phone": "+393331234567",
"email": "mario.rossi@ricambi-stella.example",
"restore_id": "<poswiadczenie-zwrocone-przez-quoting24>"
}
Ustaw Cache-Control: no-store. Tożsamość i firma tylko z sesji serwera. Zastosuj auth i CSRF aplikacji. Nie loguj treści zawierających poświadczenie.
Jeśli portal już udostępnia uwierzytelniony endpoint, który zwraca restore_id bieżącego użytkownika dla tego widgetu, użyj tej odpowiedzi i połącz ją z ID osoby oraz odniesieniem firmy.
Provisioning serwer–serwer
Typowa ścieżka: /fastify/trpc/widget.provisionRestoreId
(albo /trpc/widget.provisionRestoreId, gdy backend jest wystawiony bezpośrednio).
POST /fastify/trpc/widget.provisionRestoreId
Authorization: Bearer <SEKRET_INTEGRACJI>
Content-Type: application/json
{
"widget_api_key": "<publiczny-uuid-widgetu>",
"external_user_id": "1042",
"external_id": "52",
"name": "Mario Rossi",
"phone": "+393331234567",
"email": "mario.rossi@ricambi-stella.example",
"attempt_id": "1042"
}
Wygenerowane wartości zaczynają się od q24-restore-v1:. Nie wymyślaj ich i nie wyprowadzaj z publicznych ID.
| Wejście | Wymaganie |
|---|---|
widget_api_key |
Obowiązkowy UUID, dopuszczony dla sekretu |
external_user_id |
Stabilne ID osoby, równe temu w init() |
attempt_id |
Ściśle rosnąca dodatnia liczba całkowita, jako łańcuch dziesiętny, najwyżej 9223372036854775807 |
saved_restore_id |
Już zapisane poświadczenie. Pomiń je przy pierwszym provisioningu |
external_id, name, email, phone |
Opcjonalne, z tymi samymi limitami co init() |
Wzorzec ensure, pobierz albo utwórz:
- Uwierzytelnij użytkownika i weź blokadę na wiersz przywrócenia, w transakcji
- Przydziel nowe
attempt_idz sekwencji po blokadzie (CACHE 1, niecykliczna; sekwencja przeżywa rollback) - Wywołaj Quoting24 z
attempt_idi, jeśli jest,saved_restore_id - Zapisz
result.data.restore_idi zrób commit - Dopiero po commicie zwróć poświadczenie do przeglądarki
Ważne saved_restore_id trzeba użyć ponownie. Pominięcie go, gdy ważny klucz już istnieje, podmienia klucz i unieważnia przywrócone sesje. Nie generuj nowego klucza przy każdym wyświetleniu strony.
Gdy zdalny provisioning się uda, a lokalny zapis nie, zwróć błąd. Nie zwracaj niezapisanego klucza. Ogranicz timeouty.
company_name wysyła się w init(). Powyższe ciało provisioningu go nie zawiera: powiązanie i utworzenie firmy, opisane w sekcji 4, zachodzą, gdy przeglądarka inicjalizuje sesję. E-mail publicznej inicjalizacji jest zapisywany tylko przy nowej firmie.
Wynik po stronie widgetu
Quoting24 sprawdza poświadczenie i ładuje widoczną historię widgetu tego użytkownika. Zamknięte czaty mogą pojawić się jako poprzednia rozmowa: ich odczyt nie otwiera starej rozmowy na nowo; wysyłka zostaje w bieżącej.
Poświadczenie nieważne, należące do innego użytkownika albo unieważnione jest odrzucane, bez automatycznej zamiany na sesję gościa. Przycisk «Riprova» widgetu sam nie prosi o nowe restore_id. Bieżący interfejs jest po włosku.
7. Strona, sieć i wygląd
- Jedna instancja na stronę
- CSP. Zezwól na pochodzenie widgetu w
script-srciframe-srcoraz na pochodzenie API wskazane dla instalacji wconnect-src(ładowanie ustawień graficznych). Zezwól na skonfigurowane obrazy wimg-src. Loader wstawia style inline i nie udostępnia publicznej opcji nonce: sprawdź CSP strony. CSS strony nie zmienia wnętrza iframe z innego pochodzenia - Połączenia iframe. Musi dosięgnąć API, Realtime/WebSocket i magazynu własnej instalacji. Do filtrów sieci użyj rzeczywistych hostów środowiska
- Wygląd i język. Logo, kolor, pozycję i komunikaty konfiguruje się w Quoting24.
company_nameto nazwa firmy klienta. Nie ma publicznego parametrulanguageanilocale; bieżący interfejs jest po włosku - Telefon. Pływający czat może zająć viewport. W trybie osadzonym strona steruje wysokością i szerokością
- Załączniki i godziny. PDF, DOC/DOCX, JPG/JPEG i PNG. Do 10 MiB na plik i 100 MiB załączników wskazanych w rozmowie. Poza godzinami widget pokazuje baner i zostawia pisanie dostępne
8. Sprawdzenie integracji
Użyj konta testowego i kanału testowego. Sprawdź kartotekę w Quoting24, nie tylko czat.
| Próba | Oczekiwany wynik |
|---|---|
| Nowa przeglądarka, tylko skrypt | Formularz kontaktu, potem rozmowa |
Pełny payload z external_id już obecnym jako web_code |
Kontakt powiązany z istniejącą firmą; nazwa i e-mail firmy bez zmian |
| Pełny payload z nowym kodem | Firma utworzona z kodem web, nazwą i e-mailem; kontakt powiązany |
Nowy kod i imię, bez company_name |
Czat uruchomiony, żadna firma nie utworzona |
| Tylko nowy kod | Formularz kontaktu |
| Tylko nazwa firmy i imię | Żadna firma nie szukana ani nie utworzona po nazwie |
| Pusty e-mail, liczbowe ID albo za długa nazwa firmy | Payload odrzucony |
Drugie init() w tej samej sesji |
Sesja gościa trwa. E-mail i telefon nie odzyskują czatu innej osoby |
| Przeładowanie, nawigacja SPA, telefon | Jedna instancja; ciągłość gościa, gdy jest pamięć; użyteczny układ |
| Wylogowanie albo zmiana konta w trakcie ładowania | Poprzednia sesja wyczyszczona; spóźnione wyniki nie przywracają starego profilu |
Ten sam uwierzytelniony użytkownik, inna przeglądarka, ważne restore_id |
Historia tej osoby |
| Dwóch użytkowników tej samej firmy | Osobne poświadczenia i historie |
| Gość, potem przywrócenie | reset() pośrodku; sesje się nie scalają |
| Nieważne albo unieważnione poświadczenie | Błąd, bez automatycznego powrotu do gościa |
| Poprzednie rozmowy | Tylko odczyt; wysyłka zostaje w bieżącej |
| Niedozwolona domena | Dostęp odrzucony |
| Sieć przerwana | Ponowne połączenie; szkic zostaje, gdy wysyłka się nie uda |
Rozwiązywanie problemów
| Objaw | Sprawdzenie |
|---|---|
| Przycisk się nie pojawia | URL, klucz, kanał włączony, domeny, CSP, żądanie widget.getBranding. Sam global JavaScript nie dowodzi, że przycisk istnieje. open() nie działa, dopóki branding się nie załaduje, i nie działa w trybie osadzonym |
Quoting24Widget jest niezdefiniowane |
Poczekaj na załadowanie. Sprawdź parametr key |
| Osadzony czat jest niewidoczny | Kontener przed loaderem, właściwe ID, wysokość większa od zera |
Domain not allowed |
Nazwa hosta strony, bez schematu, portu i ścieżki. Sprawdź też testy. Strona musi być serwowana przez HTTP |
| Formularz telefonu mimo profilu | Kolejność asynchroniczna, snake_case, puste wartości, poprzednie reset(), profil niewystarczający dla nowego gościa |
| Firmy nie znaleziono | Porównaj external_id z web_code w tej samej organizacji, łącznie ze spacjami i zerami wiodącymi |
| Brakująca firma nie jest tworzona | Potrzebne są niepuste external_id i company_name. Sprawdź walidację i limit tworzenia |
| Nazwa albo e-mail firmy się nie zmienia | Zgodnie z kontraktem: init() tworzy brakujące firmy i nie aktualizuje istniejących |
| E-mail nie pojawia się przy kontakcie | Zgodnie z kontraktem publicznej inicjalizacji: e-mail jest używany tylko przy tworzeniu firmy |
TOO_MANY_REQUESTS |
Poczekaj i ponów. Unikaj powtarzanych wywołań albo masowego importu przez widget |
| Brak historii na innym urządzeniu | Payload firmy jej nie przywraca. Potrzebne są external_user_id i restore_id |
| Przywrócone sesje unieważnione | Wywołania ensure muszą odesłać saved_restore_id. Nie wystawiaj nowego klucza przy każdym wyświetleniu strony |
| «Riprova» nie wystarcza | Nowe restore_id z Twojego endpointu, potem reset() i init() |
| Po zmianie konta zostaje stary czat | reset() natychmiast przy zmianie tożsamości. Odrzuć poprzednie odpowiedzi asynchroniczne |
Po dispose() skrypt nie odtwarza widgetu |
Usuń global i skrypt, wyzeruj Promise loadera, potem załaduj ponownie |
| Interfejs zostaje po włosku | Brak publicznej opcji language albo locale w loaderze |
Odniesienia implementacji
- Loader i API JavaScript:
@frontend/src/public/widget/script.ts - Inicjalizacja, powiązanie firmy i tworzenie niejawne:
@backend/fastify/routes/widget/initialize.ts - Testy inicjalizacji i firm:
@backend/fastify/__tests__/widget/widgetInitialize.test.ts - Domena i kanał:
@backend/fastify/routes/widget/procedures/publicWidgetProcedure.ts - Provisioning
restore_id:@backend/fastify/routes/widget/provisionRestoreId.ts