SocialHub.AI

개발자 센터 · 플랫폼 확장

임베디드 페이지 - 관리자 페이지에 자체 도구를 추가하세요.

자신만의 https 호스팅 페이지를 등록하면 SocialHub 관리자 내에서 권한이 부여된 메뉴 항목(전체 페이지, 샌드박스 iframe)이 됩니다. Flash는 운영자가 다시 로그인하지 않고도 운영자를 승인할 수 있도록 페이지에 단기 PII가 없는 ID 토큰을 전달합니다.

초기 액세스 · 개방형 확장성 모듈 뒤에

작동 방식

URL에서 메뉴 항목까지 네 단계.

01

페이지 등록

워크스페이스 관리자는 Flash → 구성 → 포함된 페이지에서 메뉴 레이블, 출처 허용 목록, 그리고 해당 URL을 열 수 있는 역할/티어를 지정하여 HTTPS URL을 추가합니다.

02

서명 비밀을 복사하세요

Flash는 서명 비밀 키를 한 번 표시합니다(slotsec_…). 앱에 서버 비밀 키로 저장하세요. 이 키를 사용하여 Flash의 ID 토큰을 확인합니다.

03

Flash 페이지 프레임

해당 페이지는 허용된 운영자의 앱 메뉴 아래에 사이드바 메뉴 항목으로 표시됩니다. 해당 페이지를 열면 URL이 샌드박스 처리된 iframe에 ?flash_context=<token>가 추가된 형태로 로드됩니다.

04

확인 및 승인

토큰을 읽고 서버 측에서 (HS256) 검증한 다음, 해당 토큰에 포함된 작업 공간 ID와 운영자 역할을 기준으로 UI를 제한합니다.

ID 토큰

수명이 짧고, 개인 식별 정보가 없으며, 사용자의 비밀 키로 서명됩니다.

렌더링할 때마다 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와 역할에 따라 권한을 부여합니다. 쿠키에는 절대 권한을 부여하지 마세요(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).

프레임 및 보안

귀하의 페이지가 수행해야 하는 작업.

프레임 허용

페이지가 플래시 프레임에 노출될 수 있도록 허용해야 합니다.socialhub.ai — X-Frame-Options: DENY를 보내지 말고 Content-Security-Policy: frame-ancestors를 설정하세요.https://flash.socialhub.ai.

null 출처를 가정합니다.

Flash는 스크립트 허용 및 동일 출처 허용 안 함으로 iframe을 샌드박스 처리하여 페이지가 null 출처에서 실행되도록 합니다. 즉, Flash 쿠키, 저장소 또는 상위 DOM을 사용하지 않습니다. 쿠키가 아닌 토큰으로 인증합니다.

postMessage는 허용 목록에 있습니다.

호스트는 등록한 원본과 iframe 창에서만 postMessage를 허용합니다. 허용된 출처 목록을 엄격하게 https로만 유지하세요.

관리자의 서명 비밀 키가 노출될 경우 즉시 교체하세요(기존 토큰은 즉시 검증이 중단됩니다). 임베디드 페이지는 얼리 액세스이며, 워크스페이스별로 기본적으로 비활성화되어 있고, 프로덕션 환경에서 활성화하려면 SocialHub의 타사 임베드 보안 검토를 거쳐야 합니다.

관리자 화면과 일치

어울리게 통합되어 어색하지 않도록 설계되었습니다.

귀하의 페이지는 SocialHub 관리자 내부로 렌더링되므로 해당 페이지가 속한 것처럼 보일 것입니다. 관리자는 몇 개의 토큰을 기반으로 구축된 단순하고 가벼운 단일 테마입니다. 이를 미러링합니다. Flash은 이미 "제3자" 배지를 사용하여 제목 창에 페이지를 래핑했으므로 상단 표시줄, 사이드바 또는 전체 페이지 크롬을 추가하지 마세요. 그냥 콘텐츠를 렌더링하세요.

/* 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 테두리, 녹색 포인트(#2ecc87) 하나.

너비를 채우고 높이를 흐르게 하세요

전체 너비를 사용하고 콘텐츠가 자연스럽게 흐르도록 하세요. 호스트가 프레임 크기(대략 전체 뷰포트 높이)를 조정하고 스크롤합니다. 100vh 또는 고정된 바닥글을 강제로 사용하지 마세요.

우아하게 저하하다

글꼴(Inter / Plus Jakarta Sans / JetBrains Mono)은 자체 호스팅하지 않을 경우 system-ui로 대체됩니다. honour는 동작 최소화와 밝은 색상 구성을 선호합니다.

이 값은 현재 관리 테마를 추적합니다. 이를 출발점으로 삼고 중요한 곳에 자신의 브랜드를 유지하십시오. 목표는 다른 제품을 보여주는 창이 아닌 SocialHub를 통해 하나의 표면으로 읽히는 페이지를 만드는 것입니다.

완전한 예시

약 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 · 승인

검증됨 역할의 게이트 기능(org_owner / org_admin / …), 절대 원시 요청에는 적용되지 않습니다.

4 · 범위 지정 렌더링

ctx.teamId(도구가 열린 작업 공간)에 대한 도구를 렌더링합니다.

그런 다음 Flash → 설정 → 삽입된 페이지에서 URL(예: https://tools.yourapp.com/embed)을 추가하고, 동일한 출처를 나열하고, 이를 열 수 있는 역할 + 계층을 선택하고, 일회성 서명 비밀을 서버 환경에 붙여넣습니다. 이제 허용한 운영자에 대한 앱 아래에 표시됩니다.