SocialHub.AI

開発者センター · プラットフォームの拡張

埋め込みページ — 独自のツールを管理画面に配置します。

独自の https ホスト ページを登録すると、そのページが SocialHub 管理内の許可されたメニュー項目 (フルページのサンドボックス化された iframe) になります。 Flash は、有効期間が短い PII を含まない ID トークン をページに渡します。これにより、オペレーターが再度ログインしなくても認証できるようになります。

早期アクセス – オープン拡張モジュールの背後にある

仕組み

URL からメニュー項目までの 4 つのステップ。

01

ページを登録する

ワークスペース管理者は、https URL を Flash → [設定] → [埋め込みページ] に追加し、メニュー ラベル、オリジン ホワイトリスト、およびどのロール/層が開くことができるかを指定します。

02

署名シークレットをコピーする

Flash は、署名シークレットを 1 回表示します (slotsec_…)。これをサーバー シークレットとしてアプリに保存します。それを使用して Flash の ID トークンを検証します。

03

Flash ページをフレーム化します

このページは、許可されたオペレーターのアプリの下にサイドバー メニュー項目として表示されます。これを開くと、?flash_context=<token> が追加されたサンドボックス化された iframe に URL がロードされます。

04

確認+承認

トークンを読み取り、サーバー側 (HS256) で検証し、ワークスペース ID とトークンが持つオペレーターの役割に基づいて UI をゲートします。

ID トークン

有効期限が短く、PII がなく、秘密で署名されています。

レンダリングのたびに、Flash は URL に ?flash_context=<JWT> を追加します。これは、ページの シークレットで署名された HS256 JWT であり (つまり、プラットフォームではなくあなたがトラスト アンカーになります)、有効期限は最大 120 秒です。メンバーの個人データは含まれません。ワークスペース ID とオペレーターの不透明な ID + 大まかなロールのみが含まれるため、承認できます。

{
  "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
}

確認してください

リクエストではなくトークンを信頼します。

サーバー上の署名、発行者 (flash)、対象者 (nav.page)、有効期限を確認します。次に、ワークスペース ID とワークスペースが持つロールで承認します。Cookie では決して承認しません (iframe には使用可能なオリジンがありません。以下を参照)。

// 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).

フレーミングとセキュリティ

ページがしなければならないこと。

フレームを許可する

ページはFlashによるフレーム表示を許可する必要があります。socialhub.ai — X-Frame-Options: DENY を送信しないでください。Content-Security-Policy: frame-ancestors を設定してください。https://flash.socialhub.ai.

null 原点を想定します

Flash は、allow-scripts を使用して iframe をサンドボックス化し、allow-same-origin を使用しないので、ページは null オリジン、つまり Flash Cookie、ストレージ、親 DOM なしで実行されます。 Cookie ではなくトークンを使用して認証します。

postMessage は許可リストに登録されています

ホストは、登録したオリジンからの postMessage のみを受け入れ、iframe ウィンドウからのみ受け入れます。許可されるオリジンのリストを厳格に、https のみに限定してください。

署名シークレットが公開された場合は、マネージャーからの署名シークレットをローテーションします (古いトークンはすぐに検証を停止します)。埋め込みページは早期アクセスであり、ワークスペースごとにデフォルトでオフになっており、運用環境で有効にする場合は、SocialHub のサードパーティによる埋め込みセキュリティ レビューの対象となります。

管理者の外観と一致する

ボルトで固定されたものではなく、ネイティブな感じにしましょう。

ページは SocialHub 管理者の内部にレンダリングされるため、ページはそれに属しているように見えるはずです。 admin は、いくつかのトークンに基づいて構築された単一のフラットで軽いテーマであり、それらを反映しています。 Flash はすでに「サードパーティ」バッジが付いたタイトル付きウィンドウでページをラップしているため、独自のトップバー、サイドバー、またはページ全体のクロムを追加しないでください。コンテンツをレンダリングするだけです。

/* 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); }

独自のクロムはありません

Flash はウィンドウ フレーム + バッジを提供します。アプリのグローバル ナビゲーション/サイドバー/ヘッダーをスキップして、ツールのみをレンダリングします。

軽くてフラット、ワンアクセント

ライトテーマのみ (管理者にはダークモードがありません): #fafbfc ページに白いカード、ヘアライン #e2e8f0 の境界線、1 つの緑のアクセント (#2ecc87)。

幅を埋め、高さを流します

全幅を使用してコンテンツを流れさせます。ホストはフレームのサイズ (ビューポートの高さのほぼ全体) とスクロールを行います。独自の 100vh または固定フッターを強制しないでください。

正常に劣化する

フォント (Inter / Plus Jakarta Sans / JetBrains Mono) は、自己ホストしない場合は system-ui にフォールバックします。優先モーションと優先カラースキーム: ライトを優先します。

これらの値は、現在の管理テーマを追跡します。それらを出発点として扱い、重要な場所で独自のブランドを維持してください。目標は、別の製品への窓ではなく、SocialHub と 1 つの面として読み取れるページです。

完全な例

約 30 行の埋め込みページが動作します。

そのまま実行および登録できる最小限の Express サーバー。Flash によってフレーム化され、ID トークンが検証され、オペレーターの役割が承認され、ワー​​クスペースをスコープとするツールが表示されます。署名シークレットをマネージャーから FLASH_EMBED_SECRET にコピーして実行します。

// 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・フレームを許可する

Frame-ancestors https://flash.socialhub.ai — そのため、SocialHub 管理者のみが iframe を実行できます。

2 · トークンを検証する

?flash_context を読み取り、HS256 + 発行者のフラッシュ + 視聴者のナビゲーション ページ + ≤120 秒の有効期限を確認します。それ以外の場合は拒否します。

3・承認する

ゲート機能は VERIFIED ロール (org_owner / org_admin / …) で使用され、生のリクエストでは使用されません。

4 · レンダリング、スコープ付き

ctx.teamId (ツールが開かれたワークスペース) 用にツールをレンダリングします。

次に、Flash → 設定 → 埋め込みページ で、URL (例: https://tools.yourapp.com/embed) を追加し、同じオリジンをリストし、それを開くロールと層を選択して、ワンタイム署名シークレットをサーバーの環境に貼り付けます。これで、許可したオペレータの [アプリ] に表示されます。