@aglyn/plugins-marketplace 1.15.2 → 1.17.0

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 (32) hide show
  1. package/package.json +15 -15
  2. package/src/lib/components/listing-content.component.js +32 -14
  3. package/src/lib/components/listing-content.component.js.map +1 -1
  4. package/src/lib/components/site-plugin-connections-card.component.js +4 -3
  5. package/src/lib/components/site-plugin-connections-card.component.js.map +1 -1
  6. package/src/lib/model/editor-connections.d.ts +1 -1
  7. package/src/lib/model/editor-connections.js +13 -2
  8. package/src/lib/model/editor-connections.js.map +1 -1
  9. package/src/lib/model/marketplace.d.ts +11 -0
  10. package/src/lib/model/marketplace.js +12 -2
  11. package/src/lib/model/marketplace.js.map +1 -1
  12. package/src/lib/model/plugin-connection-card.d.ts +6 -0
  13. package/src/lib/model/plugin-connection-card.js +8 -0
  14. package/src/lib/model/plugin-connection-card.js.map +1 -1
  15. package/src/lib/model/plugin-connections.d.ts +14 -0
  16. package/src/lib/model/plugin-connections.js +18 -1
  17. package/src/lib/model/plugin-connections.js.map +1 -1
  18. package/src/lib/native-nav.js +26 -1
  19. package/src/lib/native-nav.js.map +1 -1
  20. package/src/lib/server/admin-reviews.js +9 -0
  21. package/src/lib/server/admin-reviews.js.map +1 -1
  22. package/src/lib/server/admin-trust.js +11 -0
  23. package/src/lib/server/admin-trust.js.map +1 -1
  24. package/src/lib/server/connections/site-records-platform.js +8 -5
  25. package/src/lib/server/connections/site-records-platform.js.map +1 -1
  26. package/src/lib/server/install-plugin.js +34 -19
  27. package/src/lib/server/install-plugin.js.map +1 -1
  28. package/src/lib/server/listing-versions.js +7 -2
  29. package/src/lib/server/listing-versions.js.map +1 -1
  30. package/src/lib/server/revoke-cache-drop.d.ts +29 -34
  31. package/src/lib/server/revoke-cache-drop.js +43 -49
  32. package/src/lib/server/revoke-cache-drop.js.map +1 -1
@@ -27,7 +27,7 @@ import { useSiteKeys } from "@aglyn/tenant-feature-instance/hooks/use-site-keys"
27
27
  import { Alert, Button, CircularProgress, MenuItem, Stack, TextField, Typography } from "@mui/material";
28
28
  import { useCallback, useEffect, useRef, useState } from "react";
29
29
  import { PluginConnectionRouteError, usePluginConnectionsApi } from "../hooks/use-plugin-connections-api.js";
30
- import { declaredPluginConnections, declaredSyncIntervalHours, formatConnectionTime, nextResourceToChoose, PLUGIN_CONNECTION_PHASE_CHIP, PLUGIN_CONNECTIONS_NOT_CONFIGURED_TEXT, pluginConnectionNextSyncAtMs, pluginConnectionNotSetUpText, pluginConnectionPhase, pluginConnectionResultMessage, pluginConnectionSharesText, pluginConnectionTooSoonText, readPluginConnectionReturn } from "../model/plugin-connection-card.js";
30
+ import { declaredPluginConnections, declaredSyncIntervalHours, formatConnectionTime, nextResourceToChoose, PLUGIN_CONNECTION_PHASE_CHIP, PLUGIN_CONNECTIONS_NOT_CONFIGURED_TEXT, pluginConnectionNextSyncAtMs, pluginConnectionNotSetUpText, pluginConnectionPhase, pluginConnectionResultMessage, pluginConnectionSharesText, pluginConnectionSyncFailedText, pluginConnectionTooSoonText, readPluginConnectionReturn } from "../model/plugin-connection-card.js";
31
31
  import { PLUGIN_CONNECTIONS_PLAN_REFUSAL, PLUGIN_CONNECTIONS_SITE_PERMISSION, PLUGIN_CONNECTIONS_SITE_ROLE_PRESET, pluginConnectionsPermissionRefusal } from "../model/plugin-connections.js";
