SocialHub.AI

Centre de développement · Étendre la plateforme

Pages intégrées — placez vos propres outils dans l’interface d’administration.

Enregistrez votre propre page hébergée sur https et elle deviendra un élément de menu autorisé dans l'administrateur SocialHub - une iframe pleine page en bac à sable. Flash remet à votre page un jeton d'identité sans PII de courte durée afin que vous puissiez autoriser l'opérateur, sans qu'il se reconnecte.

Accès anticipé · derrière le module d'extensibilité ouverte

Fonctionnement

Quatre étapes sont nécessaires pour accéder à un élément de menu depuis votre URL.

01

Enregistrer votre page

Un administrateur d'espace de travail ajoute votre URL HTTPS dans Flash → Configurer → Pages intégrées, avec un libellé de menu, une liste d'origines autorisées et les rôles/niveaux autorisés à y accéder.

02

Copier le secret de signature

Flash affiche une clé secrète de signature (slotsec_…). Stockez-le comme secret serveur dans votre application ; vous l'utilisez pour vérifier le jeton d'identité de Flash.

03

Flash encadre votre page

La page apparaît comme un élément du menu latéral sous « Applications » pour les opérateurs autorisés ; son ouverture charge votre URL dans une iframe isolée avec le paramètre ?flash_context=<token> ajouté.

04

Vérification et autorisation

Lecture du jeton, vérification côté serveur (HS256) et restriction de l'interface utilisateur en fonction de l'identifiant de l'espace de travail et du rôle de l'opérateur associés.

Le jeton d'identité

Éphémère, sans données personnelles, signature sécurisée

À chaque rendu, Flash ajoute ?flash_context=<JWT> à votre URL. Il s'agit d'un HS256 JWT signé avec le secret de votre page (vous - et non la plate-forme - êtes donc l'ancre de confiance), valable pendant au plus 120 secondes, et il ne contient aucune donnée personnelle du membre - seulement l'identifiant de l'espace de travail et l'identifiant opaque de l'opérateur + le rôle grossier, vous pouvez donc autoriser.

{
  "iss": "flash",
  "aud": "nav.page",
  "slotKey": "nav.page",
  "ctx": {
    "teamId": "team_...",   // the workspace (tenant) — the tenant already owns this
    "userId": "usr_...",    // opaque operator id (NOT an email/name/phone)
    "role":   "org_admin",  // coarse role: org_owner | org_admin | marketer | store_manager
    "pageType": "nav.page",
    "locale": "en"
  },
  "jti": "…", "iat": 1730000000, "nbf": 1730000000,
  "exp": 1730000110          // <= 120s after iat
}

Vérifiez-le.

Faites confiance au jeton, pas à la requête.

Vérifiez la signature, l'émetteur (flash), le public (nav.page) et l'expiration sur votre serveur. Autorisez ensuite l'accès à l'aide de l'identifiant de l'espace de travail et du rôle associé, et jamais à l'aide de cookies (l'iframe n'a pas d'origine utilisable ; voir ci-dessous).

// Node (jose). The secret is your page's signing secret, shown ONCE at registration.
import { jwtVerify } from "jose";

const SECRET = new TextEncoder().encode(process.env.FLASH_EMBED_SECRET); // slotsec_…

export async function verifyFlashContext(token) {
  const { payload } = await jwtVerify(token, SECRET, {
    algorithms: ["HS256"],
    issuer: "flash",
    audience: "nav.page",
    requiredClaims: ["exp", "jti"],
    maxTokenAge: "120s",
  });
  // payload.ctx = { teamId, userId, role, pageType, locale }
  return payload.ctx; // → authorize the operator with this
}
// In your page: read ?flash_context=… , verify it server-side, then authorize.
const token = new URLSearchParams(location.search).get("flash_context");
// POST it to YOUR backend, verify with the snippet above, and gate the UI on
// ctx.teamId + ctx.role. Never trust it unverified, and never rely on cookies —
// the iframe is a null origin (see "Framing" below).

Sécurité et encadrement

Fonctionnalités requises pour votre page.

Intégrez le cadre

