Salta ai contenuti

Integra DUST Go

DUST Go è un browser mobile incorporato: carica la tua applicazione web in una WebView ed espone alla pagina l’hardware di scansione DUST del dispositivo tramite un piccolo bridge JavaScript, @dustid/dust-go-connect. Puoi sviluppare e ospitare una normale applicazione web; DUST Go fornisce la fotocamera, gli accessori ottici e la pipeline di acquisizione, senza richiedere una toolchain nativa.

Questa pagina ti guida fino a ottenere una scansione risolta. Tutto ciò che va oltre — accesso, geolocalizzazione, compatibilità, hardware desktop — è descritto nella sezione Oltre la prima scansione più avanti.

Prima di iniziare

Ruolo
Una credenziale di un account di servizio conservata dal tuo backend
Dispositivo
Un iPhone con DUST Go e un accessorio Loupe supportato per l'acquisizione DUST
  1. Un utente apre la tua applicazione web all’interno di DUST Go (tramite un link dell’app).
  2. La pagina importa @dustid/dust-go-connect; la libreria rileva il bridge di DUST Go ed espone un connector.
  3. La pagina chiama scanAsync(). DUST Go apre lo scanner nativo sopra la pagina.
  4. Al momento dell’acquisizione, DUST Go restituisce alla pagina un evento di scansione: il payload contiene i dati e i metadati dell’acquisizione.
  5. La pagina invia l’acquisizione al tuo backend, che chiama APID (/api/v1/tags/identify, /bind o /verify) per risolverla.

Una scansione DUST fornisce alla pagina un’acquisizione grezza (un JPEG codificato in base64) e i relativi metadati, non un Identificatore risolto. L’identificazione, la verifica e l’associazione avvengono tutte sul server.

Terminal window
npm install @dustid/dust-go-connect

Il pacchetto non ha dipendenze, è distribuito con licenza MIT e include i tipi TypeScript.

L’esportazione connector è undefined quando la pagina non viene eseguita all’interno di DUST Go, quindi la stessa build dell’applicazione può essere usata sia nei browser normali sia in DUST Go:

dust-go.ts — runs in the BROWSER
import { connector } from "@dustid/dust-go-connect";
export const insideDustGo = Boolean(connector);

Due aspetti da conoscere:

  • Momento dell’importazione. Il rilevamento avviene nel browser al momento dell’importazione del modulo. Se utilizzi il rendering lato server, consenti l’uso del connettore soltanto dopo l’idratazione.
  • Rilevamento lato server. Le build recenti di DUST Go contrassegnano anche lo User-Agent della WebView con DustGo/<version> (<app id>), consentendo al server di rilevare l’applicazione prima dell’esecuzione di qualsiasi JavaScript. Consideralo un’indicazione, non un confine di sicurezza: chiunque può falsificare uno User-Agent.

Il percorso più semplice è l’API basata sulle promise: mostra lo scanner e attendi una singola acquisizione:

scan.ts — runs in the BROWSER
import { scanAsync } from "@dustid/dust-go-connect";
const payload = await scanAsync();
// payload: { type: 'DUST' | 'QR' | 'BARCODE' | 'DATA_MATRIX' | 'NFC',
// data: string, metadata?: ScanMetadata }

scanAsync() restituisce una Promise<ScanPayload>. Viene rifiutata se l’utente chiude lo scanner senza effettuare un’acquisizione e viene rifiutata immediatamente se chiamata al di fuori di DUST Go (quando non è presente alcun connettore); verifica quindi prima connector se lo stesso percorso di codice viene eseguito anche nei browser normali.

payload.typepayload.dataNote
DUSTJPEG dell’acquisizione DUST codificato in base64Di grandi dimensioni; risolvilo lato server tramite APID
QR, BARCODE, DATA_MATRIXContenuto decodificato del simbolo
NFCID esadecimale letto dal chip NFC

