{"version":3,"file":"extensions.mjs","names":[],"sources":["../../../../../src/astro/routes/api/toolbar/extensions.ts"],"sourcesContent":["/**\n * GET /_emdash/api/toolbar/extensions\n *\n * Buttons plugins add to the visual-editing toolbar. A plugin takes part by\n * exposing a private route named `toolbar` that returns\n * `{ label, script, config? }`: the toolbar shows the label and, when it is\n * clicked, loads `script` — which registers\n * `window.__emdashToolbarExtensions[pluginId] = { open(config) }` — then\n * calls `open`. The toolbar is a browser surface for signed-in editors, so\n * this is session-only: an API token gets nothing here.\n */\nimport type { Permission } from \"@premium-cms/auth\";\nimport type { APIRoute } from \"astro\";\n\nimport { requirePerm } from \"#api/authorize.js\";\nimport { apiError, apiSuccess } from \"#api/error.js\";\n\nexport const prerender = false;\n\n/** Same threshold as the toolbar itself (author and above). */\nconst MIN_ROLE = 30;\nconst HTTPS = /^https:\\/\\//;\n\nexport interface ToolbarExtension {\n\tpluginId: string;\n\tlabel: string;\n\tscript: string;\n\tconfig: unknown;\n}\n\n/** One line per plugin route considered, for `?debug=1`: why it did or did not become a button. */\ninterface Diagnostic {\n\tpluginId: string;\n\tpermission: string;\n\toutcome: \"ok\" | \"permission-denied\" | \"route-failed\" | \"invalid-descriptor\";\n\tdetail?: string;\n}\n\nexport const GET: APIRoute = async ({ request, locals }) => {\n\tconst { emdash, user } = locals;\n\tif (!emdash) return apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\tif (!user) return apiError(\"UNAUTHORIZED\", \"Authentication required\", 401);\n\tif (locals.tokenAuth) {\n\t\treturn apiError(\n\t\t\t\"TOKEN_AUTH_FORBIDDEN\",\n\t\t\t\"The toolbar is a browser surface; API tokens cannot use it.\",\n\t\t\t403,\n\t\t);\n\t}\n\tif (user.role < MIN_ROLE) return apiSuccess({ extensions: [] });\n\n\tconst debug = new URL(request.url).searchParams.has(\"debug\");\n\tconst allRoutes = emdash.listPluginRoutes?.() ?? [];\n\tconst candidates = allRoutes.filter((r) => r.route === \"toolbar\" && !r.public);\n\tconst extensions: ToolbarExtension[] = [];\n\tconst diagnostics: Diagnostic[] = [];\n\tfor (const candidate of candidates) {\n\t\tconst note = (outcome: Diagnostic[\"outcome\"], detail?: string) =>\n\t\t\tdiagnostics.push({\n\t\t\t\tpluginId: candidate.pluginId,\n\t\t\t\tpermission: candidate.permission,\n\t\t\t\toutcome,\n\t\t\t\tdetail,\n\t\t\t});\n\t\t// The plugin's own permission for the route still applies to this caller.\n\t\tif (requirePerm(user, candidate.permission as Permission)) {\n\t\t\tnote(\"permission-denied\");\n\t\t\tcontinue;\n\t\t}\n\t\ttry {\n\t\t\tconst result = await emdash.handlePluginApiRoute(\n\t\t\t\tcandidate.pluginId,\n\t\t\t\t\"POST\",\n\t\t\t\t\"/toolbar\",\n\t\t\t\trequest,\n\t\t\t\t{\n\t\t\t\t\t...user,\n\t\t\t\t\ttokenAuth: false,\n\t\t\t\t},\n\t\t\t);\n\t\t\tif (!result.success) {\n\t\t\t\tnote(\"route-failed\", `${result.error?.code ?? \"?\"}: ${result.error?.message ?? \"\"}`);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst data = (result.data ?? null) as Record<string, unknown> | null;\n\t\t\tconst label = typeof data?.label === \"string\" ? data.label.trim().slice(0, 40) : \"\";\n\t\t\tconst script = typeof data?.script === \"string\" && HTTPS.test(data.script) ? data.script : \"\";\n\t\t\tif (!label || !script) {\n\t\t\t\tnote(\"invalid-descriptor\", JSON.stringify(data).slice(0, 300));\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\textensions.push({\n\t\t\t\tpluginId: candidate.pluginId,\n\t\t\t\tlabel,\n\t\t\t\tscript,\n\t\t\t\tconfig: data?.config ?? null,\n\t\t\t});\n\t\t\tnote(\"ok\");\n\t\t} catch (error) {\n\t\t\t// A broken extension must not take the toolbar down for the others.\n\t\t\tnote(\"route-failed\", error instanceof Error ? error.message : String(error));\n\t\t}\n\t}\n\tif (debug) {\n\t\treturn apiSuccess({\n\t\t\textensions,\n\t\t\tdebug: {\n\t\t\t\tuser: { id: user.id, role: user.role },\n\t\t\t\tpluginRoutes: allRoutes.length,\n\t\t\t\tcandidates: candidates.map((c) => c.pluginId),\n\t\t\t\tdiagnostics,\n\t\t\t},\n\t\t});\n\t}\n\treturn apiSuccess({ extensions });\n};\n"],"mappings":";;;;;;AAiBA,MAAa,YAAY;;AAGzB,MAAM,WAAW;AACjB,MAAM,QAAQ;AAiBd,MAAa,MAAgB,OAAO,EAAE,SAAS,aAAa;CAC3D,MAAM,EAAE,QAAQ,SAAS;AACzB,KAAI,CAAC,OAAQ,QAAO,SAAS,kBAAkB,6BAA6B,IAAI;AAChF,KAAI,CAAC,KAAM,QAAO,SAAS,gBAAgB,2BAA2B,IAAI;AAC1E,KAAI,OAAO,UACV,QAAO,SACN,wBACA,+DACA,IACA;AAEF,KAAI,KAAK,OAAO,SAAU,QAAO,WAAW,EAAE,YAAY,EAAE,EAAE,CAAC;CAE/D,MAAM,QAAQ,IAAI,IAAI,QAAQ,IAAI,CAAC,aAAa,IAAI,QAAQ;CAC5D,MAAM,YAAY,OAAO,oBAAoB,IAAI,EAAE;CACnD,MAAM,aAAa,UAAU,QAAQ,MAAM,EAAE,UAAU,aAAa,CAAC,EAAE,OAAO;CAC9E,MAAM,aAAiC,EAAE;CACzC,MAAM,cAA4B,EAAE;AACpC,MAAK,MAAM,aAAa,YAAY;EACnC,MAAM,QAAQ,SAAgC,WAC7C,YAAY,KAAK;GAChB,UAAU,UAAU;GACpB,YAAY,UAAU;GACtB;GACA;GACA,CAAC;AAEH,MAAI,YAAY,MAAM,UAAU,WAAyB,EAAE;AAC1D,QAAK,oBAAoB;AACzB;;AAED,MAAI;GACH,MAAM,SAAS,MAAM,OAAO,qBAC3B,UAAU,UACV,QACA,YACA,SACA;IACC,GAAG;IACH,WAAW;IACX,CACD;AACD,OAAI,CAAC,OAAO,SAAS;AACpB,SAAK,gBAAgB,GAAG,OAAO,OAAO,QAAQ,IAAI,IAAI,OAAO,OAAO,WAAW,KAAK;AACpF;;GAED,MAAM,OAAQ,OAAO,QAAQ;GAC7B,MAAM,QAAQ,OAAO,MAAM,UAAU,WAAW,KAAK,MAAM,MAAM,CAAC,MAAM,GAAG,GAAG,GAAG;GACjF,MAAM,SAAS,OAAO,MAAM,WAAW,YAAY,MAAM,KAAK,KAAK,OAAO,GAAG,KAAK,SAAS;AAC3F,OAAI,CAAC,SAAS,CAAC,QAAQ;AACtB,SAAK,sBAAsB,KAAK,UAAU,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC;AAC9D;;AAED,cAAW,KAAK;IACf,UAAU,UAAU;IACpB;IACA;IACA,QAAQ,MAAM,UAAU;IACxB,CAAC;AACF,QAAK,KAAK;WACF,OAAO;AAEf,QAAK,gBAAgB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CAAC;;;AAG9E,KAAI,MACH,QAAO,WAAW;EACjB;EACA,OAAO;GACN,MAAM;IAAE,IAAI,KAAK;IAAI,MAAM,KAAK;IAAM;GACtC,cAAc,UAAU;GACxB,YAAY,WAAW,KAAK,MAAM,EAAE,SAAS;GAC7C;GACA;EACD,CAAC;AAEH,QAAO,WAAW,EAAE,YAAY,CAAC"}