@aglyn/plugins-marketplace 1.14.0 → 1.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +1 -0
  2. package/package.json +15 -14
  3. package/src/lib/components/editor-connections-provider.component.d.ts +37 -0
  4. package/src/lib/components/editor-connections-provider.component.js +161 -0
  5. package/src/lib/components/editor-connections-provider.component.js.map +1 -0
  6. package/src/lib/components/listing-connection-credentials-card.component.d.ts +33 -0
  7. package/src/lib/components/listing-connection-credentials-card.component.js +318 -0
  8. package/src/lib/components/listing-connection-credentials-card.component.js.map +1 -0
  9. package/src/lib/components/listing-detail-page.component.js +10 -2
  10. package/src/lib/components/listing-detail-page.component.js.map +1 -1
  11. package/src/lib/components/site-plugin-connections-card.component.d.ts +38 -0
  12. package/src/lib/components/site-plugin-connections-card.component.js +576 -0
  13. package/src/lib/components/site-plugin-connections-card.component.js.map +1 -0
  14. package/src/lib/declarations.console-server.d.ts +17 -0
  15. package/src/lib/declarations.console-server.js +41 -0
  16. package/src/lib/declarations.console-server.js.map +1 -0
  17. package/src/lib/declarations.js +11 -2
  18. package/src/lib/declarations.js.map +1 -1
  19. package/src/lib/declarations.server.js +68 -1
  20. package/src/lib/declarations.server.js.map +1 -1
  21. package/src/lib/hooks/use-plugin-connections-api.d.ts +89 -0
  22. package/src/lib/hooks/use-plugin-connections-api.js +132 -0
  23. package/src/lib/hooks/use-plugin-connections-api.js.map +1 -0
  24. package/src/lib/model/editor-connections.d.ts +46 -0
  25. package/src/lib/model/editor-connections.js +70 -0
  26. package/src/lib/model/editor-connections.js.map +1 -0
  27. package/src/lib/model/listing-price-floor.js +7 -4
  28. package/src/lib/model/listing-price-floor.js.map +1 -1
  29. package/src/lib/model/marketplace-publisher-permissions.d.ts +3 -1
  30. package/src/lib/model/marketplace-publisher-permissions.js +14 -0
  31. package/src/lib/model/marketplace-publisher-permissions.js.map +1 -1
  32. package/src/lib/model/marketplace-site-permissions.d.ts +9 -0
  33. package/src/lib/model/marketplace-site-permissions.js +29 -0
  34. package/src/lib/model/marketplace-site-permissions.js.map +1 -1
  35. package/src/lib/model/plugin-connection-card.d.ts +88 -0
  36. package/src/lib/model/plugin-connection-card.js +187 -0
  37. package/src/lib/model/plugin-connection-card.js.map +1 -0
  38. package/src/lib/model/plugin-connections.d.ts +172 -0
  39. package/src/lib/model/plugin-connections.js +119 -0
  40. package/src/lib/model/plugin-connections.js.map +1 -0
  41. package/src/lib/plugin.js +21 -1
  42. package/src/lib/plugin.js.map +1 -1
  43. package/src/lib/server/admin-reviews.js +18 -12
  44. package/src/lib/server/admin-reviews.js.map +1 -1
  45. package/src/lib/server/artifact-inventory.js +3 -1
  46. package/src/lib/server/artifact-inventory.js.map +1 -1
  47. package/src/lib/server/checkout.js +4 -0
  48. package/src/lib/server/checkout.js.map +1 -1
  49. package/src/lib/server/connections/cleanup.d.ts +84 -0
  50. package/src/lib/server/connections/cleanup.js +116 -0
  51. package/src/lib/server/connections/cleanup.js.map +1 -0
  52. package/src/lib/server/connections/config.d.ts +42 -0
  53. package/src/lib/server/connections/config.js +48 -0
  54. package/src/lib/server/connections/config.js.map +1 -0
  55. package/src/lib/server/connections/connection-store.d.ts +173 -0
  56. package/src/lib/server/connections/connection-store.js +331 -0
  57. package/src/lib/server/connections/connection-store.js.map +1 -0
  58. package/src/lib/server/connections/credentials-store.d.ts +71 -0
  59. package/src/lib/server/connections/credentials-store.js +78 -0
  60. package/src/lib/server/connections/credentials-store.js.map +1 -0
  61. package/src/lib/server/connections/erasure.d.ts +25 -0
  62. package/src/lib/server/connections/erasure.js +72 -0
  63. package/src/lib/server/connections/erasure.js.map +1 -0
  64. package/src/lib/server/connections/installed-connection.d.ts +64 -0
  65. package/src/lib/server/connections/installed-connection.js +92 -0
  66. package/src/lib/server/connections/installed-connection.js.map +1 -0
  67. package/src/lib/server/connections/oauth-state.d.ts +129 -0
  68. package/src/lib/server/connections/oauth-state.js +252 -0
  69. package/src/lib/server/connections/oauth-state.js.map +1 -0
  70. package/src/lib/server/connections/provider-http.d.ts +99 -0
  71. package/src/lib/server/connections/provider-http.js +253 -0
  72. package/src/lib/server/connections/provider-http.js.map +1 -0
  73. package/src/lib/server/connections/record-announce.d.ts +20 -0
  74. package/src/lib/server/connections/record-announce.js +53 -0
  75. package/src/lib/server/connections/record-announce.js.map +1 -0
  76. package/src/lib/server/connections/record-mapping.d.ts +63 -0
  77. package/src/lib/server/connections/record-mapping.js +151 -0
  78. package/src/lib/server/connections/record-mapping.js.map +1 -0
  79. package/src/lib/server/connections/record-routes.d.ts +17 -0
  80. package/src/lib/server/connections/record-routes.js +96 -0
  81. package/src/lib/server/connections/record-routes.js.map +1 -0
  82. package/src/lib/server/connections/revoke.d.ts +29 -0
  83. package/src/lib/server/connections/revoke.js +52 -0
  84. package/src/lib/server/connections/revoke.js.map +1 -0
  85. package/src/lib/server/connections/routes.d.ts +77 -0
  86. package/src/lib/server/connections/routes.js +706 -0
  87. package/src/lib/server/connections/routes.js.map +1 -0
  88. package/src/lib/server/connections/site-records-platform.d.ts +24 -0
  89. package/src/lib/server/connections/site-records-platform.js +94 -0
  90. package/src/lib/server/connections/site-records-platform.js.map +1 -0
  91. package/src/lib/server/connections/site-records.d.ts +95 -0
  92. package/src/lib/server/connections/site-records.js +221 -0
  93. package/src/lib/server/connections/site-records.js.map +1 -0
  94. package/src/lib/server/connections/sync.d.ts +110 -0
  95. package/src/lib/server/connections/sync.js +414 -0
  96. package/src/lib/server/connections/sync.js.map +1 -0
  97. package/src/lib/server/install-plugin.js +56 -6
  98. package/src/lib/server/install-plugin.js.map +1 -1
  99. package/src/lib/server/publish-plugin.js +5 -2
  100. package/src/lib/server/publish-plugin.js.map +1 -1
  101. package/src/lib/server/revenue-source.js +6 -5
  102. package/src/lib/server/revenue-source.js.map +1 -1
  103. package/src/lib/server.js +60 -0
  104. package/src/lib/server.js.map +1 -1
  105. package/src/lib/testing/memory-firestore.d.ts +34 -0
  106. package/src/lib/testing/memory-firestore.js +250 -0
  107. package/src/lib/testing/memory-firestore.js.map +1 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/site-plugin-connections-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { CardDisplay, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { BlockedControl } from '@aglyn/shared-ui-jsx/components/blocked-control.component'\nimport { PlanUpsellNotice } from '@aglyn/shared-ui-jsx/components/plan-upsell-notice.component'\nimport { PrerequisiteNotice } from '@aglyn/shared-ui-jsx/components/prerequisite-notice.component'\nimport { StatusChip } from '@aglyn/shared-ui-jsx/components/status-chip.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useSiteKeys } from '@aglyn/tenant-feature-instance/hooks/use-site-keys'\nimport { Alert, Button, CircularProgress, MenuItem, Stack, TextField, Typography } from '@mui/material'\nimport { useCallback, useEffect, useRef, useState, type ReactElement } from 'react'\nimport {\n PluginConnectionRouteError,\n usePluginConnectionsApi,\n type PluginConnectionsApi,\n type PluginConnectionsStatusAnswer,\n} from '../hooks/use-plugin-connections-api'\nimport {\n declaredPluginConnections,\n declaredSyncIntervalHours,\n formatConnectionTime,\n nextResourceToChoose,\n PLUGIN_CONNECTION_PHASE_CHIP,\n PLUGIN_CONNECTIONS_NOT_CONFIGURED_TEXT,\n pluginConnectionNextSyncAtMs,\n pluginConnectionNotSetUpText,\n pluginConnectionPhase,\n pluginConnectionResultMessage,\n pluginConnectionSharesText,\n pluginConnectionTooSoonText,\n readPluginConnectionReturn,\n type PluginConnectionCardView,\n} from '../model/plugin-connection-card'\nimport {\n PLUGIN_CONNECTIONS_PLAN_REFUSAL,\n PLUGIN_CONNECTIONS_SITE_PERMISSION,\n PLUGIN_CONNECTIONS_SITE_ROLE_PRESET,\n pluginConnectionsPermissionRefusal,\n type PluginConnectionStatusEntry,\n} from '../model/plugin-connections'\n\n/**\n * What the `sitePluginConnections` zone hands each widget: the site's page\n * for one installed plugin, and the pin whose manifest it runs.\n */\nexport interface SitePluginConnectionsCardProps {\n hostId: string\n /** Path slug for building `/[orgSlug]/…` links. */\n orgSlug: string\n /** The installation's id: the listing the pin is keyed by. */\n pluginRef: string\n /** The site's pin of the listing (else the workspace's): its version and manifest. */\n pin: Readonly<Record<string, unknown>> | null | undefined\n /** Test seam: the routes. The signed-in member's by default. */\n api?: PluginConnectionsApi\n /** Test seam: sends the browser to the provider's consent page. */\n navigate?: (url: string) => void\n}\n\ntype Load =\n | { status: 'loading' }\n | { status: 'error'; message: string; refused: boolean }\n | { status: 'ready'; answer: PluginConnectionsStatusAnswer }\n\nconst leavePage = (url: string) => window.location.assign(url)\n\nconst HELP = pluginDocsHelp('installYourFirstPlugin', {\n anchor: '#plugin-connections',\n excerpt:\n 'Some plugins show information from another service, such as your business listing. Connect it once and the plugin keeps it up to date on its own.',\n})\n\n/**\n * THE SITE'S CONNECTIONS FOR ONE INSTALLED PLUGIN (AGL-4196).\n *\n * A marketplace plugin may declare connections in its manifest: an account at\n * another service the platform signs in to, the things the owner picks there\n * (an account, then one of its locations), and a scheduled sync. The\n * platform runs all of it, so this card is the same for every plugin: one\n * card per declared connection, drawn from the pinned manifest and the\n * status route.\n *\n * Every prerequisite stays on screen: a plan without connections, a\n * publisher who has not set the connection up, a deployment that cannot run\n * them yet and a member without the site key each keep the card, with its\n * controls off and the reason said.\n */\nexport function SitePluginConnectionsCard(props: SitePluginConnectionsCardProps) {\n const { hostId, orgSlug, pluginRef, pin } = props\n const routes = usePluginConnectionsApi(hostId)\n const api = props.api ?? routes\n const navigate = props.navigate ?? leavePage\n const { enqueueSnackbar } = useSnackbar()\n const { blocked } = useSiteKeys(hostId)\n const declared = declaredPluginConnections(pin?.['manifest'])\n const hasDeclared = declared.length > 0\n const [load, setLoad] = useState<Load>({ status: 'loading' })\n const returned = useRef(false)\n\n const refresh = useCallback(async () => {\n try {\n setLoad({ status: 'ready', answer: await api.status(pluginRef) })\n } catch (error) {\n const refusal = error instanceof PluginConnectionRouteError ? error : null\n setLoad({\n status: 'error',\n message: refusal?.message ?? 'The connections could not be read. Try again.',\n refused: refusal?.reason === 'permission',\n })\n }\n }, [api, pluginRef])\n\n useEffect(() => {\n if (!hasDeclared || !hostId) return\n if (!returned.current && typeof window !== 'undefined') {\n returned.current = true\n const back = readPluginConnectionReturn(window.location.href, pluginRef)\n if (back) {\n window.history.replaceState(window.history.state, '', back.cleanUrl)\n const label = declared.find((one) => one.id === back.connectionId)?.label ?? 'The service'\n const { variant, message } = pluginConnectionResultMessage(back.result, label)\n enqueueSnackbar(message, { variant })\n }\n }\n void refresh()\n // `declared` is re-derived each render from the same pin; its ids are what matter.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hasDeclared, hostId, pluginRef, refresh])\n\n if (!hasDeclared) return null\n\n const keyRefused = blocked(PLUGIN_CONNECTIONS_SITE_PERMISSION, PLUGIN_CONNECTIONS_SITE_ROLE_PRESET)\n const permissionText = pluginConnectionsPermissionRefusal(PLUGIN_CONNECTIONS_SITE_PERMISSION)\n\n if (keyRefused || (load.status === 'error' && load.refused)) {\n return (\n <Stack spacing={3}>\n {declared.map((connection) => (\n <CardDisplay\n key={connection.id}\n header={connection.label}\n subheader={connection.description}\n help={HELP}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <BlockedControl blocked reason={permissionText}>\n <Button variant=\"contained\">{'Connect'}</Button>\n </BlockedControl>\n ),\n }}\n >\n <Alert severity=\"info\">{permissionText}</Alert>\n </CardDisplay>\n ))}\n </Stack>\n )\n }\n\n if (load.status !== 'ready') {\n return (\n <Stack spacing={3}>\n {declared.map((connection) => (\n <CardDisplay\n key={connection.id}\n header={connection.label}\n subheader={connection.description}\n help={HELP}\n contentGutterX\n contentGutterY\n HeaderProps={\n load.status === 'error'\n ? {\n action: (\n <Button variant=\"outlined\" onClick={() => void refresh()}>\n {'Try again'}\n </Button>\n ),\n }\n : undefined\n }\n >\n {load.status === 'error' ? (\n <Alert severity=\"error\">{load.message}</Alert>\n ) : (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }} role=\"status\">\n <CircularProgress size={18} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Checking the connection…'}\n </Typography>\n </Stack>\n )}\n </CardDisplay>\n ))}\n </Stack>\n )\n }\n\n const { answer } = load\n const replace = (entry: PluginConnectionStatusEntry, connection: PluginConnectionCardView | null) =>\n setLoad({\n status: 'ready',\n answer: {\n ...answer,\n connections: answer.connections.map((one) =>\n one.listingId === entry.listingId && one.connectionId === entry.connectionId ? { ...one, connection } : one,\n ),\n },\n })\n\n return (\n <Stack spacing={3}>\n {answer.connections.map((entry) => (\n <SitePluginConnection\n key={`${entry.listingId}/${entry.connectionId}`}\n entry={entry}\n configured={answer.configured}\n planAllows={answer.planAllows && entry.planAllows}\n upgradeHref={orgSlug ? `/${orgSlug}/billing#plans` : undefined}\n pluginName={String(pin?.['displayName'] ?? '') || 'this plugin'}\n intervalHours={declaredSyncIntervalHours(pin?.['manifest'], entry.connectionId)}\n api={api}\n navigate={navigate}\n onConnection={(connection) => replace(entry, connection)}\n onStale={refresh}\n />\n ))}\n </Stack>\n )\n}\nSitePluginConnectionsCard.displayName = 'SitePluginConnectionsCard'\n\nexport default SitePluginConnectionsCard\n\ntype Busy = 'connect' | 'sync' | 'choose' | 'disconnect' | null\n\ninterface SitePluginConnectionProps {\n entry: PluginConnectionStatusEntry\n configured: boolean\n planAllows: boolean\n upgradeHref: string | undefined\n pluginName: string\n intervalHours: number | null\n api: PluginConnectionsApi\n navigate: (url: string) => void\n onConnection: (connection: PluginConnectionCardView | null) => void\n onStale: () => Promise<void>\n}\n\ntype Items =\n | { status: 'idle' | 'loading' }\n | { status: 'error'; message: string }\n | { status: 'ready'; items: Array<{ id: string; label: string }> }\n\n/** One declared connection on the site: its state, what was chosen, and what can be done next. */\nfunction SitePluginConnection(props: SitePluginConnectionProps) {\n const { entry, configured, planAllows, upgradeHref, pluginName, intervalHours, api, navigate, onConnection, onStale } =\n props\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const [busy, setBusy] = useState<Busy>(null)\n const [error, setError] = useState<string | null>(null)\n const [wait, setWait] = useState<string | null>(null)\n const [changing, setChanging] = useState<number | null>(null)\n const [items, setItems] = useState<Items>({ status: 'idle' })\n const [picked, setPicked] = useState('')\n const phase = pluginConnectionPhase(entry, configured)\n // A pinned version that declares no sync has nothing for Sync now to run, and no sync times to show.\n const declaresSync = intervalHours !== null\n const view = entry.connection as PluginConnectionCardView | null\n const label = entry.label\n const ids = { listingId: entry.listingId, connectionId: entry.connectionId }\n\n const resource = changing !== null ? (entry.resources[changing] ?? null) : phase === 'choosing' ? nextResourceToChoose(entry) : null\n const resourceId = resource?.id ?? ''\n const currentChoice = resourceId ? (view?.choices[resourceId]?.id ?? '') : ''\n\n useEffect(() => {\n if (!resourceId || !planAllows) {\n setItems({ status: 'idle' })\n return\n }\n let live = true\n setItems({ status: 'loading' })\n setPicked(currentChoice)\n api\n .resources({ ...ids, resource: resourceId })\n .then((answer) => {\n if (!live) return\n setItems({ status: 'ready', items: answer.items })\n setPicked((was) => was || (answer.items.length === 1 ? answer.items[0].id : ''))\n })\n .catch((failure: unknown) => {\n if (!live) return\n setItems({ status: 'error', message: failure instanceof Error ? failure.message : String(failure) })\n if (failure instanceof PluginConnectionRouteError && failure.reason === 'reconnect') void onStale()\n })\n return () => {\n live = false\n }\n // The connection's ids and the resource are the request; the rest is display.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [api, entry.listingId, entry.connectionId, resourceId, planAllows])\n\n const run = async (kind: NonNullable<Busy>, work: () => Promise<void>) => {\n setBusy(kind)\n setError(null)\n setWait(null)\n try {\n await work()\n } catch (failure) {\n if (failure instanceof PluginConnectionRouteError && failure.reason === 'too-soon') {\n // Not a failure: the last sync is under a minute old. Say how long, and show what it brought.\n const retryAfterMs = Number(failure.body['retryAfterMs'])\n setWait(pluginConnectionTooSoonText(label, Number.isFinite(retryAfterMs) ? retryAfterMs : null))\n const connection = failure.body['connection'] as PluginConnectionCardView | undefined\n if (connection && typeof connection === 'object') onConnection(connection)\n return\n }\n const message = failure instanceof Error ? failure.message : String(failure)\n setError(message)\n if (\n failure instanceof PluginConnectionRouteError &&\n (failure.reason === 'reconnect' || failure.reason === 'not-connected' || failure.reason === 'choose-first')\n ) {\n void onStale()\n }\n } finally {\n setBusy(null)\n }\n }\n\n const connect = () =>\n run('connect', async () => {\n const { authorizeUrl } = await api.connectStart({ ...ids, returnTo: window.location.href })\n navigate(authorizeUrl)\n })\n\n const choose = () =>\n run('choose', async () => {\n if (!resource || !picked) return\n const { connection } = await api.choose({ ...ids, resource: resource.id, id: picked })\n onConnection(connection)\n if (changing !== null) {\n const next = changing + 1\n setChanging(next < entry.resources.length ? next : null)\n }\n if (connection?.ready) enqueueSnackbar(`${label} is set up. It syncs on its own from now on.`, { variant: 'success' })\n })\n\n const sync = () =>\n run('sync', async () => {\n const { connection, synced } = await api.sync(ids)\n onConnection(connection)\n enqueueSnackbar(synced ? `${label} is up to date.` : `${label} did not sync. See why below.`, {\n variant: synced ? 'success' : 'warning',\n })\n })\n\n const disconnect = async () => {\n const ok = await Promise.resolve(\n confirm({\n title: `Disconnect ${label}?`,\n description: `${pluginName} stops receiving updates from ${label} on this site, and what it last brought in is removed from the site. You can connect again at any time.`,\n confirmationText: 'Disconnect',\n confirmationButtonProps: { color: 'warning', variant: 'contained' },\n }),\n )\n .then(() => true)\n .catch(() => false)\n if (!ok) return\n await run('disconnect', async () => {\n await api.disconnect(ids)\n setChanging(null)\n onConnection(null)\n enqueueSnackbar(`${label} is disconnected.`, { variant: 'success' })\n })\n }\n\n const planReason = PLUGIN_CONNECTIONS_PLAN_REFUSAL\n const blockedBy =\n phase === 'not-configured'\n ? PLUGIN_CONNECTIONS_NOT_CONFIGURED_TEXT\n : phase === 'not-set-up'\n ? pluginConnectionNotSetUpText(label)\n : !planAllows\n ? planReason\n : null\n\n const guarded = (reason: string | null, control: ReactElement<{ disabled?: boolean }>) => (\n <BlockedControl blocked={Boolean(reason)} reason={reason ?? ''}>\n {control}\n </BlockedControl>\n )\n\n const disconnectButton = (\n <Button color=\"warning\" disabled={busy !== null} onClick={() => void disconnect()}>\n {busy === 'disconnect' ? 'Disconnecting…' : 'Disconnect'}\n </Button>\n )\n\n const actions: ReactElement[] = []\n if (resource) {\n actions.push(\n guarded(\n !planAllows ? planReason : null,\n <Button key=\"use\" variant=\"contained\" disabled={busy !== null || !picked || items.status !== 'ready'} onClick={() => void choose()}>\n {busy === 'choose' ? 'Saving…' : `Use this ${resource.label.toLowerCase()}`}\n </Button>,\n ),\n )\n if (changing !== null) {\n actions.push(\n <Button key=\"cancel\" color=\"inherit\" disabled={busy !== null} onClick={() => setChanging(null)}>\n {'Cancel'}\n </Button>,\n )\n }\n actions.push(disconnectButton)\n } else if (phase === 'not-configured' || phase === 'not-set-up' || phase === 'not-connected') {\n actions.push(\n guarded(\n blockedBy,\n <Button key=\"connect\" variant=\"contained\" disabled={busy !== null} onClick={() => void connect()}>\n {busy === 'connect' ? 'Opening…' : 'Connect'}\n </Button>,\n ),\n )\n } else if (phase === 'reconnect') {\n actions.push(\n guarded(\n !planAllows ? planReason : !entry.credentialsSet ? pluginConnectionNotSetUpText(label) : null,\n <Button key=\"reconnect\" variant=\"contained\" disabled={busy !== null} onClick={() => void connect()}>\n {busy === 'connect' ? 'Opening…' : 'Reconnect'}\n </Button>,\n ),\n )\n actions.push(disconnectButton)\n } else {\n if (declaresSync) {\n actions.push(\n guarded(\n !planAllows ? planReason : null,\n <Button key=\"sync\" variant={phase === 'error' ? 'contained' : 'outlined'} disabled={busy !== null} onClick={() => void sync()}>\n {busy === 'sync' ? 'Syncing…' : 'Sync now'}\n </Button>,\n ),\n )\n }\n if (entry.resources.length) {\n actions.push(\n guarded(\n !planAllows ? planReason : null,\n <Button key=\"change\" variant=\"outlined\" disabled={busy !== null} onClick={() => setChanging(0)}>\n {'Change'}\n </Button>,\n ),\n )\n }\n actions.push(disconnectButton)\n }\n\n const chip = PLUGIN_CONNECTION_PHASE_CHIP[phase]\n const shares = pluginConnectionSharesText(entry.hosts)\n const nextSync = pluginConnectionNextSyncAtMs(view, intervalHours)\n const chosen = entry.resources.filter((one) => view?.choices[one.id])\n\n return (\n <CardDisplay\n header={label}\n subheader={entry.description}\n help={HELP}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap', justifyContent: 'flex-end' }}>\n {actions.map((action, index) => (\n <span key={index}>{action}</span>\n ))}\n </Stack>\n ),\n }}\n >\n <Stack spacing={1.5}>\n <Stack direction=\"row\" spacing={1}>\n <StatusChip label={chip.label} tone={chip.tone} data-testid=\"plugin-connection-status\" />\n </Stack>\n\n {phase === 'not-configured' ? <Alert severity=\"info\">{PLUGIN_CONNECTIONS_NOT_CONFIGURED_TEXT}</Alert> : null}\n {phase === 'not-set-up' ? <PrerequisiteNotice>{pluginConnectionNotSetUpText(label)}</PrerequisiteNotice> : null}\n {!planAllows && phase !== 'not-configured' && phase !== 'not-set-up' ? (\n <PlanUpsellNotice planLabel={undefined} upgradeHref={upgradeHref}>\n {view ? `${planReason} You can still disconnect it.` : planReason}\n </PlanUpsellNotice>\n ) : null}\n\n {phase === 'not-connected' ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Connect ${label} so ${pluginName} can use it on this site. You sign in at ${label} and choose what to share; this site never sees your password.`}\n </Typography>\n ) : null}\n\n {phase === 'reconnect' ? (\n <Alert severity=\"warning\">\n {`${label} no longer accepts this connection. Nothing syncs until you reconnect.`}\n </Alert>\n ) : null}\n\n {phase === 'error' ? (\n <Alert severity=\"error\">\n {view?.lastError\n ? `The last sync did not work: ${view.lastError}. It tries again on schedule, or you can sync now.`\n : 'The last sync did not work. It tries again on schedule, or you can sync now.'}\n </Alert>\n ) : null}\n\n {resource ? (\n <Stack spacing={1}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {changing === null\n ? `Choose the ${resource.label.toLowerCase()} ${pluginName} should use. Nothing syncs until you do.`\n : `Choose the ${resource.label.toLowerCase()} ${pluginName} should use.`}\n </Typography>\n {items.status === 'loading' ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }} role=\"status\">\n <CircularProgress size={18} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Asking ${label}…`}\n </Typography>\n </Stack>\n ) : null}\n {items.status === 'error' ? <Alert severity=\"error\">{items.message}</Alert> : null}\n {items.status === 'ready' && !items.items.length ? (\n <Alert severity=\"info\">\n {`The ${label} account you connected has nothing to choose here. Connect a different account, or add one at ${label} first.`}\n </Alert>\n ) : null}\n {items.status === 'ready' && items.items.length ? (\n <TextField\n select\n size=\"small\"\n label={resource.label}\n value={picked}\n disabled={busy !== null || !planAllows}\n onChange={(event) => setPicked(event.target.value)}\n sx={{ maxWidth: 480 }}\n >\n {items.items.map((item) => (\n <MenuItem key={item.id} value={item.id}>\n {item.label}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n </Stack>\n ) : null}\n\n {view && chosen.length && changing === null ? (\n <Stack spacing={0.5}>\n {chosen.map((one) => (\n <Typography key={one.id} variant=\"body2\">\n <Typography component=\"span\" variant=\"body2\" color=\"text.secondary\">\n {`${one.label}: `}\n </Typography>\n {view.choices[one.id]?.label}\n </Typography>\n ))}\n </Stack>\n ) : null}\n\n {view && declaresSync && (phase === 'connected' || phase === 'error') ? (\n <Stack spacing={0.5}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {view.lastSyncAtMs !== null ? `Last synced ${formatConnectionTime(view.lastSyncAtMs)}` : 'Not synced yet'}\n </Typography>\n {nextSync !== null ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {nextSync === 'soon' ? 'Next sync: within the hour' : `Next sync ${formatConnectionTime(nextSync)}`}\n </Typography>\n ) : null}\n </Stack>\n ) : null}\n\n {shares ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {shares}\n </Typography>\n ) : null}\n\n {wait ? (\n <Alert severity=\"info\" onClose={() => setWait(null)}>\n {wait}\n </Alert>\n ) : null}\n {error ? (\n <Alert severity=\"error\" onClose={() => setError(null)}>\n {error}\n </Alert>\n ) : null}\n </Stack>\n </CardDisplay>\n )\n}\n"],"names":["pluginDocsHelp","CardDisplay","useConfirmationContext","BlockedControl","PlanUpsellNotice","PrerequisiteNotice","StatusChip","useSnackbar","useSiteKeys","Alert","Button","CircularProgress","MenuItem","Stack","TextField","Typography","useCallback","useEffect","useRef","useState","PluginConnectionRouteError","usePluginConnectionsApi","declaredPluginConnections","declaredSyncIntervalHours","formatConnectionTime","nextResourceToChoose","PLUGIN_CONNECTION_PHASE_CHIP","PLUGIN_CONNECTIONS_NOT_CONFIGURED_TEXT","pluginConnectionNextSyncAtMs","pluginConnectionNotSetUpText","pluginConnectionPhase","pluginConnectionResultMessage","pluginConnectionSharesText","pluginConnectionTooSoonText","readPluginConnectionReturn","PLUGIN_CONNECTIONS_PLAN_REFUSAL","PLUGIN_CONNECTIONS_SITE_PERMISSION","PLUGIN_CONNECTIONS_SITE_ROLE_PRESET","pluginConnectionsPermissionRefusal","leavePage","url","window","location","assign","HELP","anchor","excerpt","SitePluginConnectionsCard","props","hostId","orgSlug","pluginRef","pin","routes","api","navigate","enqueueSnackbar","blocked","declared","hasDeclared","length","load","setLoad","status","returned","refresh","answer","error","refusal","message","refused","reason","current","back","href","history","replaceState","state","cleanUrl","label","find","one","id","connectionId","variant","result","keyRefused","permissionText","spacing","map","connection","header","subheader","description","help","contentGutterX","contentGutterY","HeaderProps","action","severity","onClick","undefined","direction","sx","alignItems","role","size","color","replace","entry","connections","listingId","SitePluginConnection","configured","planAllows","upgradeHref","pluginName","String","intervalHours","onConnection","onStale","displayName","view","confirm","busy","setBusy","setError","wait","setWait","changing","setChanging","items","setItems","picked","setPicked","phase","declaresSync","ids","resource","resources","resourceId","currentChoice","choices","live","then","was","catch","failure","Error","run","kind","work","retryAfterMs","Number","body","isFinite","connect","authorizeUrl","connectStart","returnTo","choose","next","ready","sync","synced","disconnect","ok","Promise","resolve","title","confirmationText","confirmationButtonProps","planReason","blockedBy","guarded","control","Boolean","disconnectButton","disabled","actions","push","toLowerCase","credentialsSet","chip","shares","hosts","nextSync","chosen","filter","useFlexGap","flexWrap","justifyContent","index","span","tone","data-testid","planLabel","lastError","select","value","onChange","event","target","maxWidth","item","component","lastSyncAtMs","onClose"],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ export declare function registerMarketplaceConsoleServerDeclarations(): void;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /**
17
+ * The marketplace's CONSOLE-ONLY server declarations (AGL-4194), named under
18
+ * `consoleServerDeclarations` in `plugins.config.json` and run at the
19
+ * console's boot. The tenant runtime never bundles this module.
20
+ *
21
+ * - The declared-connection sync, on the console's fifteen-minute
22
+ * `plugin-console-crons` tick (`server/connections/sync.ts`): it opens
23
+ * each site's sealed grant with `PLUGIN_CONNECTIONS_TOKEN_KEY`, which only
24
+ * the console holds. Without the key every tick returns at once.
25
+ *
26
+ * Light at boot: the job's body is imported the first time a tick runs it.
27
+ */ import { registerPluginConsoleCron } from "@aglyn/aglyn/plugin-manager/plugin-console-crons";
28
+ import { BUNDLE_ID } from "./constants/bundle-common.js";
29
+ /** The sync job's id; `sync.ts` exports the same literal. */ const CONNECTION_SYNC_JOB_ID = 'marketplace-connection-sync';
30
+ export function registerMarketplaceConsoleServerDeclarations() {
31
+ registerPluginConsoleCron({
32
+ id: CONNECTION_SYNC_JOB_ID,
33
+ label: 'Plugin connection sync',
34
+ drives: 'Runs the scheduled fetch each installed marketplace plugin declares for its connection (Google reviews, for one) and writes the record the site’s pages show. If it stops, those pages keep showing the last record they had, which grows stale.',
35
+ run: async (context)=>(await import("./server/connections/sync.js")).runPluginConnectionSyncJob(context)
36
+ }, {
37
+ pluginId: BUNDLE_ID
38
+ });
39
+ }
40
+
41
+ //# sourceMappingURL=declarations.console-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/plugins/marketplace/src/lib/declarations.console-server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The marketplace's CONSOLE-ONLY server declarations (AGL-4194), named under\n * `consoleServerDeclarations` in `plugins.config.json` and run at the\n * console's boot. The tenant runtime never bundles this module.\n *\n * - The declared-connection sync, on the console's fifteen-minute\n * `plugin-console-crons` tick (`server/connections/sync.ts`): it opens\n * each site's sealed grant with `PLUGIN_CONNECTIONS_TOKEN_KEY`, which only\n * the console holds. Without the key every tick returns at once.\n *\n * Light at boot: the job's body is imported the first time a tick runs it.\n */\n\nimport { registerPluginConsoleCron } from '@aglyn/aglyn/plugin-manager/plugin-console-crons'\nimport { BUNDLE_ID } from './constants/bundle-common'\n\n/** The sync job's id; `sync.ts` exports the same literal. */\nconst CONNECTION_SYNC_JOB_ID = 'marketplace-connection-sync'\n\nexport function registerMarketplaceConsoleServerDeclarations(): void {\n registerPluginConsoleCron(\n {\n id: CONNECTION_SYNC_JOB_ID,\n label: 'Plugin connection sync',\n drives:\n 'Runs the scheduled fetch each installed marketplace plugin declares for its connection (Google reviews, for one) and writes the record the site’s pages show. If it stops, those pages keep showing the last record they had, which grows stale.',\n run: async (context) => (await import('./server/connections/sync')).runPluginConnectionSyncJob(context),\n },\n { pluginId: BUNDLE_ID },\n )\n}\n"],"names":["registerPluginConsoleCron","BUNDLE_ID","CONNECTION_SYNC_JOB_ID","registerMarketplaceConsoleServerDeclarations","id","label","drives","run","context","runPluginConnectionSyncJob","pluginId"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;CAWC,GAED,SAASA,yBAAyB,QAAQ,mDAAkD;AAC5F,SAASC,SAAS,QAAQ,+BAA2B;AAErD,2DAA2D,GAC3D,MAAMC,yBAAyB;AAE/B,OAAO,SAASC;IACdH,0BACE;QACEI,IAAIF;QACJG,OAAO;QACPC,QACE;QACFC,KAAK,OAAOC,UAAY,AAAC,CAAA,MAAM,MAAM,CAAC,+BAA2B,EAAGC,0BAA0B,CAACD;IACjG,GACA;QAAEE,UAAUT;IAAU;AAE1B"}
@@ -16,7 +16,8 @@
16
16
  */ import { registerPluginEntitlements } from "@aglyn/aglyn/plugin-manager/plugin-entitlements";
17
17
  import { registerPluginPermissions } from "@aglyn/aglyn/plugin-manager/plugin-permissions";
18
18
  import { MARKETPLACE_PUBLISHER_PERMISSIONS } from "./model/marketplace-publisher-permissions.js";
19
- import { MARKETPLACE_SITE_PERMISSIONS } from "./model/marketplace-site-permissions.js";
19
+ import { MARKETPLACE_CONNECTION_SITE_PERMISSIONS, MARKETPLACE_SITE_PERMISSIONS } from "./model/marketplace-site-permissions.js";
20
+ import { PLUGIN_CONNECTIONS_FEATURE_DECLARATION } from "./model/plugin-connections.js";
20
21
  let declared = false;
21
22
  /**
22
23
  * The plugin's DECLARATIONS (AGL-4047): installing into a site and publishing
@@ -32,7 +33,15 @@ let declared = false;
32
33
  declared = true;
33
34
  registerPluginEntitlements({
34
35
  pluginId: 'marketplace',
35
- orgPermissions: MARKETPLACE_SITE_PERMISSIONS
36
+ // Declared connections (AGL-4193): the site key that connects a plugin to
37
+ // another service, and the plan feature it rides (Starter, Pro, Custom).
38
+ orgPermissions: [
39
+ ...MARKETPLACE_SITE_PERMISSIONS,
40
+ ...MARKETPLACE_CONNECTION_SITE_PERMISSIONS
41
+ ],
42
+ features: [
43
+ PLUGIN_CONNECTIONS_FEATURE_DECLARATION
44
+ ]
36
45
  });
37
46
  registerPluginPermissions(MARKETPLACE_PUBLISHER_PERMISSIONS);
38
47
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/plugins/marketplace/src/lib/declarations.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { registerPluginEntitlements } from '@aglyn/aglyn/plugin-manager/plugin-entitlements'\nimport { registerPluginPermissions } from '@aglyn/aglyn/plugin-manager/plugin-permissions'\nimport { MARKETPLACE_PUBLISHER_PERMISSIONS } from './model/marketplace-publisher-permissions'\nimport { MARKETPLACE_SITE_PERMISSIONS } from './model/marketplace-site-permissions'\n\nlet declared = false\n\n/**\n * The plugin's DECLARATIONS (AGL-4047): installing into a site and publishing\n * from one, and the workspace's publisher acts (its profile, its published\n * listings), which the one permission catalog lists beside the core keys.\n * Loaded eagerly by the console (client and server) and the tenant's server\n * through the generated declarations manifest, so the role editor offers\n * them, a custom role or a per-member override keeps them, and the site's\n * stamp carries them. Light by construction: nothing here reaches a\n * provider, Firestore or React.\n */\nexport function registerMarketplaceDeclarations(): void {\n if (declared) return\n declared = true\n registerPluginEntitlements({ pluginId: 'marketplace', orgPermissions: MARKETPLACE_SITE_PERMISSIONS })\n registerPluginPermissions(MARKETPLACE_PUBLISHER_PERMISSIONS)\n}\n\nregisterMarketplaceDeclarations()\n"],"names":["registerPluginEntitlements","registerPluginPermissions","MARKETPLACE_PUBLISHER_PERMISSIONS","MARKETPLACE_SITE_PERMISSIONS","declared","registerMarketplaceDeclarations","pluginId","orgPermissions"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,0BAA0B,QAAQ,kDAAiD;AAC5F,SAASC,yBAAyB,QAAQ,iDAAgD;AAC1F,SAASC,iCAAiC,QAAQ,+CAA2C;AAC7F,SAASC,4BAA4B,QAAQ,0CAAsC;AAEnF,IAAIC,WAAW;AAEf;;;;;;;;;CASC,GACD,OAAO,SAASC;IACd,IAAID,UAAU;IACdA,WAAW;IACXJ,2BAA2B;QAAEM,UAAU;QAAeC,gBAAgBJ;IAA6B;IACnGF,0BAA0BC;AAC5B;AAEAG"}
1
+ {"version":3,"sources":["../../../../../../libs/plugins/marketplace/src/lib/declarations.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { registerPluginEntitlements } from '@aglyn/aglyn/plugin-manager/plugin-entitlements'\nimport { registerPluginPermissions } from '@aglyn/aglyn/plugin-manager/plugin-permissions'\nimport { MARKETPLACE_PUBLISHER_PERMISSIONS } from './model/marketplace-publisher-permissions'\nimport {\n MARKETPLACE_CONNECTION_SITE_PERMISSIONS,\n MARKETPLACE_SITE_PERMISSIONS,\n} from './model/marketplace-site-permissions'\nimport { PLUGIN_CONNECTIONS_FEATURE_DECLARATION } from './model/plugin-connections'\n\nlet declared = false\n\n/**\n * The plugin's DECLARATIONS (AGL-4047): installing into a site and publishing\n * from one, and the workspace's publisher acts (its profile, its published\n * listings), which the one permission catalog lists beside the core keys.\n * Loaded eagerly by the console (client and server) and the tenant's server\n * through the generated declarations manifest, so the role editor offers\n * them, a custom role or a per-member override keeps them, and the site's\n * stamp carries them. Light by construction: nothing here reaches a\n * provider, Firestore or React.\n */\nexport function registerMarketplaceDeclarations(): void {\n if (declared) return\n declared = true\n registerPluginEntitlements({\n pluginId: 'marketplace',\n // Declared connections (AGL-4193): the site key that connects a plugin to\n // another service, and the plan feature it rides (Starter, Pro, Custom).\n orgPermissions: [...MARKETPLACE_SITE_PERMISSIONS, ...MARKETPLACE_CONNECTION_SITE_PERMISSIONS],\n features: [PLUGIN_CONNECTIONS_FEATURE_DECLARATION],\n })\n registerPluginPermissions(MARKETPLACE_PUBLISHER_PERMISSIONS)\n}\n\nregisterMarketplaceDeclarations()\n"],"names":["registerPluginEntitlements","registerPluginPermissions","MARKETPLACE_PUBLISHER_PERMISSIONS","MARKETPLACE_CONNECTION_SITE_PERMISSIONS","MARKETPLACE_SITE_PERMISSIONS","PLUGIN_CONNECTIONS_FEATURE_DECLARATION","declared","registerMarketplaceDeclarations","pluginId","orgPermissions","features"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,0BAA0B,QAAQ,kDAAiD;AAC5F,SAASC,yBAAyB,QAAQ,iDAAgD;AAC1F,SAASC,iCAAiC,QAAQ,+CAA2C;AAC7F,SACEC,uCAAuC,EACvCC,4BAA4B,QACvB,0CAAsC;AAC7C,SAASC,sCAAsC,QAAQ,gCAA4B;AAEnF,IAAIC,WAAW;AAEf;;;;;;;;;CASC,GACD,OAAO,SAASC;IACd,IAAID,UAAU;IACdA,WAAW;IACXN,2BAA2B;QACzBQ,UAAU;QACV,0EAA0E;QAC1E,yEAAyE;QACzEC,gBAAgB;eAAIL;eAAiCD;SAAwC;QAC7FO,UAAU;YAACL;SAAuC;IACpD;IACAJ,0BAA0BC;AAC5B;AAEAK"}
@@ -1,3 +1,4 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
1
2
  /**
2
3
  * @license
3
4
  * Copyright 2026 Aglyn LLC
@@ -15,10 +16,14 @@
15
16
  * limitations under the License.
16
17
  */ // The registry from its own module, not the plugin-manager barrel: boot needs
17
18
  // the registry and nothing else.
19
+ import { registerVariableComputer } from "@aglyn/aglyn/plugin-manager/computed-variables";
18
20
  import { registerCustomFieldType } from "@aglyn/aglyn/plugin-manager/custom-fields";
19
21
  import { listPluginOrgErasers, registerPluginOrgEraser } from "@aglyn/aglyn/plugin-manager/plugin-org-erasure";
20
22
  import { registerPluginRevocationReader } from "@aglyn/aglyn/plugin-manager/plugin-revocations";
23
+ import { registerPluginSiteResourceEraser } from "@aglyn/aglyn/plugin-manager/plugin-site-resources";
24
+ import { registerPluginSiteCspSource } from "@aglyn/aglyn/plugin-manager/plugin-site-csp";
21
25
  import { registerPluginSiteStarterSource } from "@aglyn/aglyn/plugin-manager/plugin-site-starters";
26
+ import { registerSitePageEnricherService } from "@aglyn/aglyn/plugin-manager/site-page-enricher-services";
22
27
  import { BUNDLE_ID } from "./constants/bundle-common.js";
23
28
  import { RATING_FIELD } from "./model/rating-field.js";
24
29
  /**
@@ -46,12 +51,39 @@ import { RATING_FIELD } from "./model/rating-field.js";
46
51
  */ if (!listPluginOrgErasers().includes(BUNDLE_ID)) {
47
52
  registerPluginOrgEraser(async (request)=>{
48
53
  const { createPublisherIdentityEraser } = await import("./server/publisher-erasure.js");
49
- return createPublisherIdentityEraser()(request);
54
+ const identity = await createPublisherIdentityEraser()(request);
55
+ /*
56
+ * The OAuth app credentials the workspace's listings hold for their
57
+ * declared connections (AGL-4193). The erasure also sweeps them by
58
+ * `orgId`; deleting them here as well counts them on the audit
59
+ * record. Isolated from the REQUIRED identity share above: a failure
60
+ * here is reported as an unmeasured figure, never as that share
61
+ * failing.
62
+ */ let connectionCredentials = null;
63
+ try {
64
+ const { erasePluginConnectionCredentials } = await import("./server/connections/erasure.js");
65
+ connectionCredentials = Number((await erasePluginConnectionCredentials(request))['connectionCredentials']);
66
+ } catch (error) {
67
+ console.error('[marketplace] erasing plugin connection credentials failed', error);
68
+ }
69
+ return _extends({}, identity, {
70
+ connectionCredentials
71
+ });
50
72
  }, {
51
73
  pluginId: BUNDLE_ID
52
74
  });
53
75
  }
54
76
  /*
77
+ * A site's declared connections (AGL-4193): its grants revoked at each
78
+ * provider before the site's tree is deleted. Nothing is listed on the
79
+ * Delete site card. The Admin SDK arrives with the first delete.
80
+ */ registerPluginSiteResourceEraser({
81
+ preview: async ()=>(await import("./server/connections/erasure.js")).pluginConnectionsSiteEraser.preview(),
82
+ erase: async (request)=>(await import("./server/connections/erasure.js")).pluginConnectionsSiteEraser.erase(request)
83
+ }, {
84
+ pluginId: BUNDLE_ID
85
+ });
86
+ /*
55
87
  * The kill switch (`plugin-revocations`, AGL-3080): the send path asks it
56
88
  * before mailing a design installed from a listing, in whichever process
57
89
  * sends. The Admin SDK arrives with the first read.
@@ -79,6 +111,41 @@ import { RATING_FIELD } from "./model/rating-field.js";
79
111
  }, {
80
112
  pluginId: BUNDLE_ID
81
113
  });
114
+ registerPluginConnectionsSite();
115
+ }
116
+ /** The site's readers of a site's declared connections (AGL-4195), loaded with their first use. */ const siteRecords = ()=>import("./server/connections/site-records-platform.js");
117
+ /**
118
+ * What a published site shows of its plugins' declared connections
119
+ * (AGL-4195), registered wherever these declarations run and used by the
120
+ * tenant's page render and policy verdict:
121
+ *
122
+ * - the page enricher hands each signed element on the page its own
123
+ * listing's connection records, server-rendered; it reads nothing on a
124
+ * page without a marketplace element;
125
+ * - the variable computer gives each record's declared summary fields to
126
+ * the page as `{{var:…}}` values;
127
+ * - the CSP source admits the image hosts the site's signed plugins
128
+ * declare, on those sites only.
129
+ *
130
+ * All three go through the plugin-service registry, which `instrumentation.ts`
131
+ * and the page route share (a module-scope list filled at boot is empty in
132
+ * the route, AGL-3947), and each names this plugin as its owner, so the
133
+ * page's "who contributed" narrowing still holds. Registering again replaces.
134
+ */ function registerPluginConnectionsSite() {
135
+ registerSitePageEnricherService(async (context)=>(await siteRecords()).connectionRecordsEnricher(context), {
136
+ pluginId: BUNDLE_ID,
137
+ key: 'connection-records'
138
+ });
139
+ registerVariableComputer({
140
+ prepare: async (hostId)=>(await siteRecords()).connectionVariableComputer.prepare(hostId)
141
+ }, {
142
+ pluginId: BUNDLE_ID
143
+ });
144
+ registerPluginSiteCspSource({
145
+ hosts: async (input)=>(await siteRecords()).pluginImageHostsSource.hosts(input)
146
+ }, {
147
+ pluginId: BUNDLE_ID
148
+ });
82
149
  }
83
150
 
84
151
  //# sourceMappingURL=declarations.server.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/plugins/marketplace/src/lib/declarations.server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// The registry from its own module, not the plugin-manager barrel: boot needs\n// the registry and nothing else.\nimport { registerCustomFieldType } from '@aglyn/aglyn/plugin-manager/custom-fields'\nimport {\n listPluginOrgErasers,\n registerPluginOrgEraser,\n} from '@aglyn/aglyn/plugin-manager/plugin-org-erasure'\nimport { registerPluginRevocationReader } from '@aglyn/aglyn/plugin-manager/plugin-revocations'\nimport { registerPluginSiteStarterSource } from '@aglyn/aglyn/plugin-manager/plugin-site-starters'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { RATING_FIELD } from './model/rating-field'\n\n/**\n * The marketplace plugin's server declarations: the light registrations core\n * reads at boot, before any surface loads.\n *\n * The `rating` field type's validator (AGL-434), the marketplace's share of\n * a workspace erasure, its kill switch as another plugin reads it, and the\n * site starters \"Create site\" offers (AGL-4049).\n *\n * The validator: A dataset field of this type\n * can be written by a path that never loads the marketplace — a visitor's\n * form submission and an automation step, both in the tenant (AGL-2773) — so\n * the validator registers from here, which every app runs at boot, rather\n * than only from the console API surface. Pure data: no React, no Admin SDK.\n */\nexport function registerMarketplaceServerDeclarations(): void {\n registerCustomFieldType(RATING_FIELD)\n /*\n * The organization's public marketplace identity, erased with it (AGL-1970).\n * Declared REQUIRED in `plugins.config.json`: the profile carries a payout\n * identifier on a world-readable document, so an erasure refuses to run\n * without this eraser rather than report a workspace erased with it\n * standing. From here, which every app and the erasure script run at boot;\n * the Admin SDK arrives with the first erasure, not with the boot.\n */\n if (!listPluginOrgErasers().includes(BUNDLE_ID)) {\n registerPluginOrgEraser(\n async (request) => {\n const { createPublisherIdentityEraser } = await import('./server/publisher-erasure')\n return createPublisherIdentityEraser()(request)\n },\n { pluginId: BUNDLE_ID },\n )\n }\n /*\n * The kill switch (`plugin-revocations`, AGL-3080): the send path asks it\n * before mailing a design installed from a listing, in whichever process\n * sends. The Admin SDK arrives with the first read.\n */\n registerPluginRevocationReader(\n {\n async revocation(listingId) {\n const { marketplaceRevocationReader } = await import('./server/revocation-reader')\n return marketplaceRevocationReader.revocation(listingId)\n },\n },\n { pluginId: BUNDLE_ID },\n )\n /*\n * Site starters (AGL-4049): \"Create site\" searches the marketplace's\n * `siteStarter` listings and creates a site from the one picked, through\n * core's `core.site-starters` seam. Registering reads nothing; the Admin SDK\n * arrives with the first search, not with the boot. The same plugin\n * registering again replaces itself, so a re-run of these declarations is\n * safe.\n */\n const starters = () => import('./server/site-starter-source').then((m) => m.marketplaceSiteStarterSource)\n registerPluginSiteStarterSource(\n {\n search: async (input, viewer) => (await starters()).search(input, viewer),\n resolve: async (choice, viewer) => (await starters()).resolve(choice, viewer),\n installed: async (choice, viewer, site) => (await starters()).installed(choice, viewer, site),\n describe: async (choice, viewer) => (await starters()).describe(choice, viewer),\n },\n { pluginId: BUNDLE_ID },\n )\n}\n"],"names":["registerCustomFieldType","listPluginOrgErasers","registerPluginOrgEraser","registerPluginRevocationReader","registerPluginSiteStarterSource","BUNDLE_ID","RATING_FIELD","registerMarketplaceServerDeclarations","includes","request","createPublisherIdentityEraser","pluginId","revocation","listingId","marketplaceRevocationReader","starters","then","m","marketplaceSiteStarterSource","search","input","viewer","resolve","choice","installed","site","describe"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,8EAA8E;AAC9E,iCAAiC;AACjC,SAASA,uBAAuB,QAAQ,4CAA2C;AACnF,SACEC,oBAAoB,EACpBC,uBAAuB,QAClB,iDAAgD;AACvD,SAASC,8BAA8B,QAAQ,iDAAgD;AAC/F,SAASC,+BAA+B,QAAQ,mDAAkD;AAClG,SAASC,SAAS,QAAQ,+BAA2B;AACrD,SAASC,YAAY,QAAQ,0BAAsB;AAEnD;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASC;IACdP,wBAAwBM;IACxB;;;;;;;GAOC,GACD,IAAI,CAACL,uBAAuBO,QAAQ,CAACH,YAAY;QAC/CH,wBACE,OAAOO;YACL,MAAM,EAAEC,6BAA6B,EAAE,GAAG,MAAM,MAAM,CAAC;YACvD,OAAOA,gCAAgCD;QACzC,GACA;YAAEE,UAAUN;QAAU;IAE1B;IACA;;;;GAIC,GACDF,+BACE;QACE,MAAMS,YAAWC,SAAS;YACxB,MAAM,EAAEC,2BAA2B,EAAE,GAAG,MAAM,MAAM,CAAC;YACrD,OAAOA,4BAA4BF,UAAU,CAACC;QAChD;IACF,GACA;QAAEF,UAAUN;IAAU;IAExB;;;;;;;GAOC,GACD,MAAMU,WAAW,IAAM,MAAM,CAAC,mCAAgCC,IAAI,CAAC,CAACC,IAAMA,EAAEC,4BAA4B;IACxGd,gCACE;QACEe,QAAQ,OAAOC,OAAOC,SAAW,AAAC,CAAA,MAAMN,UAAS,EAAGI,MAAM,CAACC,OAAOC;QAClEC,SAAS,OAAOC,QAAQF,SAAW,AAAC,CAAA,MAAMN,UAAS,EAAGO,OAAO,CAACC,QAAQF;QACtEG,WAAW,OAAOD,QAAQF,QAAQI,OAAS,AAAC,CAAA,MAAMV,UAAS,EAAGS,SAAS,CAACD,QAAQF,QAAQI;QACxFC,UAAU,OAAOH,QAAQF,SAAW,AAAC,CAAA,MAAMN,UAAS,EAAGW,QAAQ,CAACH,QAAQF;IAC1E,GACA;QAAEV,UAAUN;IAAU;AAE1B"}
1
+ {"version":3,"sources":["../../../../../../libs/plugins/marketplace/src/lib/declarations.server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// The registry from its own module, not the plugin-manager barrel: boot needs\n// the registry and nothing else.\nimport { registerVariableComputer } from '@aglyn/aglyn/plugin-manager/computed-variables'\nimport { registerCustomFieldType } from '@aglyn/aglyn/plugin-manager/custom-fields'\nimport {\n listPluginOrgErasers,\n registerPluginOrgEraser,\n} from '@aglyn/aglyn/plugin-manager/plugin-org-erasure'\nimport { registerPluginRevocationReader } from '@aglyn/aglyn/plugin-manager/plugin-revocations'\nimport { registerPluginSiteResourceEraser } from '@aglyn/aglyn/plugin-manager/plugin-site-resources'\nimport { registerPluginSiteCspSource } from '@aglyn/aglyn/plugin-manager/plugin-site-csp'\nimport { registerPluginSiteStarterSource } from '@aglyn/aglyn/plugin-manager/plugin-site-starters'\nimport { registerSitePageEnricherService } from '@aglyn/aglyn/plugin-manager/site-page-enricher-services'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { RATING_FIELD } from './model/rating-field'\n\n/**\n * The marketplace plugin's server declarations: the light registrations core\n * reads at boot, before any surface loads.\n *\n * The `rating` field type's validator (AGL-434), the marketplace's share of\n * a workspace erasure, its kill switch as another plugin reads it, and the\n * site starters \"Create site\" offers (AGL-4049).\n *\n * The validator: A dataset field of this type\n * can be written by a path that never loads the marketplace — a visitor's\n * form submission and an automation step, both in the tenant (AGL-2773) — so\n * the validator registers from here, which every app runs at boot, rather\n * than only from the console API surface. Pure data: no React, no Admin SDK.\n */\nexport function registerMarketplaceServerDeclarations(): void {\n registerCustomFieldType(RATING_FIELD)\n /*\n * The organization's public marketplace identity, erased with it (AGL-1970).\n * Declared REQUIRED in `plugins.config.json`: the profile carries a payout\n * identifier on a world-readable document, so an erasure refuses to run\n * without this eraser rather than report a workspace erased with it\n * standing. From here, which every app and the erasure script run at boot;\n * the Admin SDK arrives with the first erasure, not with the boot.\n */\n if (!listPluginOrgErasers().includes(BUNDLE_ID)) {\n registerPluginOrgEraser(\n async (request) => {\n const { createPublisherIdentityEraser } = await import('./server/publisher-erasure')\n const identity = await createPublisherIdentityEraser()(request)\n /*\n * The OAuth app credentials the workspace's listings hold for their\n * declared connections (AGL-4193). The erasure also sweeps them by\n * `orgId`; deleting them here as well counts them on the audit\n * record. Isolated from the REQUIRED identity share above: a failure\n * here is reported as an unmeasured figure, never as that share\n * failing.\n */\n let connectionCredentials: number | null = null\n try {\n const { erasePluginConnectionCredentials } = await import('./server/connections/erasure')\n connectionCredentials = Number((await erasePluginConnectionCredentials(request))['connectionCredentials'])\n } catch (error) {\n console.error('[marketplace] erasing plugin connection credentials failed', error)\n }\n return { ...identity, connectionCredentials }\n },\n { pluginId: BUNDLE_ID },\n )\n }\n /*\n * A site's declared connections (AGL-4193): its grants revoked at each\n * provider before the site's tree is deleted. Nothing is listed on the\n * Delete site card. The Admin SDK arrives with the first delete.\n */\n registerPluginSiteResourceEraser(\n {\n preview: async () => (await import('./server/connections/erasure')).pluginConnectionsSiteEraser.preview(),\n erase: async (request) => (await import('./server/connections/erasure')).pluginConnectionsSiteEraser.erase(request),\n },\n { pluginId: BUNDLE_ID },\n )\n /*\n * The kill switch (`plugin-revocations`, AGL-3080): the send path asks it\n * before mailing a design installed from a listing, in whichever process\n * sends. The Admin SDK arrives with the first read.\n */\n registerPluginRevocationReader(\n {\n async revocation(listingId) {\n const { marketplaceRevocationReader } = await import('./server/revocation-reader')\n return marketplaceRevocationReader.revocation(listingId)\n },\n },\n { pluginId: BUNDLE_ID },\n )\n /*\n * Site starters (AGL-4049): \"Create site\" searches the marketplace's\n * `siteStarter` listings and creates a site from the one picked, through\n * core's `core.site-starters` seam. Registering reads nothing; the Admin SDK\n * arrives with the first search, not with the boot. The same plugin\n * registering again replaces itself, so a re-run of these declarations is\n * safe.\n */\n const starters = () => import('./server/site-starter-source').then((m) => m.marketplaceSiteStarterSource)\n registerPluginSiteStarterSource(\n {\n search: async (input, viewer) => (await starters()).search(input, viewer),\n resolve: async (choice, viewer) => (await starters()).resolve(choice, viewer),\n installed: async (choice, viewer, site) => (await starters()).installed(choice, viewer, site),\n describe: async (choice, viewer) => (await starters()).describe(choice, viewer),\n },\n { pluginId: BUNDLE_ID },\n )\n registerPluginConnectionsSite()\n}\n\n/** The site's readers of a site's declared connections (AGL-4195), loaded with their first use. */\nconst siteRecords = () => import('./server/connections/site-records-platform')\n\n/**\n * What a published site shows of its plugins' declared connections\n * (AGL-4195), registered wherever these declarations run and used by the\n * tenant's page render and policy verdict:\n *\n * - the page enricher hands each signed element on the page its own\n * listing's connection records, server-rendered; it reads nothing on a\n * page without a marketplace element;\n * - the variable computer gives each record's declared summary fields to\n * the page as `{{var:…}}` values;\n * - the CSP source admits the image hosts the site's signed plugins\n * declare, on those sites only.\n *\n * All three go through the plugin-service registry, which `instrumentation.ts`\n * and the page route share (a module-scope list filled at boot is empty in\n * the route, AGL-3947), and each names this plugin as its owner, so the\n * page's \"who contributed\" narrowing still holds. Registering again replaces.\n */\nfunction registerPluginConnectionsSite(): void {\n registerSitePageEnricherService(\n async (context) => (await siteRecords()).connectionRecordsEnricher(context),\n { pluginId: BUNDLE_ID, key: 'connection-records' },\n )\n registerVariableComputer(\n { prepare: async (hostId) => (await siteRecords()).connectionVariableComputer.prepare(hostId) },\n { pluginId: BUNDLE_ID },\n )\n registerPluginSiteCspSource(\n { hosts: async (input) => (await siteRecords()).pluginImageHostsSource.hosts(input) },\n { pluginId: BUNDLE_ID },\n )\n}\n"],"names":["registerVariableComputer","registerCustomFieldType","listPluginOrgErasers","registerPluginOrgEraser","registerPluginRevocationReader","registerPluginSiteResourceEraser","registerPluginSiteCspSource","registerPluginSiteStarterSource","registerSitePageEnricherService","BUNDLE_ID","RATING_FIELD","registerMarketplaceServerDeclarations","includes","request","createPublisherIdentityEraser","identity","connectionCredentials","erasePluginConnectionCredentials","Number","error","console","pluginId","preview","pluginConnectionsSiteEraser","erase","revocation","listingId","marketplaceRevocationReader","starters","then","m","marketplaceSiteStarterSource","search","input","viewer","resolve","choice","installed","site","describe","registerPluginConnectionsSite","siteRecords","context","connectionRecordsEnricher","key","prepare","hostId","connectionVariableComputer","hosts","pluginImageHostsSource"],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ import type { PluginConnectionCardView } from '../model/plugin-connection-card';
2
+ import { type PluginConnectionCredentialsStatus, type PluginConnectionStatusEntry } from '../model/plugin-connections';
3
+ /** Why a connection route refused, as the routes name it. */
4
+ export type PluginConnectionRefusal = 'permission' | 'plan' | 'credentials' | 'not-connected' | 'reconnect' | 'provider' | 'choose-first' | 'no-sync' | 'too-soon';
5
+ /** A route's refusal: its own sentence, its HTTP status and, when it says, why. */
6
+ export declare class PluginConnectionRouteError extends Error {
7
+ readonly status: number;
8
+ readonly reason: PluginConnectionRefusal | null;
9
+ /** The rest of the refusal's JSON: `retryAfterMs` and the view on a `too-soon`. */
10
+ readonly body: Readonly<Record<string, unknown>>;
11
+ constructor(message: string, status: number, reason: PluginConnectionRefusal | null,
12
+ /** The rest of the refusal's JSON: `retryAfterMs` and the view on a `too-soon`. */
13
+ body?: Readonly<Record<string, unknown>>);
14
+ }
15
+ export interface PluginConnectionsStatusAnswer {
16
+ configured: boolean;
17
+ planAllows: boolean;
18
+ connections: PluginConnectionStatusEntry[];
19
+ }
20
+ export interface PluginConnectionCredentialsAnswer {
21
+ configured: boolean;
22
+ credentials: PluginConnectionCredentialsStatus[];
23
+ }
24
+ /** The site's connection routes, for one site. */
25
+ export interface PluginConnectionsApi {
26
+ status(listingId: string): Promise<PluginConnectionsStatusAnswer>;
27
+ connectStart(input: {
28
+ listingId: string;
29
+ connectionId: string;
30
+ returnTo: string;
31
+ }): Promise<{
32
+ authorizeUrl: string;
33
+ }>;
34
+ resources(input: {
35
+ listingId: string;
36
+ connectionId: string;
37
+ resource: string;
38
+ }): Promise<{
39
+ items: Array<{
40
+ id: string;
41
+ label: string;
42
+ }>;
43
+ }>;
44
+ choose(input: {
45
+ listingId: string;
46
+ connectionId: string;
47
+ resource: string;
48
+ id: string;
49
+ }): Promise<{
50
+ connection: PluginConnectionCardView | null;
51
+ }>;
52
+ sync(input: {
53
+ listingId: string;
54
+ connectionId: string;
55
+ }): Promise<{
56
+ connection: PluginConnectionCardView | null;
57
+ synced: boolean;
58
+ }>;
59
+ disconnect(input: {
60
+ listingId: string;
61
+ connectionId: string;
62
+ }): Promise<{
63
+ connection: null;
64
+ }>;
65
+ }
66
+ /** A listing's OAuth app credentials, for its publisher. */
67
+ export interface PluginConnectionCredentialsApi {
68
+ read(listingId: string): Promise<PluginConnectionCredentialsAnswer>;
69
+ save(input: {
70
+ listingId: string;
71
+ connectionId: string;
72
+ clientId: string;
73
+ clientSecret: string;
74
+ }): Promise<PluginConnectionCredentialsAnswer>;
75
+ clear(input: {
76
+ listingId: string;
77
+ connectionId: string;
78
+ }): Promise<PluginConnectionCredentialsAnswer>;
79
+ }
80
+ /** One call to a connection route: the answer, or the route's refusal as a {@link PluginConnectionRouteError}. */
81
+ export declare function callPluginConnectionsRoute<T>(user: unknown, path: string, init: {
82
+ method: 'GET' | 'POST';
83
+ query?: Record<string, string>;
84
+ body?: Record<string, unknown>;
85
+ }): Promise<T>;
86
+ /** The connection routes for one site, as the signed-in member. */
87
+ export declare function usePluginConnectionsApi(hostId: string): PluginConnectionsApi;
88
+ /** A listing's credentials routes, as the signed-in member. */
89
+ export declare function usePluginConnectionCredentialsApi(): PluginConnectionCredentialsApi;
@@ -0,0 +1,132 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { _ as _extends } from "@swc/helpers/_/_extends";
18
+ import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
19
+ import { useUser } from "@aglyn/tenant-feature-instance";
20
+ import { useMemo } from "react";
21
+ import { PLUGIN_CONNECTIONS_API_ROUTES } from "../model/plugin-connections.js";
22
+ /** A route's refusal: its own sentence, its HTTP status and, when it says, why. */ export class PluginConnectionRouteError extends Error {
23
+ constructor(message, status, reason, /** The rest of the refusal's JSON: `retryAfterMs` and the view on a `too-soon`. */ body = {}){
24
+ super(message), this.status = status, this.reason = reason, this.body = body;
25
+ this.name = 'PluginConnectionRouteError';
26
+ }
27
+ }
28
+ const FALLBACK = 'That did not work. Try again.';
29
+ const REASONS = new Set([
30
+ 'permission',
31
+ 'plan',
32
+ 'credentials',
33
+ 'not-connected',
34
+ 'reconnect',
35
+ 'provider',
36
+ 'choose-first',
37
+ 'no-sync',
38
+ 'too-soon'
39
+ ]);
40
+ /** One call to a connection route: the answer, or the route's refusal as a {@link PluginConnectionRouteError}. */ export async function callPluginConnectionsRoute(user, path, init) {
41
+ const search = init.query ? `?${new URLSearchParams(init.query).toString()}` : '';
42
+ let response;
43
+ try {
44
+ response = await authorizedFetch(user, `/api/${path}${search}`, _extends({
45
+ method: init.method
46
+ }, init.body ? {
47
+ headers: {
48
+ 'Content-Type': 'application/json'
49
+ },
50
+ body: JSON.stringify(init.body)
51
+ } : {}));
52
+ } catch (unused) {
53
+ throw new PluginConnectionRouteError(FALLBACK, 0, null);
54
+ }
55
+ const data = await response.json().catch(()=>null);
56
+ if (!response.ok) {
57
+ const reason = typeof (data == null ? void 0 : data.reason) === 'string' && REASONS.has(data.reason) ? data.reason : null;
58
+ const message = typeof (data == null ? void 0 : data.error) === 'string' && data.error ? data.error : FALLBACK;
59
+ throw new PluginConnectionRouteError(message, response.status, reason, data != null ? data : {});
60
+ }
61
+ return data != null ? data : {};
62
+ }
63
+ /** The connection routes for one site, as the signed-in member. */ export function usePluginConnectionsApi(hostId) {
64
+ const { data: user } = useUser();
65
+ return useMemo(()=>({
66
+ status: (listingId)=>callPluginConnectionsRoute(user, PLUGIN_CONNECTIONS_API_ROUTES.status, {
67
+ method: 'GET',
68
+ query: {
69
+ hostId,
70
+ listingId
71
+ }
72
+ }),
73
+ connectStart: (input)=>callPluginConnectionsRoute(user, PLUGIN_CONNECTIONS_API_ROUTES.connectStart, {
74
+ method: 'POST',
75
+ body: _extends({
76
+ hostId
77
+ }, input)
78
+ }),
79
+ resources: (input)=>callPluginConnectionsRoute(user, PLUGIN_CONNECTIONS_API_ROUTES.resources, {
80
+ method: 'GET',
81
+ query: _extends({
82
+ hostId
83
+ }, input)
84
+ }),
85
+ choose: (input)=>callPluginConnectionsRoute(user, PLUGIN_CONNECTIONS_API_ROUTES.choose, {
86
+ method: 'POST',
87
+ body: _extends({
88
+ hostId
89
+ }, input)
90
+ }),
91
+ sync: (input)=>callPluginConnectionsRoute(user, PLUGIN_CONNECTIONS_API_ROUTES.sync, {
92
+ method: 'POST',
93
+ body: _extends({
94
+ hostId
95
+ }, input)
96
+ }),
97
+ disconnect: (input)=>callPluginConnectionsRoute(user, PLUGIN_CONNECTIONS_API_ROUTES.disconnect, {
98
+ method: 'POST',
99
+ body: _extends({
100
+ hostId
101
+ }, input)
102
+ })
103
+ }), [
104
+ user,
105
+ hostId
106
+ ]);
107
+ }
108
+ /** A listing's credentials routes, as the signed-in member. */ export function usePluginConnectionCredentialsApi() {
109
+ const { data: user } = useUser();
110
+ return useMemo(()=>({
111
+ read: (listingId)=>callPluginConnectionsRoute(user, PLUGIN_CONNECTIONS_API_ROUTES.credentials, {
112
+ method: 'GET',
113
+ query: {
114
+ listingId
115
+ }
116
+ }),
117
+ save: (input)=>callPluginConnectionsRoute(user, PLUGIN_CONNECTIONS_API_ROUTES.credentials, {
118
+ method: 'POST',
119
+ body: _extends({}, input)
120
+ }),
121
+ clear: (input)=>callPluginConnectionsRoute(user, PLUGIN_CONNECTIONS_API_ROUTES.credentials, {
122
+ method: 'POST',
123
+ body: _extends({}, input, {
124
+ clear: true
125
+ })
126
+ })
127
+ }), [
128
+ user
129
+ ]);
130
+ }
131
+
132
+ //# sourceMappingURL=use-plugin-connections-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/hooks/use-plugin-connections-api.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { useMemo } from 'react'\nimport type { PluginConnectionCardView } from '../model/plugin-connection-card'\nimport {\n PLUGIN_CONNECTIONS_API_ROUTES,\n type PluginConnectionCredentialsStatus,\n type PluginConnectionStatusEntry,\n} from '../model/plugin-connections'\n\n/** Why a connection route refused, as the routes name it. */\nexport type PluginConnectionRefusal =\n | 'permission'\n | 'plan'\n | 'credentials'\n | 'not-connected'\n | 'reconnect'\n | 'provider'\n | 'choose-first'\n | 'no-sync'\n | 'too-soon'\n\n/** A route's refusal: its own sentence, its HTTP status and, when it says, why. */\nexport class PluginConnectionRouteError extends Error {\n constructor(\n message: string,\n readonly status: number,\n readonly reason: PluginConnectionRefusal | null,\n /** The rest of the refusal's JSON: `retryAfterMs` and the view on a `too-soon`. */\n readonly body: Readonly<Record<string, unknown>> = {},\n ) {\n super(message)\n this.name = 'PluginConnectionRouteError'\n }\n}\n\nexport interface PluginConnectionsStatusAnswer {\n configured: boolean\n planAllows: boolean\n connections: PluginConnectionStatusEntry[]\n}\n\nexport interface PluginConnectionCredentialsAnswer {\n configured: boolean\n credentials: PluginConnectionCredentialsStatus[]\n}\n\n/** The site's connection routes, for one site. */\nexport interface PluginConnectionsApi {\n status(listingId: string): Promise<PluginConnectionsStatusAnswer>\n connectStart(input: { listingId: string; connectionId: string; returnTo: string }): Promise<{ authorizeUrl: string }>\n resources(input: { listingId: string; connectionId: string; resource: string }): Promise<{ items: Array<{ id: string; label: string }> }>\n choose(input: { listingId: string; connectionId: string; resource: string; id: string }): Promise<{ connection: PluginConnectionCardView | null }>\n sync(input: { listingId: string; connectionId: string }): Promise<{ connection: PluginConnectionCardView | null; synced: boolean }>\n disconnect(input: { listingId: string; connectionId: string }): Promise<{ connection: null }>\n}\n\n/** A listing's OAuth app credentials, for its publisher. */\nexport interface PluginConnectionCredentialsApi {\n read(listingId: string): Promise<PluginConnectionCredentialsAnswer>\n save(input: { listingId: string; connectionId: string; clientId: string; clientSecret: string }): Promise<PluginConnectionCredentialsAnswer>\n clear(input: { listingId: string; connectionId: string }): Promise<PluginConnectionCredentialsAnswer>\n}\n\nconst FALLBACK = 'That did not work. Try again.'\nconst REASONS: ReadonlySet<string> = new Set([\n 'permission',\n 'plan',\n 'credentials',\n 'not-connected',\n 'reconnect',\n 'provider',\n 'choose-first',\n 'no-sync',\n 'too-soon',\n])\n\n/** One call to a connection route: the answer, or the route's refusal as a {@link PluginConnectionRouteError}. */\nexport async function callPluginConnectionsRoute<T>(\n user: unknown,\n path: string,\n init: { method: 'GET' | 'POST'; query?: Record<string, string>; body?: Record<string, unknown> },\n): Promise<T> {\n const search = init.query ? `?${new URLSearchParams(init.query).toString()}` : ''\n let response: Response\n try {\n response = await authorizedFetch(user as never, `/api/${path}${search}`, {\n method: init.method,\n ...(init.body\n ? { headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(init.body) }\n : {}),\n })\n } catch {\n throw new PluginConnectionRouteError(FALLBACK, 0, null)\n }\n const data = (await response.json().catch(() => null)) as (Record<string, unknown> & { error?: unknown; reason?: unknown }) | null\n if (!response.ok) {\n const reason = typeof data?.reason === 'string' && REASONS.has(data.reason) ? (data.reason as PluginConnectionRefusal) : null\n const message = typeof data?.error === 'string' && data.error ? data.error : FALLBACK\n throw new PluginConnectionRouteError(message, response.status, reason, data ?? {})\n }\n return (data ?? {}) as T\n}\n\n/** The connection routes for one site, as the signed-in member. */\nexport function usePluginConnectionsApi(hostId: string): PluginConnectionsApi {\n const { data: user } = useUser()\n return useMemo<PluginConnectionsApi>(\n () => ({\n status: (listingId) =>\n callPluginConnectionsRoute(user, PLUGIN_CONNECTIONS_API_ROUTES.status, { method: 'GET', query: { hostId, listingId } }),\n connectStart: (input) =>\n callPluginConnectionsRoute(user, PLUGIN_CONNECTIONS_API_ROUTES.connectStart, { method: 'POST', body: { hostId, ...input } }),\n resources: (input) =>\n callPluginConnectionsRoute(user, PLUGIN_CONNECTIONS_API_ROUTES.resources, { method: 'GET', query: { hostId, ...input } }),\n choose: (input) =>\n callPluginConnectionsRoute(user, PLUGIN_CONNECTIONS_API_ROUTES.choose, { method: 'POST', body: { hostId, ...input } }),\n sync: (input) =>\n callPluginConnectionsRoute(user, PLUGIN_CONNECTIONS_API_ROUTES.sync, { method: 'POST', body: { hostId, ...input } }),\n disconnect: (input) =>\n callPluginConnectionsRoute(user, PLUGIN_CONNECTIONS_API_ROUTES.disconnect, { method: 'POST', body: { hostId, ...input } }),\n }),\n [user, hostId],\n )\n}\n\n/** A listing's credentials routes, as the signed-in member. */\nexport function usePluginConnectionCredentialsApi(): PluginConnectionCredentialsApi {\n const { data: user } = useUser()\n return useMemo<PluginConnectionCredentialsApi>(\n () => ({\n read: (listingId) =>\n callPluginConnectionsRoute(user, PLUGIN_CONNECTIONS_API_ROUTES.credentials, { method: 'GET', query: { listingId } }),\n save: (input) =>\n callPluginConnectionsRoute(user, PLUGIN_CONNECTIONS_API_ROUTES.credentials, { method: 'POST', body: { ...input } }),\n clear: (input) =>\n callPluginConnectionsRoute(user, PLUGIN_CONNECTIONS_API_ROUTES.credentials, {\n method: 'POST',\n body: { ...input, clear: true },\n }),\n }),\n [user],\n )\n}\n"],"names":["authorizedFetch","useUser","useMemo","PLUGIN_CONNECTIONS_API_ROUTES","PluginConnectionRouteError","Error","message","status","reason","body","name","FALLBACK","REASONS","Set","callPluginConnectionsRoute","user","path","init","search","query","URLSearchParams","toString","response","method","headers","JSON","stringify","data","json","catch","ok","has","error","usePluginConnectionsApi","hostId","listingId","connectStart","input","resources","choose","sync","disconnect","usePluginConnectionCredentialsApi","read","credentials","save","clear"],"mappings":"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"}