payload.metadata (di tipo ScanMetadata) descrive l’acquisizione: identificatori del dispositivo (deviceId, modelName, osName, osVersion, appVersion), selezione dell’obiettivo e della fotocamera e, facoltativamente, campi relativi all’ottica (zoom, messa a fuoco, esposizione, ISO), geolocalizzazione (latitude/longitude/accuracy) e dettagli sull’origine dell’acquisizione per gli accessori di scansione esterni (captureSource, usbVendorId, usbProductId, dragonBackend). Inoltralo senza modificarlo durante l’associazione: APID lo archivia insieme all’Identificatore.

Leggi questa sezione prima del passaggio di risoluzione: determina la struttura dell’intera integrazione.

Il passaggio di risoluzione comprende quindi due file in due posizioni:

FileAmbiente di esecuzioneContiene la credenziale DUST?
La tua pagina di scansioneBrowser all’interno di DUST GoNo
Il tuo gestore /api/dust-scanIl tuo serverSì

L’acquisizione DUST diventa utile soltanto quando APID ha trovato una corrispondenza. La pagina decodifica in binario l’acquisizione in base64 e la invia al tuo endpoint:

identify.ts — runs in the BROWSER
// No DUST credential in this file.
async function identifyDustScan(base64Jpeg: string) {
// The scan arrives base64-encoded; APID expects binary multipart data.
const bytes = Uint8Array.from(atob(base64Jpeg), (c) => c.charCodeAt(0));
const form = new FormData();
form.set("operation", "identify");
form.set("tagType", "DUST");
form.set("data", new Blob([bytes], { type: "image/jpeg" }));
const response = await fetch("/api/dust-scan", {
method: "POST",
body: form,
credentials: "same-origin",
});
return { status: response.status, body: await response.json() };
}

Il backend aggiunge la credenziale e il contesto e definisce direttamente l’ambito della ricerca:

server/dust-scan.ts — runs on YOUR SERVER
export async function handleIdentify(form: FormData, session: Session) {
// `searchTeamIds` is a JSON array of Team UUIDs. Set it on the server — a
// browser-supplied scope is a request, never an authorization.
form.set("searchTeamIds", JSON.stringify(session.allowedTeamIds));
const response = await fetch("https://apid.dustid.io/api/v1/tags/identify", {
method: "POST",
headers: {
Authorization: `Bearer ${await getDustToken()}`,
"Dust-Ctx-Org-Id": session.organizationId,
},
body: form,
});
// Pass the status and body through unchanged: the page needs to tell
// "nothing matched" (404 IDENTIFIER_NOT_FOUND) from "try again"
// (503 SCAN_SEARCH_INCOMPLETE).
return new Response(await response.text(), {
status: response.status,
headers: { "Content-Type": "application/json" },
});
}

Il campo è searchTeamIds. I payload di identificazione rifiutano le proprietà non dichiarate, quindi la grafia legacy searchGroupIds fa fallire l’intera richiesta con 400 INVALID_REQUEST anziché essere ignorata. L’unico nome legacy group ancora supportato è l’header Dust-Ctx-Grp-Id, tuttora accettato come alias di Dust-Ctx-Team-Id.

La stessa struttura multipart viene utilizzata per le altre due operazioni:

  • /api/v1/tags/bind — aggiungi threadId. options.enrollmentSessionId è facoltativo: fornisci un UUID generato dal client e riutilizzato per l’intera esecuzione quando più acquisizioni fanno parte dello stesso gruppo (per esempio, una postazione di registrazione che elabora un lotto); omettilo per un’associazione singola.
  • /api/v1/tags/verify — aggiungi threadId e tags, un array di oggetti ([{ "tagId": "…", "tagType": "DUST" }], codificato come JSON nel multipart), non stringhe di ID. tags è obbligatorio.

Consulta Identificatori per i contratti completi di richiesta e risposta e Scanner React per un componente pronto da copiare che implementa tutte e tre le operazioni tramite un gestore backend come quello mostrato sopra.

  1. Installa DUST Go dall’App Store o da Google Play (consulta Dispositivi supportati per i requisiti hardware: l’acquisizione DUST richiede un accessorio ottico supportato).
  2. Pubblica l’applicazione tramite HTTPS a un URL raggiungibile dal dispositivo (per lo sviluppo è sufficiente un indirizzo LAN).
  3. In DUST Go, aggiungi il tuo URL come link dell’app personalizzato e aprilo.
  4. Verifica che il connettore venga rilevato, quindi esegui una scansione.
  5. Verifica che l’acquisizione raggiunga il backend e che la chiamata DUST effettuata dal backend restituisca un risultato visualizzabile, incluso il caso «nessuna corrispondenza».

