Vai al contenuto

Widget — Integrazione tecnica

Guida per sviluppatori: script, payload di init(), associazione e creazione azienda, ciclo di vita, ripristino autenticato (restore_id), CSP e verifica.

Pagine di percorso: Panoramica · B2C · B2B.

Edizione di riferimento: 26 ottobre 2026. Il ripristino tra dispositivi è aggiuntivo rispetto al payload azienda. Prima di abilitarlo, verifica che provisionRestoreId sia disponibile nella tua installazione.

L’interfaccia è in un iframe. Nel sito ospitante non servono Angular, un client Supabase o accesso al database di Quoting24.


Scegliere il tipo di integrazione

Caso Implementazione Continuità
Sito pubblico, visitatore sconosciuto Solo script. Modulo di contatto Sessione ospite nello stesso browser
Sito con dati del cliente Script e init() con profilo e azienda Sessione ospite precompilata e associazione aziendale
Portale con cronologia personale tra dispositivi Anche backend per external_user_id e restore_id Cronologia personale nel canale widget autorizzato

Associare un’azienda e recuperare la cronologia sono operazioni distinte. external_id, company_name, nome, email e telefono non autorizzano le conversazioni di un’altra persona. Un utente autenticato nel sito resta un ospite del widget finché non fornisci una credenziale di ripristino valida.


1. Configurazione da Quoting24

In Impostazioni → Canali, apri il canale widget e recupera:

  • URL completo dello script e chiave pubblica
  • domini autorizzati, compresi collaudo e sviluppo locale
  • logo, colore, posizione, saluto, benvenuto e orari

Usa URL e chiave dello stesso ambiente. Negli esempi sostituisci HOST_WIDGET e CHIAVE_PUBBLICA_WIDGET. La chiave nell’URL è pubblica e seleziona il canale: non è una credenziale personale né un segreto di integrazione backend.

I domini sono nomi host (shop.example.com, localhost), senza https://, porta, percorso o prefisso *.. example.com autorizza anche i sottodomini. Una lista vuota non applica restrizioni. Per il collaudo locale servi la pagina via HTTP, non come file.

Valore Dove conservarlo Scopo
Chiave pubblica URL dello script Seleziona il canale widget
Segreto privato dell’integrazione Solo secrets del backend Autorizza create/reuse di restore_id
restore_id Archivio privato per utente; in pagina solo a sessione autenticata Dimostra quale cronologia ripristinare
Credenziale sessione ospite localStorage del sito, per chiave widget, gestita dal loader Continuità ospite nello stesso browser

Per il ripristino servono integrazione backend, endpoint privato e segreto, separati dal codice pubblico. Vedi sezione 6.


2. Installazione

Pulsante flottante

Una sola volta, prima di </body>:

<script
  src="https://HOST_WIDGET/assets/widget/script.js?key=CHIAVE_PUBBLICA_WIDGET"
  async
></script>

Il pulsante compare dopo il caricamento delle impostazioni grafiche. Un nuovo visitatore apre il modulo con telefono e nome facoltativo. Il loader conserva la credenziale ospite nel localStorage, separata per chiave. Se lo storage è bloccato, la continuità può limitarsi alla pagina corrente.

Il loader espone un unico window.Quoting24Widget. Un secondo script non crea un secondo widget.

Chat incorporata

Contenitore prima dello script, altezza esplicita:

<section aria-labelledby="titolo-assistenza">
  <h2 id="titolo-assistenza">Assistenza</h2>
  <div id="chat-assistenza" style="width: 100%; height: 600px;"></div>
</section>

<script
  src="https://HOST_WIDGET/assets/widget/script.js?key=CHIAVE_PUBBLICA_WIDGET&amp;mode=embedded&amp;target=chat-assistenza"
  async
></script>

target è l’ID, senza #. L’iframe riempie il contenitore. Non c’è pulsante flottante. open(), close() e toggle() non hanno effetto.

Attendere il loader