Votre page doit autoriser l'affichage dans une frame Flash.socialhub.ai — n'envoyez pas X-Frame-Options : DENY ; définissez Content-Security-Policy : frame-ancestors https://flash.socialhub.ai.

Supposons une origine null

Flash isole l’iframe avec les autorisations « allow-scripts » et « no allow-same-origin », de sorte que votre page s’exécute sur une origine null : sans cookies, stockage ni DOM parent Flash. L’authentification se fait par jeton, et non par cookies.

postMessage est autorisé.

L’hôte n’accepte que les requêtes postMessage provenant des origines que vous avez enregistrées, et uniquement depuis votre fenêtre iframe. Limitez votre liste d’origines autorisées et utilisez exclusivement le protocole HTTPS.

Renouvelez le secret de signature depuis le gestionnaire s'il est divulgué (les anciens jetons cessent immédiatement d'être vérifiés). Pages intégrées accès anticipé, désactivée par défaut dans chaque espace de travail, et son activation en production est soumise à l'examen de sécurité des intégrations tierces de SocialHub.

Adéquation de l'interface d'administration

Intégration naturelle et fluide.

Votre page s'affiche à l'intérieur de l'administrateur SocialHub, elle devrait donc donner l'impression qu'elle appartient. L’administrateur est un thème simple et léger construit sur quelques jetons – reflétez-les. Flash enveloppe déjà votre page dans une fenêtre intitulée avec un badge « Tiers », alors n'ajoutez pas votre propre barre supérieure, barre latérale ou chrome pleine page ; restituez simplement le contenu.

/* Starter tokens that match the SocialHub admin (flat, light, single-accent).
   Drop into your embedded page so it feels native, not bolted on. */
:root {
  /* type — the admin's three families */
  --font-sans:    "Inter", -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-display: "Plus Jakarta Sans", "Inter", system-ui, sans-serif;
  --font-mono:    "JetBrains Mono", ui-monospace, monospace;

  /* surfaces — light theme only (the admin has no dark mode) */
  --bg:      #fafbfc;  /* page background */
  --surface: #ffffff;  /* cards / panels  */
  --subtle:  #f7fafc;  /* muted fills     */
  --border:  #e2e8f0;  /* hairlines       */

  /* text */
  --text:   #121c3d;   /* primary (brand navy) */
  --text-2: #4a5568;   /* secondary            */
  --muted:  #64748b;   /* muted                */

  /* accent — the admin's primary action colour */
  --accent:      #2ecc87;
  --accent-dark: #25b678;

  /* radius + 4px spacing scale + elevation */
  --radius: 8px; --radius-lg: 12px;
  --space-2: 8px; --space-3: 12px; --space-4: 16px; --space-6: 24px;
  --shadow-sm: 0 1px 3px rgba(0,0,0,.06), 0 1px 2px rgba(0,0,0,.04);
}

body { margin: 0; background: var(--bg); color: var(--text);
       font: 14px/1.5 var(--font-sans); }
h1, h2, h3 { font-family: var(--font-display); color: var(--text); }
.card { background: var(--surface); border: 1px solid var(--border);
        border-radius: var(--radius-lg); box-shadow: var(--shadow-sm);
        padding: var(--space-4); }
.btn  { background: var(--accent); color: #fff; border: 0; font-weight: 600;
        border-radius: var(--radius); padding: 8px 14px; cursor: pointer; }
.btn:hover { background: var(--accent-dark); }
code  { font-family: var(--font-mono); color: var(--text-2); }

Aucune configuration Chrome requise

Flash fournit le cadre de la fenêtre et le badge. Oubliez la navigation globale, la barre latérale et l'en-tête de votre application : affichez uniquement l'outil.

Léger, plat, une seule couleur accentuée

Thème clair uniquement (l'administrateur n'a pas accès au mode sombre) : cartes blanches sur une page #fafbfc, bordures fines #e2e8f0, une touche de vert (#2ecc87).

Remplissez la largeur, coulez la hauteur