Nel repository del pacchetto è disponibile una pagina diagnostica minimale che verifica l’intero bridge (rilevamento, scanAsync, registro degli eventi).


Tutto ciò che segue è facoltativo. Torna a questa sezione dopo aver risolto correttamente una singola scansione dall’inizio alla fine.

Per i flussi in cui si eseguono più scansioni prima dell’invio, utilizza l’API listener: lo scanner rimane aperto tra un’acquisizione e l’altra.

scan-many.ts — runs in the BROWSER
import { connector } from "@dustid/dust-go-connect";
connector?.add("my-listener", (event) => {
switch (event.type) {
case "scan":
handleScan(event.payload);
break;
case "hide": // scanner closed
case "show": // scanner opened
break;
default:
// Ignore unknown event types — the protocol may grow.
break;
}
});
connector?.showScanner();
// later: connector?.hideScanner(); connector?.remove("my-listener");

addScanListener offre la stessa funzionalità senza dover gestire il listener: riceve ogni scansione e restituisce una propria funzione per annullare l’iscrizione:

scan-many-simple.ts — runs in the BROWSER
import { addScanListener } from "@dustid/dust-go-connect";
const stop = addScanListener((payload) => handleScan(payload));
// later: stop();

Una singola sessione dello scanner può produrre una serie di acquisizioni; inviale quindi una alla volta. Un payload DUST è un JPEG base64 di grandi dimensioni: caricarne diversi contemporaneamente satura la connessione e non offre all’operatore alcuna indicazione utile sull’avanzamento. Metti i payload in coda e attendi il completamento di ogni invio prima di avviare quello successivo.

Le funzionalità variano in base all’host: un telefono può regolare lo zoom e l’esposizione, un accessorio microscopio USB espone una serie di controlli diversa e una build meno recente potrebbe non esporne alcuno. Interroga l’host anziché dedurne le funzionalità dallo User-Agent:

capabilities.ts — runs in the BROWSER
const capabilities = connector?.getCapabilities?.();

Se l’annuncio delle funzionalità è assente o vuoto, considera disponibili soltanto acquisizioni singole, senza alcuna regolazione. Non interpretare mai il silenzio come una funzionalità.

Le normali chiamate a navigator.geolocation funzionano all’interno di DUST Go: l’applicazione le inoltra in modo trasparente alla richiesta di autorizzazione del sistema operativo nativo. Non è necessario alcun codice del connettore.

Se l’applicazione utilizza OAuth/OIDC, tieni presente che DUST Go trasferisce al browser di sistema la navigazione verso provider di identità esterni e che il callback torna alla pagina tramite uno schema URL personalizzato.

Quando costruisci un redirect_uri OAuth nella pagina, elaboralo sempre così:

redirect.ts — runs in the BROWSER
import { connector } from "@dustid/dust-go-connect";
const origin = window.location.origin;
const redirectUri = (
connector?.rewriteRedirect(new URL(`${origin}/auth/callback`)) ??
new URL(`${origin}/auth/callback`)
).href;

Al di fuori di DUST Go, e negli host in cui non è necessaria alcuna riscrittura, questa operazione non produce modifiche. All’interno di DUST Go sostituisce il protocollo dell’URL con lo schema personalizzato dell’applicazione, generando un URI come com.dustidentity.dustgo://your-host/auth/callback (lo schema esatto dipende dalla build di DUST Go; dustgo è il valore di ripiego), che il provider di identità deve aver registrato come URI di reindirizzamento consentito.

Il bridge utilizza un protocollo numerato. La pagina annuncia gli eventi che è in grado di gestire mediante un handshake automatico hello eseguito al momento dell’importazione, mentre l’host invia soltanto gli eventi dichiarati dalla pagina. In questo modo, un host meno recente e una pagina più recente continuano a funzionare insieme riducendo le funzionalità anziché generando un errore.

