# MMM-AirQuality – Bug fetch failed / ETIMEDOUT

#### Symptômes

- MagicMirror démarre mais log PM2 affiche :
    
    
    - `TypeError: fetch failed`
    - `AggregateError [ETIMEDOUT]` dans `MMM-AirQuality/helper.js`
- `curl https://api.waqi.info/...` fonctionne

---

#### Objectif

👉 Contourner `fetch` de Node en remplaçant l’appel API par `https.request`.

---

#### Étapes rapides

1. **Aller dans le dossier du module**
    
    <div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary"><div class="sticky top-9"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2">  
    </div></div><div class="overflow-y-auto p-4" dir="ltr">  
    </div></div>```shell
    cd ~/MagicMirror/modules/MMM-AirQuality
    ```
2. **Sauvegarder le helper**
    
    <div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary"><div class="sticky top-9"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2">  
    </div></div><div class="overflow-y-auto p-4" dir="ltr">  
    </div></div>```
    cp helper.js helper.js.bak
    ```
3. **Éditer `helper.js`**
    
    <div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary"><div class="sticky top-9"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2">  
    </div></div><div class="overflow-y-auto p-4" dir="ltr">  
    </div></div>```
    nano helper.js
    ```
4. **Remplacer tout le contenu**
    
    ```javascript
    /* MagicMirror²
     * Module: MMM-AirQuality
     *
     * By Christopher Fenner https://github.com/CFenner
     * Patched to use https.request instead of fetch (ETIMEDOUT issue).
     * MIT Licensed.
     */
    
    const https = require("https");
    const { URL } = require("url");
    
    module.exports = {
      notifications: {
        DATA: "AIR_QUALITY_DATA",
        DATA_RESPONSE: "AIR_QUALITY_DATA_RESPONSE",
      },
    
      start: function () {
        console.log("AirQuality helper started ...");
      },
    
      loadData: function (payload) {
        const self = this;
        const urlString = `https://${payload.config.apiBase}${payload.config.dataEndpoint}${payload.config.location}/?token=${payload.config.token}`;
    
        console.log(`AirQuality-Fetcher (https): ${urlString}`);
    
        let url;
        try {
          url = new URL(urlString);
        } catch (e) {
          console.error("AirQuality-Fetcher: invalid URL", e);
          self.sendSocketNotification(self.notifications.DATA_RESPONSE, {
            payloadReturn: null,
            status: "ERROR",
            identifier: payload.identifier,
          });
          return;
        }
    
        const options = {
          hostname: url.hostname,
          port: 443,
          path: url.pathname + url.search,
          method: "GET",
          timeout: 10000, // 10s
        };
    
        const req = https.request(options, (res) => {
          let data = "";
    
          res.on("data", (chunk) => {
            data += chunk;
          });
    
          res.on("end", () => {
            try {
              const json = JSON.parse(data);
              self.sendSocketNotification(self.notifications.DATA_RESPONSE, {
                payloadReturn: json,
                status: "OK",
                identifier: payload.identifier,
              });
            } catch (err) {
              console.error("AirQuality-Fetcher: JSON parse error", err);
              self.sendSocketNotification(self.notifications.DATA_RESPONSE, {
                payloadReturn: null,
                status: "ERROR",
                identifier: payload.identifier,
              });
            }
          });
        });
    
        req.on("error", (err) => {
          console.error("AirQuality-Fetcher: https error", err);
          self.sendSocketNotification(self.notifications.DATA_RESPONSE, {
            payloadReturn: null,
            status: "ERROR",
            identifier: payload.identifier,
          });
        });
    
        req.on("timeout", () => {
          console.error("AirQuality-Fetcher: request timeout");
          req.destroy();
          self.sendSocketNotification(self.notifications.DATA_RESPONSE, {
            payloadReturn: null,
            status: "TIMEOUT",
            identifier: payload.identifier,
          });
        });
    
        req.end();
      },
    
      socketNotificationReceived: function (notification, payload) {
        switch (notification) {
          case this.notifications.DATA:
            console.log(
              `AirQuality-Fetcher: Loading data of ${payload.config.location} for module ${payload.identifier}`
            );
            this.loadData(payload);
            break;
        }
      },
    };
    
    ```
5. **Redémarrer MagicMirror**
    
    <div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary"><div class="sticky top-9"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2">  
    </div></div><div class="overflow-y-auto p-4" dir="ltr"></div></div>```
    pm2 restart mm
    ```
6. **Vérifier**
    
    
    - Plus d’erreur `fetch failed / ETIMEDOUT`
    - MMM-AirQuality affiche à nouveau les données

---

#### À noter

- En cas de mise à jour du module, le patch peut être écrasé → **réappliquer `helper.js` patché**.