Exemple : page de suivi de livraison
La page que votre client ouvre depuis le message « Votre commande est en route » : le livreur, la route jusqu’à sa porte, et l’heure d’arrivée.
Quand une commande part, votre client reçoit un message : « Votre commande est en route ». Le lien ouvre cette page : le livreur sur la carte, le point de livraison et l’heure d’arrivée, mis à jour à quelques secondes d’intervalle.
Pas besoin de code ? Chaque livraison a déjà une page de suivi toute prête : envoyez le trackingUrl de la tâche (https://nways.ng/t/…). Construisez votre propre page, comme ci-dessous, quand vous la voulez sur votre propre site, à vos couleurs, à côté des détails de la commande. Liens de suivi
Comment les pièces s’assemblent
- Votre appli livreur envoie les positions au suivi de flotte, et N-Ways tient à jour l’heure d’arrivée de la livraison.
- Votre serveur lit la livraison et son véhicule avec une clé serveur (les clés Flotte ne vont jamais dans une page web), et ne renvoie à votre page que ce que le client doit voir.
- La page l’affiche avec le SDK Maps, en utilisant une clé de site web restreinte à votre site et à Maps.
Le serveur
Node.js 18 ou plus récent, rien à installer. Il sert la page et /track/{order}.json.
// node server.mjs (NWAYS_SERVER_KEY dans l’environnement)
import { readFile } from 'node:fs/promises'
import { createServer } from 'node:http'
const KEY = process.env.NWAYS_SERVER_KEY
const fleet = async (path) => {
const res = await fetch('https://nways.ng/maps/v1/fleet' + path, { headers: { 'X-NWays-Api-Key': KEY } })
const data = await res.json()
if (!res.ok) throw Object.assign(new Error(data.error?.message), { status: res.status, reason: data.error?.reason })
return data
}
// Ce que le client peut voir de sa commande : ni nom ni téléphone du livreur, ni historique.
async function tracking(orderId) {
const task = await fleet(`/tasks/${encodeURIComponent(orderId)}`)
const vehicle = task.vehicleId ? await fleet(`/vehicles/${encodeURIComponent(task.vehicleId)}`) : null
const live = ['assigned', 'picked_up', 'arriving'].includes(task.status)
return {
status: task.status,
dropoff: { lat: task.dropoff.lat, lng: task.dropoff.lng },
eta: task.eta ? { at: task.eta.at, minutes: Math.round(task.eta.durationS / 60) } : null,
rider: live && vehicle?.online && vehicle.lastLocation ? { lat: vehicle.lastLocation.lat, lng: vehicle.lastLocation.lng } : null,
}
}
createServer(async (req, res) => {
const m = req.url.match(/^\/track\/([\w.:-]{1,64})\.json$/)
try {
if (m) {
res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' })
return res.end(JSON.stringify(await tracking(m[1])))
}
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
res.end(await readFile(new URL('./track.html', import.meta.url)))
} catch (e) {
res.writeHead(e.reason === 'TASK_NOT_FOUND' ? 404 : 502, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ error: e.reason ?? 'ERROR' }))
}
}).listen(8080, () => console.log('http://localhost:8080/?order=order-1041'))La page
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="robots" content="noindex" />
<title>Votre livraison</title>
<link rel="stylesheet" href="https://nways.ng/sdk/v1/maplibre-gl.css" />
<style>
body { margin: 0; font: 16px/1.4 system-ui, sans-serif }
#map { height: 70vh }
#info { padding: 16px }
#info b { font-size: 1.5em; display: block }
</style>
</head>
<body>
<div id="map"></div>
<div id="info" aria-live="polite">Chargement…</div>
<script type="module">
import { createMap, maplibregl } from 'https://nways.ng/sdk/v1/nways-maps.standalone.js'
const order = new URLSearchParams(location.search).get('order')
const info = document.getElementById('info')
const map = createMap({ container: 'map', key: 'VOTRE_CLE_SITE', lang: 'fr', zoom: 14 })
const home = new maplibregl.Marker({ color: '#0b7a3e' })
const rider = new maplibregl.Marker({ color: '#1a73e8' })
const WORDS = { assigned: 'Votre livreur va chercher votre commande', picked_up: 'En route vers vous', arriving: 'Arrive maintenant', completed: 'Livrée', failed: 'Échec de la livraison', cancelled: 'Annulée', created: 'En préparation' }
let framed = false
async function update() {
const res = await fetch(`/track/${encodeURIComponent(order)}.json`)
if (!res.ok) return void (info.textContent = 'Nous ne trouvons pas cette livraison.')
const t = await res.json()
home.setLngLat([t.dropoff.lng, t.dropoff.lat]).addTo(map)
if (t.rider) rider.setLngLat([t.rider.lng, t.rider.lat]).addTo(map)
else rider.remove()
if (!framed) {
const box = new maplibregl.LngLatBounds([t.dropoff.lng, t.dropoff.lat], [t.dropoff.lng, t.dropoff.lat])
if (t.rider) box.extend([t.rider.lng, t.rider.lat])
map.fitBounds(box, { padding: 80, maxZoom: 16, duration: 0 })
framed = true
}
const when = t.eta ? new Date(t.eta.at).toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }) : null
info.innerHTML = ''
info.append(
Object.assign(document.createElement('b'), { textContent: when ? `Arrivée vers ${when}` : (WORDS[t.status] ?? t.status) }),
t.eta ? `${WORDS[t.status] ?? ''} · ${t.eta.minutes} min` : '',
)
if (['completed', 'failed', 'cancelled'].includes(t.status)) clearInterval(timer)
}
const timer = setInterval(update, 10000)
update()
</script>
</body>
</html>Remarques
- Deux clés.
NWAYS_SERVER_KEYest restreinte aux adresses IP de votre serveur et à Flotte.VOTRE_CLE_SITEest restreinte à votre site web et à Maps. ChaquecreateMapcompte pour un chargement de carte. - Confidentialité. La page ne montre le livreur que pendant la livraison et quand le véhicule est en ligne, et jamais son nom ni son numéro de téléphone. N’enregistrez pas les positions de vos clients dans la page.
- Mises à jour plus rapides. Interrogez le serveur toutes les 10 secondes, comme ici, ou poussez les changements vers la page depuis votre serveur quand les webhooks arrivent (
task.arriving,task.completed). - Français.
createMap({ lang: 'fr' })affiche les libellés de la carte en français, pour qu’ils correspondent aux textes de la page.