Utilisez toute la largeur et laissez le contenu s'afficher correctement ; l'hôte dimensionne le cadre (approximativement à la hauteur de la fenêtre d'affichage) et gère le défilement ; n'imposez pas vos propres pieds de page fixes ou de 100 vh.

Dégradation progressive

Les polices (Inter / Plus Jakarta Sans / JetBrains Mono) utilisent celles du système si vous ne les hébergez pas vous-même. Honor privilégie une animation réduite et une palette de couleurs claires.

Ces valeurs suivent le thème d'administration actuel ; traitez-les comme un point de départ et gardez votre propre marque là où cela compte. L'objectif est une page qui se lit comme une seule surface avec SocialHub, et non comme une fenêtre sur un produit différent.

Un exemple complet

Une page intégrée fonctionnelle en une trentaine de lignes.

Un serveur Express minimal, prêt à l'emploi : il permet à Flash de l'intégrer, vérifie le jeton d'identité, authentifie le rôle de l'opérateur et affiche un outil limité à l'espace de travail. Copiez le secret de signature depuis le gestionnaire dans FLASH_EMBED_SECRET et exécutez-le.

// server.js — a complete, minimal SocialHub Embedded Page (Express + jose).
// Register https://<host>/embed in Flash → Settings → Embedded Pages, allow the
// origin https://<host>, pick roles/tier, then copy the signing secret into env.
import express from "express";
import { jwtVerify } from "jose";

const app = express();
const SECRET = new TextEncoder().encode(process.env.FLASH_EMBED_SECRET); // slotsec_…
const FLASH = "https://flash.socialhub.ai";

app.get("/embed", async (req, res) => {
  // 1. Let ONLY Flash frame this page.
  res.setHeader("Content-Security-Policy", "frame-ancestors " + FLASH);

  // 2. Read + verify the identity token Flash appended (?flash_context=…).
  let ctx;
  try {
    const { payload } = await jwtVerify(String(req.query.flash_context || ""), SECRET, {
      algorithms: ["HS256"], issuer: "flash", audience: "nav.page",
      requiredClaims: ["exp", "jti"], maxTokenAge: "120s",
    });
    ctx = payload.ctx; // { teamId, userId, role, pageType, locale }
  } catch {
    return res.status(401).send("<p>Unauthorized — open this from inside SocialHub.</p>");
  }

  // 3. Authorize on the VERIFIED role (never on the raw request).
  const canEdit = ctx.role === "org_owner" || ctx.role === "org_admin";

  // 4. Render your tool, scoped to ctx.teamId. (Values are verified + opaque;
  //    still escape anything user-supplied you add.)
  res.send(
    '<!doctype html><meta charset="utf-8"><body style="font:14px system-ui;padding:24px">' +
    '<h1>Returns portal</h1>' +
    '<p>Workspace <code>' + ctx.teamId + '</code> · operator <code>' + ctx.userId +
    '</code> · role <b>' + ctx.role + '</b></p>' +
    (canEdit ? '<button>Process a return</button>' : '<p>Read-only for your role.</p>') +
    '</body>'
  );
});

app.listen(3000, () => console.log("Embedded page on :3000/embed"));

1 · Autoriser le cadre

frame-ancestors https://flash.socialhub.ai — afin que seul l'administrateur SocialHub puisse l'iframe.

2 · Vérifier le jeton

Lire ?flash_context, vérifier HS256 + flash de l'émetteur + page de navigation de l'audience + expiration ≤ 120 s ; rejeter dans le cas contraire.

3 · Autoriser

Les fonctionnalités de la porte sur le rôle VÉRIFIÉ (org_owner / org_admin / …), jamais sur la requête brute.

4 · Rendu, périmètre

Rendez votre outil pour ctx.teamId — l'espace de travail dans lequel il a été ouvert.

Puis dans Flash → Paramètres → Pages intégrées : ajoutez l'URL (par exemple https://tools.yourapp.com/embed), répertoriez cette même origine, choisissez les rôles + le niveau qui peuvent l'ouvrir et collez le secret de signature unique dans l'environnement de votre serveur. Il apparaît désormais sous Applications pour les opérateurs que vous avez autorisés.