32
32
  const leavePage = (url)=>window.location.assign(url);
33
33
  const HELP = pluginDocsHelp('installYourFirstPlugin', {
@@ -410,7 +410,8 @@ export default SitePluginConnectionsCard;
410
410
  }
411
411
  const chip = PLUGIN_CONNECTION_PHASE_CHIP[phase];
412
412
  const shares = pluginConnectionSharesText(entry.hosts);
413
- const nextSync = pluginConnectionNextSyncAtMs(view, intervalHours);
413
+ // A plan without connections is never run by the scheduled sync, so it has no next one to show.
414
+ const nextSync = planAllows ? pluginConnectionNextSyncAtMs(view, intervalHours) : null;
414
415
  const chosen = entry.resources.filter((one)=>view == null ? void 0 : view.choices[one.id]);
415
416
  return /*#__PURE__*/ _jsx(CardDisplay, {
416
417
  header: label,
@@ -467,7 +468,7 @@ export default SitePluginConnectionsCard;
467
468
  }) : null,
468
469
  phase === 'error' ? /*#__PURE__*/ _jsx(Alert, {
469
470
  severity: "error",
470
- children: (view == null ? void 0 : view.lastError) ? `The last sync did not work: ${view.lastError}. It tries again on schedule, or you can sync now.` : 'The last sync did not work. It tries again on schedule, or you can sync now.'
471
+ children: pluginConnectionSyncFailedText(view == null ? void 0 : view.lastError)
471
472
  }) : null,
472
473
  resource ? /*#__PURE__*/ _jsxs(Stack, {
473
474
  spacing: 1,
@@ -1 +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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"}
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 pluginConnectionSyncFailedText,\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 // A plan without connections is never run by the scheduled sync, so it has no next one to show.\n const nextSync = planAllows ? pluginConnectionNextSyncAtMs(view, intervalHours) : null\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\">{pluginConnectionSyncFailedText(view?.lastError)}</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","pluginConnectionSyncFailedText","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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"}
@@ -29,7 +29,7 @@ export interface EditorListingConnections {
29
29
  }
30
30
  /** The connections a pin's manifest declares that run a sync: only those write a record. */
31
31
  export declare function syncingConnectionsOf(manifest: unknown): PluginConnectionDeclaration[];
32
- /** A declared field name as a person reads it: `reviewCount` → `Review count`. */
32
+ /** A declared field name as a person reads it: `reviewCount` → `Review count`, `reviewUrl` → `Review URL`. */
33
33
  export declare function connectionFieldLabel(field: string): string;
34
34
  /** The listing whose namespace `componentId` is in, longest identity first. */
35
35
  export declare function listingForComponent(installs: readonly EditorRealmInstall[]): (componentId: string) => string | null;
@@ -18,8 +18,19 @@
18
18
  const verdict = validatePluginConnections(manifest == null ? void 0 : manifest.connections);
19
19
  return verdict.ok ? verdict.value.filter((connection)=>Boolean(connection.sync)) : [];
20
20
  }
21
- /** A declared field name as a person reads it: `reviewCount` → `Review count`. */ export function connectionFieldLabel(field) {
22
- const words = field.replace(/_/g, ' ').replace(/([a-z0-9])([A-Z])/g, '$1 $2').trim().toLowerCase();
21
+ /** Words a field name abbreviates that a person reads in capitals: `reviewUrl` → `Review URL`. */ const FIELD_ACRONYMS = {
22
+ url: 'URL',
23
+ uri: 'URI',
24
+ id: 'ID',
25
+ api: 'API',
26
+ sms: 'SMS',
27
+ html: 'HTML'
28
+ };
29
+ /** A declared field name as a person reads it: `reviewCount` → `Review count`, `reviewUrl` → `Review URL`. */ export function connectionFieldLabel(field) {
30
+ const words = field.replace(/_/g, ' ').replace(/([a-z0-9])([A-Z])/g, '$1 $2').trim().toLowerCase().split(/\s+/).filter(Boolean).map((word)=>{
31
+ var _FIELD_ACRONYMS_word;
32
+ return (_FIELD_ACRONYMS_word = FIELD_ACRONYMS[word]) != null ? _FIELD_ACRONYMS_word : word;
33
+ }).join(' ');
23
34
  return words ? words.charAt(0).toUpperCase() + words.slice(1) : field;
24
35
  }
25
36
  /** The listing whose namespace `componentId` is in, longest identity first. */ export function listingForComponent(installs) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/model/editor-connections.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 type {\n EditorConnectionVariable,\n EditorPluginConnections,\n} from '@aglyn/aglyn/app-utils/plugin-connection-editor'\nimport type { PluginConnectionSiteRecord } from '@aglyn/aglyn/app-utils/plugin-connection-records'\nimport {\n pluginConnectionRecordVariables,\n pluginConnectionVariableId,\n validatePluginConnections,\n type PluginConnectionDeclaration,\n} from '@aglyn/aglyn/app-utils/plugin-connections'\n\n/*\n * WHAT THE EDITOR IS HANDED OF A SITE'S DECLARED CONNECTIONS (AGL-4196).\n *\n * Pure: the provider reads the site's realm installs, their pins and the\n * records, and this turns them into core's `EditorPluginConnections`, the\n * same attribution and the same variables the published page gets.\n */\n\n/** A realm install as far as the editor needs it: the listing and its namespace. */\nexport interface EditorRealmInstall {\n listingId: string\n identity?: string\n}\n\n/** One installed listing's declared connections that sync, from its pin. */\nexport interface EditorListingConnections {\n listingId: string\n connections: PluginConnectionDeclaration[]\n}\n\n/** The connections a pin's manifest declares that run a sync: only those write a record. */\nexport function syncingConnectionsOf(manifest: unknown): PluginConnectionDeclaration[] {\n const verdict = validatePluginConnections((manifest as { connections?: unknown } | null | undefined)?.connections)\n return verdict.ok ? verdict.value.filter((connection) => Boolean(connection.sync)) : []\n}\n\n/** A declared field name as a person reads it: `reviewCount` → `Review count`. */\nexport function connectionFieldLabel(field: string): string {\n const words = field\n .replace(/_/g, ' ')\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .trim()\n .toLowerCase()\n return words ? words.charAt(0).toUpperCase() + words.slice(1) : field\n}\n\n/** The listing whose namespace `componentId` is in, longest identity first. */\nexport function listingForComponent(installs: readonly EditorRealmInstall[]): (componentId: string) => string | null {\n const namespaces = installs\n .filter((install) => typeof install.identity === 'string' && install.identity)\n .map((install) => ({ prefix: `${install.identity}.`, listingId: install.listingId }))\n .sort((a, b) => b.prefix.length - a.prefix.length)\n return (componentId) => namespaces.find((one) => componentId.startsWith(one.prefix))?.listingId ?? null\n}\n\n/**\n * Core's `EditorPluginConnections` for a site: which listing a component is\n * whose, each listing's records, and one variable per declared summary\n * field, named for a person and holding what the last sync wrote (or `null`\n * before one, so an author can bind it before the first sync).\n */\nexport function editorPluginConnections(input: {\n installs: readonly EditorRealmInstall[]\n listings: readonly EditorListingConnections[]\n records: Readonly<Record<string, Readonly<Record<string, PluginConnectionSiteRecord>>>>\n}): EditorPluginConnections {\n const variables: EditorConnectionVariable[] = []\n // `installed`, not `listing`: the listing-field coverage guard reads every `listing.<field>` here as a document read.\n for (const installed of input.listings) {\n for (const connection of installed.connections) {\n const record = input.records[installed.listingId]?.[connection.id]\n const values = pluginConnectionRecordVariables(installed.listingId, connection, record?.fields)\n for (const field of Object.keys(connection.sync?.record.fields ?? {})) {\n const id = pluginConnectionVariableId(installed.listingId, connection.id, field)\n if (!id) continue\n variables.push({ id, label: `${connection.label}: ${connectionFieldLabel(field)}`, value: values[id]?.value ?? null })\n }\n }\n }\n return { listingFor: listingForComponent(input.installs), records: input.records, variables }\n}\n"],"names":["pluginConnectionRecordVariables","pluginConnectionVariableId","validatePluginConnections","syncingConnectionsOf","manifest","verdict","connections","ok","value","filter","connection","Boolean","sync","connectionFieldLabel","field","words","replace","trim","toLowerCase","charAt","toUpperCase","slice","listingForComponent","installs","namespaces","install","identity","map","prefix","listingId","sort","a","b","length","componentId","find","one","startsWith","editorPluginConnections","input","variables","installed","listings","record","records","id","values","fields","Object","keys","push","label","listingFor"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAOD,SACEA,+BAA+B,EAC/BC,0BAA0B,EAC1BC,yBAAyB,QAEpB,4CAA2C;AAsBlD,0FAA0F,GAC1F,OAAO,SAASC,qBAAqBC,QAAiB;IACpD,MAAMC,UAAUH,0BAA2BE,4BAAD,AAACA,SAA2DE,WAAW;IACjH,OAAOD,QAAQE,EAAE,GAAGF,QAAQG,KAAK,CAACC,MAAM,CAAC,CAACC,aAAeC,QAAQD,WAAWE,IAAI,KAAK,EAAE;AACzF;AAEA,gFAAgF,GAChF,OAAO,SAASC,qBAAqBC,KAAa;IAChD,MAAMC,QAAQD,MACXE,OAAO,CAAC,MAAM,KACdA,OAAO,CAAC,sBAAsB,SAC9BC,IAAI,GACJC,WAAW;IACd,OAAOH,QAAQA,MAAMI,MAAM,CAAC,GAAGC,WAAW,KAAKL,MAAMM,KAAK,CAAC,KAAKP;AAClE;AAEA,6EAA6E,GAC7E,OAAO,SAASQ,oBAAoBC,QAAuC;IACzE,MAAMC,aAAaD,SAChBd,MAAM,CAAC,CAACgB,UAAY,OAAOA,QAAQC,QAAQ,KAAK,YAAYD,QAAQC,QAAQ,EAC5EC,GAAG,CAAC,CAACF,UAAa,CAAA;YAAEG,QAAQ,GAAGH,QAAQC,QAAQ,CAAC,CAAC,CAAC;YAAEG,WAAWJ,QAAQI,SAAS;QAAC,CAAA,GACjFC,IAAI,CAAC,CAACC,GAAGC,IAAMA,EAAEJ,MAAM,CAACK,MAAM,GAAGF,EAAEH,MAAM,CAACK,MAAM;IACnD,OAAO,CAACC;;YAAgBV;wBAAAA,mBAAAA,WAAWW,IAAI,CAAC,CAACC,MAAQF,YAAYG,UAAU,CAACD,IAAIR,MAAM,uBAA1DJ,iBAA8DK,SAAS,mBAAI;;AACrG;AAEA;;;;;CAKC,GACD,OAAO,SAASS,wBAAwBC,KAIvC;IACC,MAAMC,YAAwC,EAAE;IAChD,sHAAsH;IACtH,KAAK,MAAMC,aAAaF,MAAMG,QAAQ,CAAE;QACtC,KAAK,MAAMhC,cAAc+B,UAAUnC,WAAW,CAAE;;gBAC/BiC,oCAEiB7B;YAFhC,MAAMiC,UAASJ,qCAAAA,MAAMK,OAAO,CAACH,UAAUZ,SAAS,CAAC,qBAAlCU,kCAAoC,CAAC7B,WAAWmC,EAAE,CAAC;YAClE,MAAMC,SAAS9C,gCAAgCyC,UAAUZ,SAAS,EAAEnB,YAAYiC,0BAAAA,OAAQI,MAAM;YAC9F,KAAK,MAAMjC,SAASkC,OAAOC,IAAI,UAACvC,mBAAAA,WAAWE,IAAI,qBAAfF,iBAAiBiC,MAAM,CAACI,MAAM,mBAAI,CAAC,GAAI;;oBAGqBD;gBAF1F,MAAMD,KAAK5C,2BAA2BwC,UAAUZ,SAAS,EAAEnB,WAAWmC,EAAE,EAAE/B;gBAC1E,IAAI,CAAC+B,IAAI;gBACTL,UAAUU,IAAI,CAAC;oBAAEL;oBAAIM,OAAO,GAAGzC,WAAWyC,KAAK,CAAC,EAAE,EAAEtC,qBAAqBC,QAAQ;oBAAEN,KAAK,YAAEsC,aAAAA,MAAM,CAACD,GAAG,qBAAVC,WAAYtC,KAAK,oBAAI;gBAAK;YACtH;QACF;IACF;IACA,OAAO;QAAE4C,YAAY9B,oBAAoBiB,MAAMhB,QAAQ;QAAGqB,SAASL,MAAMK,OAAO;QAAEJ;IAAU;AAC9F"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/model/editor-connections.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 type {\n EditorConnectionVariable,\n EditorPluginConnections,\n} from '@aglyn/aglyn/app-utils/plugin-connection-editor'\nimport type { PluginConnectionSiteRecord } from '@aglyn/aglyn/app-utils/plugin-connection-records'\nimport {\n pluginConnectionRecordVariables,\n pluginConnectionVariableId,\n validatePluginConnections,\n type PluginConnectionDeclaration,\n} from '@aglyn/aglyn/app-utils/plugin-connections'\n\n/*\n * WHAT THE EDITOR IS HANDED OF A SITE'S DECLARED CONNECTIONS (AGL-4196).\n *\n * Pure: the provider reads the site's realm installs, their pins and the\n * records, and this turns them into core's `EditorPluginConnections`, the\n * same attribution and the same variables the published page gets.\n */\n\n/** A realm install as far as the editor needs it: the listing and its namespace. */\nexport interface EditorRealmInstall {\n listingId: string\n identity?: string\n}\n\n/** One installed listing's declared connections that sync, from its pin. */\nexport interface EditorListingConnections {\n listingId: string\n connections: PluginConnectionDeclaration[]\n}\n\n/** The connections a pin's manifest declares that run a sync: only those write a record. */\nexport function syncingConnectionsOf(manifest: unknown): PluginConnectionDeclaration[] {\n const verdict = validatePluginConnections((manifest as { connections?: unknown } | null | undefined)?.connections)\n return verdict.ok ? verdict.value.filter((connection) => Boolean(connection.sync)) : []\n}\n\n/** Words a field name abbreviates that a person reads in capitals: `reviewUrl` → `Review URL`. */\nconst FIELD_ACRONYMS: Readonly<Record<string, string>> = { url: 'URL', uri: 'URI', id: 'ID', api: 'API', sms: 'SMS', html: 'HTML' }\n\n/** A declared field name as a person reads it: `reviewCount` → `Review count`, `reviewUrl` → `Review URL`. */\nexport function connectionFieldLabel(field: string): string {\n const words = field\n .replace(/_/g, ' ')\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .trim()\n .toLowerCase()\n .split(/\\s+/)\n .filter(Boolean)\n .map((word) => FIELD_ACRONYMS[word] ?? word)\n .join(' ')\n return words ? words.charAt(0).toUpperCase() + words.slice(1) : field\n}\n\n/** The listing whose namespace `componentId` is in, longest identity first. */\nexport function listingForComponent(installs: readonly EditorRealmInstall[]): (componentId: string) => string | null {\n const namespaces = installs\n .filter((install) => typeof install.identity === 'string' && install.identity)\n .map((install) => ({ prefix: `${install.identity}.`, listingId: install.listingId }))\n .sort((a, b) => b.prefix.length - a.prefix.length)\n return (componentId) => namespaces.find((one) => componentId.startsWith(one.prefix))?.listingId ?? null\n}\n\n/**\n * Core's `EditorPluginConnections` for a site: which listing a component is\n * whose, each listing's records, and one variable per declared summary\n * field, named for a person and holding what the last sync wrote (or `null`\n * before one, so an author can bind it before the first sync).\n */\nexport function editorPluginConnections(input: {\n installs: readonly EditorRealmInstall[]\n listings: readonly EditorListingConnections[]\n records: Readonly<Record<string, Readonly<Record<string, PluginConnectionSiteRecord>>>>\n}): EditorPluginConnections {\n const variables: EditorConnectionVariable[] = []\n // `installed`, not `listing`: the listing-field coverage guard reads every `listing.<field>` here as a document read.\n for (const installed of input.listings) {\n for (const connection of installed.connections) {\n const record = input.records[installed.listingId]?.[connection.id]\n const values = pluginConnectionRecordVariables(installed.listingId, connection, record?.fields)\n for (const field of Object.keys(connection.sync?.record.fields ?? {})) {\n const id = pluginConnectionVariableId(installed.listingId, connection.id, field)\n if (!id) continue\n variables.push({ id, label: `${connection.label}: ${connectionFieldLabel(field)}`, value: values[id]?.value ?? null })\n }\n }\n }\n return { listingFor: listingForComponent(input.installs), records: input.records, variables }\n}\n"],"names":["pluginConnectionRecordVariables","pluginConnectionVariableId","validatePluginConnections","syncingConnectionsOf","manifest","verdict","connections","ok","value","filter","connection","Boolean","sync","FIELD_ACRONYMS","url","uri","id","api","sms","html","connectionFieldLabel","field","words","replace","trim","toLowerCase","split","map","word","join","charAt","toUpperCase","slice","listingForComponent","installs","namespaces","install","identity","prefix","listingId","sort","a","b","length","componentId","find","one","startsWith","editorPluginConnections","input","variables","installed","listings","record","records","values","fields","Object","keys","push","label","listingFor"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAOD,SACEA,+BAA+B,EAC/BC,0BAA0B,EAC1BC,yBAAyB,QAEpB,4CAA2C;AAsBlD,0FAA0F,GAC1F,OAAO,SAASC,qBAAqBC,QAAiB;IACpD,MAAMC,UAAUH,0BAA2BE,4BAAD,AAACA,SAA2DE,WAAW;IACjH,OAAOD,QAAQE,EAAE,GAAGF,QAAQG,KAAK,CAACC,MAAM,CAAC,CAACC,aAAeC,QAAQD,WAAWE,IAAI,KAAK,EAAE;AACzF;AAEA,gGAAgG,GAChG,MAAMC,iBAAmD;IAAEC,KAAK;IAAOC,KAAK;IAAOC,IAAI;IAAMC,KAAK;IAAOC,KAAK;IAAOC,MAAM;AAAO;AAElI,4GAA4G,GAC5G,OAAO,SAASC,qBAAqBC,KAAa;IAChD,MAAMC,QAAQD,MACXE,OAAO,CAAC,MAAM,KACdA,OAAO,CAAC,sBAAsB,SAC9BC,IAAI,GACJC,WAAW,GACXC,KAAK,CAAC,OACNjB,MAAM,CAACE,SACPgB,GAAG,CAAC,CAACC;YAASf;gBAAAA,uBAAAA,cAAc,CAACe,KAAK,YAApBf,uBAAwBe;OACtCC,IAAI,CAAC;IACR,OAAOP,QAAQA,MAAMQ,MAAM,CAAC,GAAGC,WAAW,KAAKT,MAAMU,KAAK,CAAC,KAAKX;AAClE;AAEA,6EAA6E,GAC7E,OAAO,SAASY,oBAAoBC,QAAuC;IACzE,MAAMC,aAAaD,SAChBzB,MAAM,CAAC,CAAC2B,UAAY,OAAOA,QAAQC,QAAQ,KAAK,YAAYD,QAAQC,QAAQ,EAC5EV,GAAG,CAAC,CAACS,UAAa,CAAA;YAAEE,QAAQ,GAAGF,QAAQC,QAAQ,CAAC,CAAC,CAAC;YAAEE,WAAWH,QAAQG,SAAS;QAAC,CAAA,GACjFC,IAAI,CAAC,CAACC,GAAGC,IAAMA,EAAEJ,MAAM,CAACK,MAAM,GAAGF,EAAEH,MAAM,CAACK,MAAM;IACnD,OAAO,CAACC;;YAAgBT;wBAAAA,mBAAAA,WAAWU,IAAI,CAAC,CAACC,MAAQF,YAAYG,UAAU,CAACD,IAAIR,MAAM,uBAA1DH,iBAA8DI,SAAS,mBAAI;;AACrG;AAEA;;;;;CAKC,GACD,OAAO,SAASS,wBAAwBC,KAIvC;IACC,MAAMC,YAAwC,EAAE;IAChD,sHAAsH;IACtH,KAAK,MAAMC,aAAaF,MAAMG,QAAQ,CAAE;QACtC,KAAK,MAAM1C,cAAcyC,UAAU7C,WAAW,CAAE;;gBAC/B2C,oCAEiBvC;YAFhC,MAAM2C,UAASJ,qCAAAA,MAAMK,OAAO,CAACH,UAAUZ,SAAS,CAAC,qBAAlCU,kCAAoC,CAACvC,WAAWM,EAAE,CAAC;YAClE,MAAMuC,SAASvD,gCAAgCmD,UAAUZ,SAAS,EAAE7B,YAAY2C,0BAAAA,OAAQG,MAAM;YAC9F,KAAK,MAAMnC,SAASoC,OAAOC,IAAI,UAAChD,mBAAAA,WAAWE,IAAI,qBAAfF,iBAAiB2C,MAAM,CAACG,MAAM,mBAAI,CAAC,GAAI;;oBAGqBD;gBAF1F,MAAMvC,KAAKf,2BAA2BkD,UAAUZ,SAAS,EAAE7B,WAAWM,EAAE,EAAEK;gBAC1E,IAAI,CAACL,IAAI;gBACTkC,UAAUS,IAAI,CAAC;oBAAE3C;oBAAI4C,OAAO,GAAGlD,WAAWkD,KAAK,CAAC,EAAE,EAAExC,qBAAqBC,QAAQ;oBAAEb,KAAK,YAAE+C,aAAAA,MAAM,CAACvC,GAAG,qBAAVuC,WAAY/C,KAAK,oBAAI;gBAAK;YACtH;QACF;IACF;IACA,OAAO;QAAEqD,YAAY5B,oBAAoBgB,MAAMf,QAAQ;QAAGoB,SAASL,MAAMK,OAAO;QAAEJ;IAAU;AAC9F"}
@@ -555,6 +555,17 @@ export declare function listingInclusions(listing: {
555
555
  reviewStatus?: string;
556
556
  }, options?: {
557
557
  reviewedVersion?: boolean;
558
+ /**
559
+ * The version on offer is staff-signed to run with the site's own code
560
+ * (`trust: 'realm'`), so "sandboxed on its own origin" would be untrue.
561
+ */
562
+ realmTrusted?: boolean;
563
+ /**
564
+ * The labels of the services the version on offer declares connections
565
+ * to (AGL-4197). Connecting one needs a plan that includes plugin
566
+ * connections, which a shopper has to know before installing.
567
+ */
568
+ connections?: readonly string[];
558
569
  }): ListingInclusion[];
559
570
  /**
560
571
  * WHO STILL OWNS WHAT THEY BOUGHT — the one predicate (AGL-2158).
@@ -23,6 +23,8 @@
23
23
  // the browser). Neither is safe from here; the deep path has no dependencies.
24
24
  import { isFirstPartyMediaSrc, MEDIA_REF_PREFIX } from "@aglyn/aglyn/app-utils/media-ref";
25
25
  import { marketplaceMinPriceUsd } from "./listing-price-floor.js";
26
+ import { pluginConnectionsPlanNote } from "./plugin-connections.js";
27
+ import { PLATFORM_BRAND_NAME } from "@aglyn/aglyn/app-utils/platform-brand";
26
28
  import { landsInPublisherWorkspace } from "./own-listing.js";
27
29
  import { offeredPluginVersion } from "@aglyn/aglyn/app-utils/plugin-manifest";
28
30
  // Visibility lives in core (AGL-876), for the same reason the artifact-type
@@ -501,6 +503,7 @@ const SUPPORT_EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
501
503
  theme: 'A theme applied to the site you choose',
502
504
  siteStarter: 'A new site, created from these pages and this theme'
503
505
  };
506
+ /** A plugin version staff signed to run with the site's own code (`trust: 'realm'`). */ const REALM_PLUGIN_INSTALL_RESULT = `A plugin, reviewed and signed by ${PLATFORM_BRAND_NAME} to run with your site`;
504
507
  /**
505
508
  * The `WHAT'S INCLUDED` checklist the marketplace listing mockup shows
506
509
  * (AGL-2173), derived entirely from facts the listing already carries.
@@ -516,14 +519,21 @@ const SUPPORT_EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
516
519
  * Ordered decision-first: what you get, then where it goes, then the two
517
520
  * facts that most often stop an install.
518
521
  */ export function listingInclusions(listing, options = {}) {
519
- var _ARTIFACT_INSTALL_RESULT_type, _listing_priceUsd;
522
+ var _ARTIFACT_INSTALL_RESULT_type, _options_connections, _listing_priceUsd;
520
523
  const type = listingArtifactType(listing);
521
524
  const rows = [
522
525
  {
523
- label: (_ARTIFACT_INSTALL_RESULT_type = ARTIFACT_INSTALL_RESULT[type]) != null ? _ARTIFACT_INSTALL_RESULT_type : 'An installable artifact',
526
+ label: type === 'plugin' && options.realmTrusted ? REALM_PLUGIN_INSTALL_RESULT : (_ARTIFACT_INSTALL_RESULT_type = ARTIFACT_INSTALL_RESULT[type]) != null ? _ARTIFACT_INSTALL_RESULT_type : 'An installable artifact',
524
527
  tone: 'included'
525
528
  }
526
529
  ];
530
+ const planNote = type === 'plugin' ? pluginConnectionsPlanNote((_options_connections = options.connections) != null ? _options_connections : []) : null;
531
+ // Right after what the install produces: it is the fact most likely to stop
532
+ // an install, and a note, not a tick, because it is a limit.
533
+ if (planNote) rows.push({
534
+ label: planNote,
535
+ tone: 'note'
536
+ });
527
537
  const targets = installTargetsFor(listing);
528
538
  rows.push({
529
539
  label: targets.includes('org') ? 'Installs org-wide, covering sites you add later' : 'Installs per site — new sites are not covered automatically',