# Bip — intégration

Cette doc explique **uniquement comment intégrer Bip sur un site**. Volontairement courte, structurée — collable telle quelle à un LLM qui fait l'intégration.

`PUBLIC_KEY` = clé du projet (format `bip_pk_xxxxxxxxxxxxxxxxxxxxxxxx`) fournie par l'admin Bip.

---

## 1. Snippet

Une seule balise dans le HTML, n'importe où dans `<head>` ou `<body>` :

```html
<script async src="https://bip.pop.run/PUBLIC_KEY.js"></script>
```

Si tu **régénères la clé** en admin, l'ancien fichier renvoie `404` — colle le nouveau snippet.
Si tu **désactives** le widget en admin, l'URL renvoie `"disabled";` et la bulle ne s'affiche pas — pas besoin de retirer le tag.

**Conseil — variabiliser pour éviter de hardcoder la clé :**

```bash
# .env
BIP_LOADER_URL=https://bip.pop.run/PUBLIC_KEY.js
```

```jsx
// Next.js / SSR
<script async src={process.env.BIP_LOADER_URL}></script>

// Vite / SPA (préfixe VITE_ obligatoire pour exposer au client)
<script async src={import.meta.env.VITE_BIP_LOADER_URL}></script>

// PHP / Twig / Django / EJS
<script async src="<?= getenv('BIP_LOADER_URL') ?>"></script>
```

---

## 2. Personnaliser l'apparence

Couleur et titre via `data-*` sur le tag. S'appliquent à la bulle, au header du drawer, aux messages user et au bouton "Envoyer".

```html
<script async src="https://bip.pop.run/PUBLIC_KEY.js"
  data-color="#7c3aed"
  data-text="#ffffff"
  data-title="ShotCV — Support"></script>
```

| Attribut     | Effet                          | Défaut    |
|--------------|--------------------------------|-----------|
| `data-color` | Couleur accent                 | `#18181b` |
| `data-text`  | Texte sur fond accent          | `#ffffff` |
| `data-title` | Titre du drawer (max 40 char)  | `Support` |

Formats acceptés pour les couleurs : `#rrggbb`, `#rgb`, `rgb(…)`, `rgba(…)`, `hsl(…)`, mots-clés CSS. Valeur invalide → fallback silencieux.

L'**icône de la bulle** (`?`, chat ou bug) est choisie côté admin Bip, pas en HTML.

---

## 3. Identifier l'utilisateur (optionnel)

`window.bip` est posé par le loader **dès qu'il s'exécute** (avant tout clic). Tu peux appeler `identify` / `reset` n'importe quand sans te soucier du timing.

### A. Site server-rendered

Identité connue au render du HTML :

```html
<script async src="https://bip.pop.run/PUBLIC_KEY.js"
  data-user-id="usr_123"
  data-user-email="alice@example.com"
  data-user-name="Alice"
  data-user-meta='{"plan":"pro"}'></script>
```

### B. SPA / app dynamique

Identité connue après login. Appel JS après login, `reset` au logout :

```js
window.bip.identify({
  id: 'usr_123',
  email: 'alice@example.com',
  name: 'Alice',
  metadata: { plan: 'pro' },
});
window.bip.reset(); // au logout
```

**Contraintes** : `data-user-meta` / `metadata` = JSON object, < 4 KB sérialisé. L'email passé via `identify` n'est jamais utilisé pour notifier — seul l'email saisi par le visiteur dans la banner chat sert aux notifications.

---

## 4. API JS — `window.bip`

| Méthode                        | Effet                                                  |
|--------------------------------|--------------------------------------------------------|
| `window.bip.open()`            | Ouvre le drawer (charge `b.js` puis `b-panel.js`)      |
| `window.bip.close()`           | Ferme le drawer                                        |
| `window.bip.toggle()`          | Toggle open/close                                      |
| `window.bip.identify({...})`   | Met à jour l'identité (merge avec l'existant)          |
| `window.bip.reset()`           | Oublie l'identité côté widget                          |

---

## 5. Activation conditionnelle par path

