Retour à l'aperçu

Bonjour le monde : Tutoriel OpenIntegration

Notre tutoriel pour tous ceux qui cherchent un moyen de présenter leurs propres intégrations sur leur OpenPaper.

Avec ce court tutoriel, nous souhaitons vous montrer à quel point il est simple de programmer une extension OpenIntegration autonome, puis de l'utiliser comme plugin d'intégration dans l'application paperlesspaper.

Tout ce dont vous avez besoin sont quelques connaissances en HTML/CSS et dans l'utilisation de Docker, ainsi qu'un serveur ou une instance cloud chez le fournisseur de votre choix. Il est seulement important que le serveur soit accessible depuis Internet via un nom de domaine et un certificat SSL valide. Pour des tests réels, nous recommandons bien sûr notre OpenPaper7 ou OpenPaperL.

Alors, commençons !

Bildschirmfoto 2026-04-27 um 09

Voici donc notre structure de base. Nous avons besoin d'un Dockerfile pour empaqueter notre intégration dans un conteneur Docker. Comme une OpenIntegration doit renvoyer des fichiers HTML et la config.json via un serveur web à l'application Paperlesspaper, nous avons choisi NGINX comme serveur web — d'où la nginx.conf. L'image Docker que nous créons sera donc basée sur nginx:alpine.

Dans le /src dossier se trouvent les deux fichiers minimum nécessaires pour une OpenIntegration : config.json et index.html

Dans le config.json peuvent être renseignés les métadonnées de votre OpenIntegration : nom, description et numéro de version. De plus, c'est ici que sont configurés les paramètres que l'utilisateur de votre intégration pourra définir lui-même plus tard. Dans notre exemple, il s'agit seulement d'un champ texte pour saisir un nom.

Voici notre

json
{
  "name": "This one is a basic Hello-World example, which greets you with your name",
  "version": "1.0.0",
  "description": "A simple plugin that displays a greeting message based on the user's name.",
  "nativeSettings": {
    "orientation": "portrait"
  },
  "formSchema": {
    "type": "object",
    "properties": {
      "name": {
        "type": "string",
        "description": "The name of the user to greet.",
        "default": "OpenPaper-Lover"
      }
    }
  },
  "renderPage": "./index.html"
}

Sous nativeSettings vous pouvez transmettre des paramètres pour le cadre d'image, par exemple que votre intégration fonctionne uniquement en mode portrait. La formSchema partie définit notre champ de saisie de texte. Il est important d'indiquer le renderPage c'est le chemin relatif au sein de votre domaine serveur où se trouve le fichier qui renvoie l'image pour le cadre. Nous nous contentons ici d'un simple champ texte nommé : "name". Ceux qui veulent en savoir plus sur les possibilités de champs peuvent ici consulter la définition des types de l'interface.

Pour notre index.html nous n'avons besoin que de la structure HTML de base suivante :

html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Openintegration: Hello World</title>
  </head>
  <body>
    <div id="website-has-loading-element" class="hidden-marker"></div>
    <main class="screen">
      <section>
        <h2 class="subtitle">Hello</h2>
        <h1 class="title" id="name">World</h1>
        <h4 id="clock">00:00</h4>
      </section>
    </main>
  </body>
</html>

Il ne se passe pas grand-chose ici, à part l'affichage de Hello World. Le <div id="website-has-loading-element" class="hidden-marker"></div> indique à notre serveur de rendu que la page web n'est complètement chargée que lorsqu'un div avec id="website-has-loaded" est présent sur la page.

Si vous utilisez des appels asynchrones ou de gros graphiques dans votre intégration, cela peut être nécessaire. Sinon, notre serveur de rendu abandonne au bout de quelques secondes et ne rend que ce qui a été chargé jusque-là. Vous pouvez jeter un œil dans le navigateur pour vous faire une idée de l'apparence finale sur votre OpenPaper.

Cela devrait maintenant ressembler à peu près à ceci :

Das erste html wird auf dem Rahmen angezeigtdesign-htmlDas erste html wird auf dem Rahmen angezeigt

Comme nous voulons que ce soit un peu plus joli, nous devons ajouter un peu de CSS dans l'en-tête HTML :

html
<style>
      :root {
        color-scheme: light dark;
      }
      * {
        box-sizing: border-box;
      }

      html,
      body {
        width: 100%;
        height: 100%;
        margin: 0;
        padding: 0;
        font-family:
          ui-sans-serif,
          system-ui,
          -apple-system,
          BlinkMacSystemFont,
          "Segoe UI",
          sans-serif;
        background: #fff;
        color: #000;
      }

      .screen {
        width: 100vw;
        height: 100vh;
        padding: 1.3em;
        display: flex;
        align-items: center;
        justify-content: center;
      }

      .title {
        font-size: clamp(1rem, 1.4vw + 0.8rem, 2rem);
        font-weight: 600;
        text-align: center;
        margin: 0 0 0.2rem 0;
      }

      .subtitle {
        font-size: clamp(0.9rem, 0.9vw + 0.6rem, 1.3rem);
        opacity: 0.75;
        text-align: center;
        margin: 0 0 0.2rem 0;
      }

      #clock {
        font-size: clamp(0.5rem, 0.5vw + 0.2rem, 0.8rem);
        opacity: 0.25;
        text-align: center;
        margin: 5rem 0 0.2rem 0;
      }

      .hidden-marker {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
        white-space: nowrap;
      }
    </style>