Per i dati precompilati usa questo modulo al posto del tag statico, importato una sola volta nel layout principale. Gli esempi sono moduli JavaScript: bundler, oppure <script type="module" src="/assets/assistenza.js"></script>.

// /assets/quoting24-loader.js
const widgetScriptUrl =
  "https://HOST_WIDGET/assets/widget/script.js?key=CHIAVE_PUBBLICA_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 del widget non disponibile"));
      }
    };
    script.onerror = () => {
      script.remove();
      reject(new Error("Caricamento del widget non riuscito"));
    };
    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",
});

Per la modalità incorporata aggiungi &mode=embedded&target=chat-assistenza all’URL e monta prima il contenitore. Non duplicare questo caricamento con un tag script statico.

loadWidget() segnala la disponibilità dell’API. init() restituisce void: accoda il profilo se l’iframe non è pronto e non attende la sessione. Per l’esito usa isReady() e hasSessionError().


3. Campi di init()

Tutti i valori sono stringhe. Ometti quelli sconosciuti. Non inviare null, ID numerici o un’email vuota. Se un campo supera i limiti o l’email è malformata, la richiesta viene rifiutata.

Campo Limite e comportamento
external_id Massimo 100 caratteri. Riferimento aziendale confrontato esattamente con web_code, nell’organizzazione del widget. Gli spazi non vengono rimossi
company_name Massimo 250 caratteri dopo la rimozione degli spazi iniziali e finali. Crea un’azienda mancante solo insieme a external_id. Una stringa vuota o di soli spazi non abilita la creazione
name Massimo 250 caratteri. Nome del contatto; può aggiornarlo nella sessione corrente
email Indirizzo valido. Nell’inizializzazione pubblica viene salvata solo su un’azienda nuova. Non viene scritta sul contatto né su un’azienda già presente
phone Massimo 80 caratteri. Numero del contatto; preferisci il formato internazionale, per esempio +393331234567. Un conflitto con un numero esistente non unisce contatti o chat
external_user_id Facoltativo nel caso base. ID stabile della persona, non vuoto, massimo 100 caratteri dopo il trim. Da solo non ripristina la cronologia
restore_id Credenziale privata, massimo 200 caratteri, ottenuta dal backend. Richiede il corrispondente external_user_id. Usa il valore restituito senza modificarlo

Esempio quando il sito conosce l’account. I nomi dei campi nel gestionale possono essere diversi: mappa il riferimento azienda su external_id, la ragione sociale su company_name, la persona su name, l’email account su email.

widget.init({
  external_id: "52",
  company_name: "Ricambi Stella S.r.l.",
  name: "mario.rossi",
  phone: "+393331234567",
  email: "amministrazione@ricambi-stella.example",
});

Nome, telefono ed email del modulo e del gestionale si inviano con questi nomi: name, phone, email. Non usare Nome, Telefono o Email.

widget.init({
  name: "Mario Rossi",
  phone: "+393331234567",
});
widget.init({
  email: "mario.rossi@ricambi-stella.example",
});

Il primo esempio apre la chat con nome e telefono, senza cercare un’azienda. Il secondo basta con un’email valida. Nel profilo completo qui sopra, phone è il numero del contatto e email viene scritta sull’azienda solo se l’azienda viene creata in questa chiamata.

Mantieni separati name e company_name. Non concatenare la ragione sociale al nome utente per ottenere l’associazione. Non inviare un campo web_code: il nome pubblico è external_id. Non usare l’UUID interno di Quoting24 né companies.code al posto del valore in web_code.

init(profile) sostituisce il profilo memorizzato dal loader. Non fa merge: passa i campi che vuoi mantenere.

Un nuovo ospite può iniziare con almeno un nome, un telefono, un’email valida o un external_user_id, oppure con un’azienda trovata o creata tramite external_id. init({}) e company_name da solo non avviano una nuova sessione. Se invii soltanto un external_id sconosciuto, senza company_name, il widget chiede i dati tramite il modulo.

init({ restoreId: "..." }) non trasmette la credenziale. Il campo è restore_id.