Leggi il numero dal pacchetto installato, non da questa pagina: è esportato dall’SDK.

import { CONNECT_PROTOCOL_VERSION } from "@dustid/dust-go-connect";
ProtocolloFunzionalità aggiunte
1Il contratto legacy implicito: scan / hide / show, senza handshake.
2L’handshake hello e l’evento calibrationResult.
3L’annuncio capabilities dall’host alla pagina, il comando runCapture dalla pagina all’host e l’evento captureStatus che risponde al comando.
4CaptureRequirements.app (criteri della versione dell’app imposti dall’host) e CaptureCapabilities.enforcedRequirements, affinché una pagina possa distinguere un host che controlla un requisito da uno che lo ignorerebbe.
5ScanPayload.exif: i dati EXIF fotografici dell’acquisizione e la relativa provenienza. Un’aggiunta pura.
6Il comando capturePhoto dalla pagina all’host e l’evento photo che risponde al comando: una semplice fotografia (per esempio, un allegato di una Scheda), volutamente non una scansione.

L’SDK nell’albero dei sorgenti di questa documentazione è @dustid/dust-go-connect 0.2.0 e utilizza il protocollo 6. Questa pagina non fa alcuna affermazione sulla versione attualmente disponibile nel registro npm pubblico: controlla CONNECT_PROTOCOL_VERSION e il campo version del pacchetto effettivamente installato e contatta DUST Identity se hai bisogno di una versione specifica.

Regole che restano valide indipendentemente dal numero di versione:

  • Il rilevamento delle funzionalità in fase di esecuzione è la fonte autorevole. Il numero di protocollo indica ciò che l’SDK della pagina è in grado di esprimere; non dice nulla sull’host all’altro capo né sull’hardware collegato. Interroga getCapabilities() e, se l’annuncio è assente, considera disponibili «soltanto acquisizioni singole, senza alcuna regolazione».
  • Esegui lo switch su event.type e ignora ciò che non riconosci. Vengono aggiunti nuovi tipi di evento; una pagina che genera un errore in presenza di un tipo sconosciuto si interrompe dopo un aggiornamento dell’host che non avrebbe dovuto influenzarla.
  • Un controllo manuale nell’interfaccia di un host non costituisce una funzionalità del protocollo e il successo di un comando hardware non dimostra che il valore sia stato applicato al momento dell’acquisizione.
  • Gli eventi calibrationResult e ackCalibrationResults() sono meccanismi interni dei flussi di lavoro di calibrazione proprietari di DUST; le integrazioni di terze parti possono ignorarli.

Questa sezione riguarda un Dragon collegato a un computer desktop e controllato da un browser normale tramite l’applicazione complementare. Non riguarda il percorso Android: all’interno di DUST Go su Android, l’applicazione controlla direttamente il Dragon collegato e le sue acquisizioni arrivano tramite il normale flusso runCapture descritto sopra; createDragonClient() non è coinvolto e sul telefono non viene installata alcuna applicazione complementare. Consulta Dispositivi supportati.

Il tuo sito web può gestire l’anteprima di Dragon e i relativi controlli di messa a fuoco. L’applicazione complementare installata gestisce in background i comandi USB. Al primo utilizzo, gli utenti approvano il tuo sito web in DUST Camera Controls e concedono, quando richiesto, l’accesso del browser alla fotocamera e ai dispositivi locali. L’approvazione viene memorizzata per quel sito web e quel browser. Il sito web deve utilizzare HTTPS. Camera Controls può rimanere nella barra dei menu con la finestra chiusa; non è necessario alcun popup di connessione. Non è richiesto alcun sito web ospitato da DUST.

Installa DUST Camera Controls nella cartella Applicazioni e aprilo una volta. L’applicazione mostra se Dragon è collegato e rimane disponibile nella barra dei menu. L’avvio all’accesso è abilitato per impostazione predefinita; puoi disabilitarlo nell’applicazione.