Côté admin (champ "Patterns de path"), tu peux limiter l'activation à certaines URLs du site. Vide = actif partout. Un pattern par ligne :

| Pattern              | Match                                                   |
|----------------------|---------------------------------------------------------|
| `/hub`               | `/hub`, `/hub/`, `/hub/x`, `/hub/x/y` (smart prefix)    |
| `/hub/*`             | `/hub/x` mais PAS `/hub/x/y` (un segment)               |
| `/hub/**`            | `/hub/x`, `/hub/x/y`, `/hub/x/y/z` (récursif)           |
| `/blog/*/edit`       | `/blog/123/edit` (wildcard au milieu)                   |
| `*`                  | match tout                                              |
| `!/admin`            | EXCLUT `/admin` et ses sous-chemins                     |

**Recettes** :

- Tout sauf la landing → `!/` seul (`/` en smart prefix matche uniquement `/` exact)
- Tout sauf l'admin → `!/admin` seul
- `/app` mais pas `/app/internal` → `/app` + `!/app/internal`

Logique : les négations vétoent toujours. S'il n'y a que des négations, tout le reste est autorisé.

---

## 6. Multi-domaines (origins)

Un même projet peut tourner sur plusieurs domaines (`shotcv.fr`, `shotcv.com`, etc.). Dans l'admin, champ "Origins" — une URL par ligne :

```
https://shotcv.fr
https://shotcv.com
https://www.shotcv.fr
```

Toutes les origines listées passent le CORS. HTTPS obligatoire sauf `localhost`. Pas de wildcards : `https://x.fr` ≠ `https://www.x.fr`, il faut lister chacun.

---

## 7. Screenshots — images cross-origin

Quand le visiteur signale un bug, Bip prend automatiquement un screenshot full-page via **snapDOM**. Pour produire l'image, le navigateur lit chaque `<img>` du DOM avec `canvas.drawImage(...)`. Si une image vient d'un autre domaine (CDN, S3, sous-domaine séparé, etc.) **et n'est pas autorisée par CORS**, le canvas devient "tainted" et l'image apparaît **blanche** dans la capture — avec un warning console :

```
[snapDOM] Network/CORS issue while fetching dataURL https://… A proxy may be required
```

### Comment l'éviter

**Côté serveur** — ajouter `Access-Control-Allow-Origin: *` sur les réponses des images.

Caddy :

```caddyfile
# Dans le bloc du domaine qui sert tes images :
@public-images path /screenshots/* /uploads/* /img/*
header @public-images Access-Control-Allow-Origin "*"
```

Nginx :

```nginx
location /screenshots/ {
  add_header Access-Control-Allow-Origin "*";
}
```

Cloudflare / S3 / R2 : poser une rule ou une CORS policy équivalente.

**Côté HTML** — ajouter `crossorigin="anonymous"` sur les balises `<img>` qui doivent être capturables :

```html
<img src="https://shotcv.com/screenshots/01-refit.png" crossorigin="anonymous" alt="…" />
```

⚠️ **Ordre de déploiement** : pousse **d'abord** le header CORS côté serveur, **ensuite** seulement le `crossorigin` côté HTML — sinon le browser refuse les images qui n'ont pas répondu avec `ACAO` et elles tombent en `onError`.

### Pourquoi les deux sont nécessaires

- Sans `crossorigin` sur la balise : le browser charge l'image en mode "no-CORS" (sans vérifier le header `ACAO`), elle s'affiche normalement mais tainte le canvas → snapDOM ne peut pas l'inliner.
- Sans `Access-Control-Allow-Origin` côté serveur : le navigateur refuse une image taggée `crossorigin` qui n'a pas le bon header → `onError`.

Les deux sont coordonnés : `crossorigin` côté HTML *demande* le mode CORS au browser, le header côté serveur *autorise* la réponse à être utilisée par le canvas.

### Si tu ne contrôles pas l'image (CDN tiers)

Tu ne peux rien faire : le warn reste, l'image est blanche dans le screenshot, mais le rapport part quand même. Le reste de la page est capturé normalement.