4. Associare o creare l’azienda

Il flusso scatta quando Quoting24 elabora l’inizializzazione, anche senza un messaggio:

  1. Se riceve un external_id non vuoto, cerca un’azienda della stessa organizzazione con web_code uguale a quel valore
  2. Se la trova, collega il contatto. Nome ed email dell’azienda esistente restano invariati
  3. Se non la trova e riceve anche un company_name non vuoto, crea l’azienda con name = company_name, web_code = external_id ed eventuale email, poi collega il contatto
  4. Se non può trovare o creare l’azienda per dati mancanti, prosegue con il profilo disponibile. Se è insufficiente per un nuovo ospite, richiede il modulo. Gli errori di database o il limite richieste fanno fallire l’inizializzazione
Dati inviati Situazione in Quoting24 Risultato
external_id: "52", company_name: "Ricambi Stella S.r.l." Esiste web_code = "52" Usa l’azienda esistente; non ne modifica il nome
Stessi dati, più un’email valida Non esiste web_code = "52" Crea l’azienda con codice web 52, ragione sociale ed email, e associa il contatto
external_id: "52", name: "mario.rossi" Nessuna corrispondenza Avvia l’ospite senza una nuova associazione aziendale
external_id: "52", senza altri dati Esiste una corrispondenza Avvia l’ospite associato, con nome di default Visitatore
external_id: "52", senza altri dati Nessuna corrispondenza Richiede i dati del visitatore
company_name senza external_id, più name Qualsiasi Avvia il visitatore. Non cerca e non crea un’azienda

La ricerca non usa ragione sociale, email, telefono, partita IVA o companies.code. "052" e "52" sono valori diversi. Se importi prima l’anagrafica, compila web_code con lo stesso valore che il sito invierà in external_id.

L’associazione riguarda il contatto. Per un contatto già collegato, un nuovo codice valido può cambiarne l’azienda. Omettere il codice, o inviarne uno non risolto, non cancella l’associazione precedente. Al cambio di persona o account esegui reset(): non limitarti a cambiare il profilo.

Limiti

web_code non ha un vincolo di unicità. Con più corrispondenze viene selezionata la prima, ordinando per data di creazione crescente. Evita i duplicati: richieste simultanee che non trovano ancora l’azienda possono crearne più di una. Il flusso non garantisce una creazione atomica e non sostituisce import o sincronizzazione dal gestionale.

La creazione implicita è limitata a 20 tentativi per finestra di 60 secondi e indirizzo IP, nel processo backend che gestisce la richiesta. Al superamento, l’inizializzazione fallisce con TOO_MANY_REQUESTS. Usa una riprova con attesa. Evita cicli continui di init().

I dati arrivano dal browser e il visitatore può modificarli. L’associazione non dimostra appartenenza o identità e non concede la cronologia aziendale. Invia company_name se vuoi consentire questa creazione pubblica. Per aggiornare aziende esistenti usa Quoting24 o un’integrazione gestionale autorizzata.


5. Login, logout e cambio account

Mantieni un’unica istanza nella struttura principale di una SPA. Un cambio di pagina non richiede un nuovo script né un reset.

Al cambio di persona o account, e al logout, chiama reset() prima del nuovo profilo. Al ricaricamento con lo stesso login ancora attivo, usa init() senza reset, come nell’avvio: così conservi la sessione ospite salvata. Il sito deve distinguere il ricaricamento dal cambio effettivo.

Chiamata ai cambi di autenticazione. profile è il profilo del nuovo utente, oppure null al logout.

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);
}

Gestisci l’errore di caricamento nella UI del sito, con una riprova. Se recuperi il profilo dal backend prima di questa funzione, invalida anche quelle richieste al cambio utente. Durante l’attesa, cancella subito il profilo precedente con applyWidgetIdentity(null).

Il reset dimentica la sessione ospite di questa istanza, la credenziale salvata, il profilo e il contatore dei non letti. Non è una revoca server e non termina le sessioni su altri dispositivi. Il cambio di autenticazione va applicato anche nelle altre schede aperte.