Mieux, non ?

Jetzt mit Formatierungformatted-design-1Jetzt mit Formatierung

Tout ce dont nous avons encore besoin est un peu de JavaScript. En effet, nous voulons afficher Hello « le nom des paramètres », et non seulement « World ». De plus, nous ajoutons brièvement l'heure du rendu, ce qui est utile pour vérifier si l'image a bien été mise à jour après des modifications du code. Vous avez donc besoin du bloc de code suivant à la fin (avant la balise de fermeture body) de votre source :

html
<script>
    // live clock
    const clockElement = document.getElementById("clock");
    const now = new Date();
    const hh = String(now.getHours()).padStart(2, "0");
    const mm = String(now.getMinutes()).padStart(2, "0");
    clockElement.textContent = `${hh}:${mm}`;

    // mark page loaded
    const markLoaded = function () {
      const markerLoaded = document.createElement("div");
      markerLoaded.id = "website-has-loaded";
      document.body.appendChild(markerLoaded);
    };


    // Handling Settings Page Updates
    window.addEventListener("message", (event) => {
      const data = event.data;
      const payload = data.data || {};
      if (data.type === "INIT" && data.cmd === "message") {
        if (payload.meta?.pluginSettings?.name) {
          document.getElementById("name").textContent =
            payload.meta?.pluginSettings?.name;
        }
        markLoaded();
      }
    });
  </script>

Comme dans notre documentation décrite, nous ajoutons un écouteur d'événements à la page qui écoute l'événement message. Comme plusieurs messages sont échangés entre l'app et l'intégration, nous ne nous intéressons qu'aux messages lorsque ceux-ci ont le type="INIT" et cmd=="message" a. Le champ que nous avons créé précédemment dans la config.json pour le nom (name) se trouve dans l'objet JS reçu sous payload.meta?.pluginSettings?.name; . La chaîne contenue est maintenant écrite dans notre en-tête HTML ayant l'ID name.

Pour informer le moteur de rendu que nous avons terminé le chargement et l'affichage des données, la fonction markLoaded est appelée. Celle-ci ajoute à la fin du document un div avec l'id website-had-loaded. Terminé !

Si vous souhaitez rendre votre intégration disponible sur un hôte Docker, vous pouvez simplement utiliser le Dockerfile suivant :

dockerfile
FROM nginx:alpine
# Remove default nginx config and content
RUN rm /etc/nginx/conf.d/default.conf && \
    rm -rf /usr/share/nginx/html/*
# Copy optimized nginx config
COPY nginx.conf /etc/nginx/nginx.conf
# Copy static files
COPY src/ /usr/share/nginx/html/
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Comme vous le voyez, nous copions une nginx.conf dans le Dockerfile. Celle-ci est également assez simple. Il faut seulement veiller à ce que les en-têtes CORS soient correctement configurés :

plaintext
worker_processes auto;
events {
    worker_connections 1024;
}
http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    sendfile on;
    keepalive_timeout 65;
    server {
        listen 80;
        server_name _;
        add_header Access-Control-Allow-Origin "*" always;
        add_header Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Authorization, Content-Type, Accept, Origin, X-Requested-With" always;
        root /usr/share/nginx/html;
        index index.html;
        location / {
            if ($request_method = OPTIONS) {
                return 204;
            }
            try_files $uri $uri/ =404;
        }
    }
}

Les Access-Control-Allow-Origin doit être configuré de sorte qu'au moins web.paperlesspaper.de, api.paperlesspaper.de, capacitor://localhost (pour iOS) et http://localhost (pour Android) soient autorisés. Dans notre exemple, pour simplifier, nous autorisons toutes les URLs.


Comment ajouter votre intégration dans l'application paperlesspaper ? C'est simple :

  • Allez dans la bibliothèque de l'application et cliquez sur "Nouvelle image"
  • Dans la liste, choisissez "Plugin d'intégration"
Bildschirmfoto 2026-05-04 um 11

Dans la page de configuration qui apparaît, vous devez maintenant saisir un lien vers votre manifeste de configuration (config.json ).

Pour notre domaine d'exemple helloworld.paperlesspaper.de cela serait :

Bildschirmfoto 2026-05-04 um 12

Après avoir saisi l'URL de configuration de l'intégration et cliqué sur "Charger", le champ de saisie name. Ici, vous pouvez entrer le nom souhaité qui sera ensuite affiché sur l'OpenPaper.

Après avoir cliqué sur "Suivant" et choisi le cadre souhaité dans la boîte de dialogue suivante, votre intégration est active. L'affichage souhaité sera visible sur l'OpenPaper après le prochain intervalle de mise à jour.

C'est tout ! Le code source complet se trouve dans le dépôt GitHub Openintegration-HelloWorld.

Amusez-vous bien ! Pour questions et remarques, contactez support@paperlesspaper.de