I programmi di installazione per ciascuna piattaforma sono elencati nella pagina Download. Le distribuzioni con aggiornamenti abilitati verificano automaticamente la disponibilità di aggiornamenti e offrono Verifica aggiornamenti… nella barra dei menu. Sei tu a scegliere quando installare un aggiornamento; salva il lavoro e termina la scansione prima di riavviare l’applicazione. Le build di valutazione prive di un feed di aggiornamento richiedono un programma di installazione sostitutivo fornito da DUST.

In DICE, apri Scansione, seleziona DUST, attiva Controlli di messa a fuoco Dragon sotto lo scanner e scegli Connetti al primo utilizzo. Dopo l’approvazione, il riquadro di visualizzazione offre la messa a fuoco automatica, le impostazioni di messa a fuoco e Scansione tramite l’operazione selezionata. Quando torni a Scansione con questa modalità attiva, DICE si riconnette automaticamente se Camera Controls è in esecuzione e l’accesso del browser alla fotocamera è ancora autorizzato. Se le autorizzazioni del browser richiedono un intervento, scegli Connetti o Avvia fotocamera per completare la connessione.

dragon.ts — runs in the BROWSER
import { createDragonClient } from "@dustid/dust-go-connect";
const dragon = createDragonClient();
const video = document.querySelector("video")!;
// First use: request native approval and browser permissions from a user action.
connectButton.onclick = async () => {
try {
await dragon.connect();
await dragon.openVideo(video);
const controls = await dragon.getControls();
const focus = controls.find((control) => control.name === "focus");
// Use focus.min / focus.max for your manual slider when focus is available.
} catch (error) {
showConnectionError(error);
}
};
// On a later visit, reuse approval without creating a native approval prompt.
// If this rejects, present Connect Dragon; do not repeatedly request approval.
// await dragon.connect({ interactive: false });
// Reopen video only after browser camera permission has already been granted.
manualSlider.onchange = async () => {
await dragon.setControl("focus", Number(manualSlider.value));
};
autofocusButton.onclick = async () => {
try {
const result = await dragon.autofocus(video, {
radius: Number(autofocusRangeSlider.value),
onProgress: ({ position, sampled }) => showProgress(position, sampled),
});
manualSlider.value = String(result.position);
// "low-contrast" means autofocus retained the starting focus.
showFocusResult(result.outcome);
} catch (error) {
showFocusError(error);
}
};
cancelButton.onclick = () => dragon.cancelAutofocus();
captureButton.onclick = async () => {
const payload = await dragon.capture(video);
// Send the raw DUST JPEG to your server, which calls the Identifier API.
await sendToYourServer(payload);
};
const unsubscribe = dragon.onState(({ connected, controls }) => {
updateDeviceUI(connected, controls);
});
// On page/component teardown: unsubscribe(); dragon.dispose();

Una sola sessione di un sito web alla volta può controllare Dragon. L’utente può revocare l’accesso tramite Gestisci accesso dei siti web… in Camera Controls. La chiusura della finestra nativa lascia disponibile la connessione; la chiusura dell’applicazione la interrompe. Una sessione approvata riceve aggiornamenti sullo stato del dispositivo quando Dragon si disconnette o si riconnette. Riapri il video dopo la riconnessione. Il collegamento dell’hardware non concede implicitamente ai siti web l’accesso al dispositivo.

La messa a fuoco automatica esegue la ricerca intorno all’ultimo comando di messa a fuoco, entro i limiti dell’intervallo calibrato del dispositivo. Il relativo cursore dell’intervallo rappresenta una distanza su ciascun lato della posizione iniziale. Mantieni visibile la pagina e immobile il campione fisico, con il dettaglio desiderato al centro dell’immagine. Un campione fisico privo di dettagli o scarsamente illuminato potrebbe non offrire contrasto sufficiente per selezionare la messa a fuoco. Controlla l’immagine risultante prima dell’acquisizione.

I valori di messa a fuoco sono comandi, non posizioni misurate dell’obiettivo. Questi metodi di controllo manuale non garantiscono le impostazioni al momento dello scatto e non abilitano acquisizioni prescritte o composite. Lo scanner mobile e il relativo contratto scanAsync() esistente rimangono disponibili in modo indipendente.