Con ripristino della cronologia

Quando il profilo include restore_id, scaricalo dall’endpoint autenticato del sito dopo il reset(), e scarta la risposta se nel frattempo l’utente è cambiato. Il ricaricamento con lo stesso utente riusa la credenziale già salvata e non deve chiamare reset().

import { loadWidget } from "/assets/quoting24-loader.js";

let authRevision = 0;
let pendingRecovery;

// Solo al login, al logout e al cambio utente.
// Al ricaricamento con lo stesso utente non chiamarla: useresti reset() e perderesti la sessione già in corso.
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("Ripristino del widget non disponibile");
    const profile = await response.json();
    if (revision !== authRevision) return;
    if (profile.external_user_id !== expectedUserId || !profile.restore_id) {
      throw new Error("Risposta di ripristino inattesa");
    }

    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("Chat di assistenza: ripristino non riuscito.", error);
  }
}

Il ciclo copia solo i campi presenti e non vuoti. Un’email "" o un ID numerico non vanno inviati.

API JavaScript

Metodo Uso
init(profile) Fornisce o sostituisce il profilo corrente. Il loader non unisce i campi a un profilo precedente
open() / close() / toggle() Apre, chiude o alterna il widget flottante. La chiusura non è un logout. In modalità incorporata non hanno effetto
isReady() Inizializzazione della sessione visitatore riuscita. Resta falso durante il modulo di contatto
hasSessionError() Errore di sessione segnalato dall’iframe
reset() Cancella profilo, sessione locale, credenziale ospite salvata e contatore dei non letti
dispose() Smonta interfaccia e listener. Non cancella la credenziale ospite salvata né l’oggetto API globale

Puoi collegare un pulsante del sito a window.Quoting24Widget?.open() quando il pulsante flottante è comparso. Non aspettare isReady() per lasciare che un nuovo ospite apra il modulo. Non esiste un evento pubblico di disponibilità. Questi indicatori non descrivono l’intero stato della rete.

loadWidget() risolto non significa che branding e sessione siano pronti.

Smontaggio completo

Se devi togliere il widget, esegui dispose(). Se lo smontaggio coincide con il logout, esegui prima reset(). Prima di caricare di nuovo lo script, elimina il globale e azzera la Promise del loader.

export function removeWidget() {
  window.Quoting24Widget?.reset();
  window.Quoting24Widget?.dispose();
  delete window.Quoting24Widget;
  document.getElementById("quoting24-widget-script")?.remove();
  widgetPromise = undefined;
}

widgetPromise è quella definita nel modulo del loader. Esegui lo smontaggio dopo il completamento del caricamento. Non farlo in concorrenza con una nuova loadWidget(). Per i cambi di autenticazione usa applyWidgetIdentity() o applyWidgetAuth(), che attendono il loader.

Il reset locale non revoca il restore_id sul server.


6. Ripristino autenticato

La sessione ospite legge la sua conversazione più recente anche dopo la chiusura. Per le conversazioni visibili della stessa persona su un altro browser servono insieme external_user_id e 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,
});

Il recupero non unisce vecchie sessioni ospite e non condivide la cronologia tra colleghi della stessa azienda.

Endpoint sul backend del sito

Esempio da implementare nel portale, non un URL di Quoting24.

POST /api/widget/session
Content-Type: application/json

{}
{
  "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": "<credenziale-restituita-da-quoting24>"
}

Imposta Cache-Control: no-store. Identità e azienda solo dalla sessione server. Applica auth e CSRF dell’applicazione. Non loggare body che contengono la credenziale.

Se il portale espone già un endpoint autenticato che restituisce il restore_id dell’utente corrente per questo widget, usa quella risposta e combinala con l’ID persona e il riferimento azienda.

Provisioning server-to-server

Percorso tipico: /fastify/trpc/widget.provisionRestoreId
(oppure /trpc/widget.provisionRestoreId se il backend è esposto direttamente).