---

## 8. Content-Security-Policy

Si ton site a une CSP stricte, ajoute `https://bip.pop.run` aux directives :

| Directive          | Action                                                                       |
|--------------------|------------------------------------------------------------------------------|
| `script-src`       | Ajouter `https://bip.pop.run` — couvre loader + `b.js` + chunks lazy              |
| `script-src-elem`  | Si défini explicitement, ajouter aussi `https://bip.pop.run`                      |
| `connect-src`      | Ajouter `https://bip.pop.run` — pour `/api/public/*`, `import()`, SSE             |
| `style-src`        | Doit autoriser `'unsafe-inline'` — loader + bundle injectent du CSS inline   |
| `img-src`          | Ajouter `blob:` (previews via `URL.createObjectURL`). Icônes = SVG inline, pas de `data:` ; polices système, pas de `font-src` |

La CSP corrigée est celle du site hôte (header HTTP ou `<meta http-equiv>`), pas celle de Bip. Avec un `default-src 'self'`, chaque directive ci-dessus doit être ajoutée explicitement : une directive spécifique remplace le repli, elle ne s'y ajoute pas.

### Identifier la directive en cause

Le terme qui suit « violates the following Content Security Policy directive » indique la ligne à corriger :

| Message console (extrait)                                              | Fix                              |
|------------------------------------------------------------------------|----------------------------------|
| `…script '….js' violates … "script-src 'self'"`                        | `script-src` += `https://bip.pop.run` |
| `Refused to connect to '….../api/public/…' … "connect-src 'self'"`     | `connect-src` += `https://bip.pop.run`|
| `Refused to apply inline style … "style-src 'self'"`                    | `style-src` += `'unsafe-inline'` |
| `Loading the image 'blob:…' violates … "img-src …"`                     | `img-src` += `blob:`             |

### Exemple d'en-tête minimal compatible Bip

```
Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://bip.pop.run;
  connect-src 'self' https://bip.pop.run;
  style-src 'self' 'unsafe-inline';
  img-src 'self' blob:;
```

### Nonces

Le tag `<script src=".../PUBLIC_KEY.js">` que tu colles peut porter ton nonce. Le `<script>` que le loader inject ensuite (pour `b.js`) **hérite** du whitelist `script-src https://bip.pop.run` — pas besoin de nonce sur lui.

---

## 9. Troubleshooting

| Symptôme                                              | Cause / fix                                                                 |
|-------------------------------------------------------|-----------------------------------------------------------------------------|
| `[bip] no siteId found` en console                    | Tu as embed `/b.js` directement. Remplace par `/PUBLIC_KEY.js`.             |
| Loader retourne `404`                                 | La clé a été régénérée ou le projet supprimé en admin.                      |
| Loader retourne `"disabled";` (pas de bulle)          | Toggle "Widget activé" est OFF côté admin.                                  |
| Bulle invisible sur certaines pages                   | "Patterns de path" configuré en admin et l'URL ne match aucun pattern.      |
| Erreur CSP `Loading the script … violates`            | Voir section 8 — ajouter `https://bip.pop.run` à `script-src` / `connect-src`.   |
| Erreur CSP `Loading the image 'blob:' violates`       | Ajouter `blob:` à `img-src` (voir section 8).                               |
| `[snapDOM] Network/CORS issue while fetching dataURL` | Image cross-origin sans header CORS — voir section 7.                       |
| Bulle visible mais drawer ne s'ouvre pas              | Erreur réseau au chargement de `b.js` / `b-panel.js`. Vérifier console + origin déclarée en admin. |
| `403 chat_disabled` / `bug_report_disabled`           | Toggle correspondant est OFF côté admin.                                    |
| `403 cors` ou requête bloquée par le browser          | Origin du site pas dans la liste "Origins" du projet en admin.              |
| `429 too_many_requests` sur l'API chat                | Rate-limit 30 messages / min par (IP, projet).                              |
| `503 llm_unavailable`                                 | Clé Anthropic manquante ou API down côté Bip. Le bug-report reste OK.       |