POST /fastify/trpc/widget.provisionRestoreId
Authorization: Bearer <SEGRETO_INTEGRAZIONE>
Content-Type: application/json

{
  "widget_api_key": "<uuid-pubblico-del-widget>",
  "external_user_id": "1042",
  "external_id": "52",
  "name": "Mario Rossi",
  "phone": "+393331234567",
  "email": "mario.rossi@ricambi-stella.example",
  "attempt_id": "1042"
}
{
  "result": {
    "data": {
      "restore_id": "<credenziale-opaca>"
    }
  }
}

I valori generati iniziano con q24-restore-v1:. Non inventarli e non derivarli da ID pubblici.

Input Requisito
widget_api_key UUID obbligatorio, autorizzato per il segreto
external_user_id ID persona stabile, uguale a quello in init()
attempt_id Intero positivo strettamente crescente, come stringa decimale, massimo 9223372036854775807
saved_restore_id Credenziale già salvata. Omettila al primo provisioning
external_id, name, email, phone Facoltativi, con gli stessi limiti di init()

Pattern ensure, recupera o crea:

  1. Autentica l’utente e acquisisci un lock sulla riga di ripristino, in transazione
  2. Alloca un nuovo attempt_id da sequenza dopo il lock (CACHE 1, non ciclica; la sequenza sopravvive al rollback)
  3. Chiama Quoting24 con attempt_id e, se presente, saved_restore_id
  4. Salva result.data.restore_id e fai commit
  5. Solo dopo il commit restituisci la credenziale al browser

Un saved_restore_id valido va riutilizzato. Ometterlo quando esiste già una chiave valida sostituisce la chiave e invalida le sessioni ripristinate. Non generare una chiave nuova a ogni page view.

Se il provisioning remoto riesce e il salvataggio locale fallisce, restituisci errore. Non restituire la chiave non salvata. Timeout limitati.

company_name si invia in init(). Il body di provisioning sopra non lo include: associazione e creazione dell’azienda, descritte nella sezione 4, avvengono quando il browser inizializza la sessione. L’email dell’inizializzazione pubblica è scritta solo su un’azienda nuova.

Esito lato widget

Quoting24 verifica la credenziale e carica la cronologia widget visibile di quell’utente. Le chat già chiuse possono comparire come conversazione precedente: consultarle non riapre la vecchia conversazione; l’invio resta sulla conversazione corrente.

Una credenziale non valida, di un altro utente o revocata viene rifiutata, senza conversione automatica in sessione ospite. Il pulsante «Riprova» del widget non richiede da solo un nuovo restore_id.


7. Pagina, rete e aspetto

  • Una sola istanza per pagina
  • CSP. Consenti l’origine del widget in script-src e frame-src, e l’origine API indicata per l’installazione in connect-src (caricamento delle impostazioni grafiche). Autorizza le immagini configurate in img-src. Il loader inserisce stili inline e non espone un’opzione pubblica per il nonce: verifica la CSP del sito. Il CSS della pagina non modifica l’interno dell’iframe cross-origin
  • Connessioni dell’iframe. Deve raggiungere API, Realtime/WebSocket e storage della propria installazione. Per filtri di rete usa gli host effettivi dell’ambiente
  • Aspetto e lingua. Logo, colore, posizione e messaggi si configurano in Quoting24. company_name è la ragione sociale del cliente. Non esiste un parametro pubblico language o locale; l’interfaccia attuale è in italiano
  • Mobile. La chat flottante può occupare il viewport. In modalità incorporata il sito controlla altezza e larghezza
  • Allegati e orari. PDF, DOC/DOCX, JPG/JPEG e PNG. Fino a 10 MiB per file e 100 MiB di allegati referenziati per conversazione. Fuori orario il widget mostra il banner e lascia disponibile la composizione

8. Verificare l’integrazione

Usa un account e un canale di test. Controlla l’anagrafica in Quoting24, oltre alla chat.

Prova Risultato atteso
Browser nuovo, solo script Modulo di contatto, poi conversazione
Payload completo con external_id già presente come web_code Contatto associato all’azienda esistente; nome ed email dell’azienda invariati
Payload completo con codice nuovo Azienda creata con codice web, ragione sociale ed email; contatto associato
Codice nuovo e nome, senza company_name Chat avviata, nessuna azienda creata
Solo codice nuovo Modulo di contatto
Solo ragione sociale e nome Nessuna azienda cercata o creata per nome
Email vuota, ID numerico o ragione sociale troppo lunga Payload rifiutato
Seconda init() nella stessa sessione Continua la sessione ospite. Email e telefono non recuperano la chat di un’altra persona
Ricarica, navigazione SPA, mobile Una sola istanza; continuità ospite se lo storage c’è; layout utilizzabile
Logout o cambio account durante il caricamento Sessione precedente cancellata; i risultati tardivi non ripristinano il vecchio profilo
Stesso utente autenticato, altro browser, restore_id valido Cronologia di quella persona
Due utenti della stessa azienda Credenziali e cronologie distinte
Ospite, poi ripristino reset() in mezzo; le sessioni non si uniscono
Credenziale non valida o revocata Errore, senza fallback ospite automatico
Conversazioni precedenti Solo consultazione; l’invio resta sulla corrente
Dominio non autorizzato Accesso rifiutato
Rete interrotta Riconnessione; bozza conservata se l’invio fallisce

Risoluzione dei problemi

Sintomo Controllo
Il pulsante non compare URL, chiave, canale abilitato, domini, CSP, richiesta widget.getBranding. Il solo globale JavaScript non prova che il pulsante esista. open() non ha effetto finché il branding non è caricato, e non ha effetto in modalità incorporata
Quoting24Widget non è definito Attendi il caricamento. Controlla il parametro key
La chat incorporata è invisibile Contenitore prima del loader, ID corretto, altezza maggiore di zero
Domain not allowed Nome host del sito, senza schema, porta e percorso. Verifica anche il collaudo. La pagina va servita via HTTP
Modulo telefono nonostante il profilo Ordine asincrono, snake_case, valori vuoti, reset() precedente, profilo insufficiente per un nuovo ospite
L’azienda non viene trovata Confronta external_id con web_code nella stessa organizzazione, spazi e zeri iniziali compresi
L’azienda mancante non viene creata Servono external_id e company_name entrambi non vuoti. Controlla validazione e limite di creazione
Nome o email dell’azienda non cambiano Previsto: init() crea le aziende mancanti e non aggiorna quelle esistenti
L’email non appare sul contatto Previsto per l’inizializzazione pubblica: l’email si usa solo alla creazione dell’azienda
TOO_MANY_REQUESTS Attendi e riprova. Evita chiamate ripetute o importazioni massive tramite il widget
La cronologia manca su un altro dispositivo Il payload azienda non la ripristina. Servono external_user_id e restore_id
Sessioni ripristinate invalidate Le chiamate ensure devono rimandare saved_restore_id. Non emettere una chiave nuova a ogni page view
«Riprova» non basta Nuovo restore_id dal tuo endpoint, poi reset() e init()
Dopo un cambio account resta la vecchia chat reset() subito al cambio identità. Scarta le risposte asincrone precedenti
Dopo dispose() lo script non ricrea il widget Elimina il globale e lo script, azzera la Promise del loader, poi ricarica
L’interfaccia resta in italiano Nessuna opzione pubblica language o locale sul loader

Riferimenti implementazione

  • Loader e API JavaScript: @frontend/src/public/widget/script.ts
  • Inizializzazione, associazione aziendale e creazione implicita: @backend/fastify/routes/widget/initialize.ts
  • Test di inizializzazione e aziende: @backend/fastify/__tests__/widget/widgetInitialize.test.ts
  • Dominio e canale: @backend/fastify/routes/widget/procedures/publicWidgetProcedure.ts
  • Provisioning restore_id: @backend/fastify/routes/widget/provisionRestoreId.ts