@aglyn/plugins-marketplace 1.15.0 → 1.15.2
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.
- package/README.md +1 -0
- package/package.json +15 -14
- package/src/lib/components/editor-connections-provider.component.d.ts +37 -0
- package/src/lib/components/editor-connections-provider.component.js +161 -0
- package/src/lib/components/editor-connections-provider.component.js.map +1 -0
- package/src/lib/components/listing-connection-credentials-card.component.d.ts +33 -0
- package/src/lib/components/listing-connection-credentials-card.component.js +318 -0
- package/src/lib/components/listing-connection-credentials-card.component.js.map +1 -0
- package/src/lib/components/listing-detail-page.component.js +10 -2
- package/src/lib/components/listing-detail-page.component.js.map +1 -1
- package/src/lib/components/site-plugin-connections-card.component.d.ts +38 -0
- package/src/lib/components/site-plugin-connections-card.component.js +576 -0
- package/src/lib/components/site-plugin-connections-card.component.js.map +1 -0
- package/src/lib/declarations.console-server.d.ts +17 -0
- package/src/lib/declarations.console-server.js +41 -0
- package/src/lib/declarations.console-server.js.map +1 -0
- package/src/lib/declarations.js +11 -2
- package/src/lib/declarations.js.map +1 -1
- package/src/lib/declarations.server.js +68 -1
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/hooks/use-plugin-connections-api.d.ts +89 -0
- package/src/lib/hooks/use-plugin-connections-api.js +132 -0
- package/src/lib/hooks/use-plugin-connections-api.js.map +1 -0
- package/src/lib/model/editor-connections.d.ts +46 -0
- package/src/lib/model/editor-connections.js +70 -0
- package/src/lib/model/editor-connections.js.map +1 -0
- package/src/lib/model/listing-price-floor.js +7 -4
- package/src/lib/model/listing-price-floor.js.map +1 -1
- package/src/lib/model/marketplace-publisher-permissions.d.ts +3 -1
- package/src/lib/model/marketplace-publisher-permissions.js +14 -0
- package/src/lib/model/marketplace-publisher-permissions.js.map +1 -1
- package/src/lib/model/marketplace-site-permissions.d.ts +9 -0
- package/src/lib/model/marketplace-site-permissions.js +29 -0
- package/src/lib/model/marketplace-site-permissions.js.map +1 -1
- package/src/lib/model/plugin-connection-card.d.ts +88 -0
- package/src/lib/model/plugin-connection-card.js +187 -0
- package/src/lib/model/plugin-connection-card.js.map +1 -0
- package/src/lib/model/plugin-connections.d.ts +172 -0
- package/src/lib/model/plugin-connections.js +119 -0
- package/src/lib/model/plugin-connections.js.map +1 -0
- package/src/lib/plugin.js +21 -1
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/admin-reviews.js +18 -12
- package/src/lib/server/admin-reviews.js.map +1 -1
- package/src/lib/server/artifact-inventory.js +3 -1
- package/src/lib/server/artifact-inventory.js.map +1 -1
- package/src/lib/server/checkout.js +4 -0
- package/src/lib/server/checkout.js.map +1 -1
- package/src/lib/server/connections/cleanup.d.ts +84 -0
- package/src/lib/server/connections/cleanup.js +116 -0
- package/src/lib/server/connections/cleanup.js.map +1 -0
- package/src/lib/server/connections/config.d.ts +42 -0
- package/src/lib/server/connections/config.js +48 -0
- package/src/lib/server/connections/config.js.map +1 -0
- package/src/lib/server/connections/connection-store.d.ts +173 -0
- package/src/lib/server/connections/connection-store.js +331 -0
- package/src/lib/server/connections/connection-store.js.map +1 -0
- package/src/lib/server/connections/credentials-store.d.ts +71 -0
- package/src/lib/server/connections/credentials-store.js +78 -0
- package/src/lib/server/connections/credentials-store.js.map +1 -0
- package/src/lib/server/connections/erasure.d.ts +25 -0
- package/src/lib/server/connections/erasure.js +72 -0
- package/src/lib/server/connections/erasure.js.map +1 -0
- package/src/lib/server/connections/installed-connection.d.ts +64 -0
- package/src/lib/server/connections/installed-connection.js +92 -0
- package/src/lib/server/connections/installed-connection.js.map +1 -0
- package/src/lib/server/connections/oauth-state.d.ts +129 -0
- package/src/lib/server/connections/oauth-state.js +252 -0
- package/src/lib/server/connections/oauth-state.js.map +1 -0
- package/src/lib/server/connections/provider-http.d.ts +99 -0
- package/src/lib/server/connections/provider-http.js +253 -0
- package/src/lib/server/connections/provider-http.js.map +1 -0
- package/src/lib/server/connections/record-announce.d.ts +20 -0
- package/src/lib/server/connections/record-announce.js +53 -0
- package/src/lib/server/connections/record-announce.js.map +1 -0
- package/src/lib/server/connections/record-mapping.d.ts +63 -0
- package/src/lib/server/connections/record-mapping.js +151 -0
- package/src/lib/server/connections/record-mapping.js.map +1 -0
- package/src/lib/server/connections/record-routes.d.ts +17 -0
- package/src/lib/server/connections/record-routes.js +96 -0
- package/src/lib/server/connections/record-routes.js.map +1 -0
- package/src/lib/server/connections/revoke.d.ts +29 -0
- package/src/lib/server/connections/revoke.js +52 -0
- package/src/lib/server/connections/revoke.js.map +1 -0
- package/src/lib/server/connections/routes.d.ts +77 -0
- package/src/lib/server/connections/routes.js +706 -0
- package/src/lib/server/connections/routes.js.map +1 -0
- package/src/lib/server/connections/site-records-platform.d.ts +24 -0
- package/src/lib/server/connections/site-records-platform.js +94 -0
- package/src/lib/server/connections/site-records-platform.js.map +1 -0
- package/src/lib/server/connections/site-records.d.ts +95 -0
- package/src/lib/server/connections/site-records.js +221 -0
- package/src/lib/server/connections/site-records.js.map +1 -0
- package/src/lib/server/connections/sync.d.ts +110 -0
- package/src/lib/server/connections/sync.js +414 -0
- package/src/lib/server/connections/sync.js.map +1 -0
- package/src/lib/server/install-plugin.js +56 -6
- package/src/lib/server/install-plugin.js.map +1 -1
- package/src/lib/server/publish-plugin.js +5 -2
- package/src/lib/server/publish-plugin.js.map +1 -1
- package/src/lib/server/revenue-source.js +6 -5
- package/src/lib/server/revenue-source.js.map +1 -1
- package/src/lib/server.js +60 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/memory-firestore.d.ts +34 -0
- package/src/lib/testing/memory-firestore.js +250 -0
- package/src/lib/testing/memory-firestore.js.map +1 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ import { platformConsoleOrigin } from "@aglyn/aglyn/app-utils/platform-brand";
|
|
18
|
+
import { PLUGIN_CONNECTIONS_API_ROUTES } from "./plugin-connections.js";
|
|
19
|
+
/*
|
|
20
|
+
* WHAT THE CONNECTION CARDS SAY (AGL-4196), kept apart from React so the
|
|
21
|
+
* console card, the publisher's credentials card and their specs read one
|
|
22
|
+
* answer. Pure: no Firestore, no fetch, no window.
|
|
23
|
+
*/ /** The address a publisher registers at the provider as the OAuth app's redirect URI. */ export function pluginConnectionRedirectUri(origin = platformConsoleOrigin()) {
|
|
24
|
+
return `${origin.replace(/\/+$/, '')}/api/${PLUGIN_CONNECTIONS_API_ROUTES.connectCallback}`;
|
|
25
|
+
}
|
|
26
|
+
export function pluginConnectionPhase(entry, configured) {
|
|
27
|
+
if (!configured) return 'not-configured';
|
|
28
|
+
const connection = entry.connection;
|
|
29
|
+
if (!connection) return entry.credentialsSet ? 'not-connected' : 'not-set-up';
|
|
30
|
+
if (connection.status === 'reconnect_required') return 'reconnect';
|
|
31
|
+
if (connection.status === 'error') return 'error';
|
|
32
|
+
return connection.ready ? 'connected' : 'choosing';
|
|
33
|
+
}
|
|
34
|
+
/** The first declared resource the site has not chosen yet, in order. */ export function nextResourceToChoose(entry) {
|
|
35
|
+
var _ref, _entry_resources_find;
|
|
36
|
+
var _entry_connection;
|
|
37
|
+
const choices = (_ref = (_entry_connection = entry.connection) == null ? void 0 : _entry_connection.choices) != null ? _ref : {};
|
|
38
|
+
return (_entry_resources_find = entry.resources.find((resource)=>!choices[resource.id])) != null ? _entry_resources_find : null;
|
|
39
|
+
}
|
|
40
|
+
/** The status chip each phase wears. */ export const PLUGIN_CONNECTION_PHASE_CHIP = {
|
|
41
|
+
'not-configured': {
|
|
42
|
+
label: 'Not available yet',
|
|
43
|
+
tone: 'neutral'
|
|
44
|
+
},
|
|
45
|
+
'not-set-up': {
|
|
46
|
+
label: 'Waiting on the publisher',
|
|
47
|
+
tone: 'neutral'
|
|
48
|
+
},
|
|
49
|
+
'not-connected': {
|
|
50
|
+
label: 'Not connected',
|
|
51
|
+
tone: 'neutral'
|
|
52
|
+
},
|
|
53
|
+
choosing: {
|
|
54
|
+
label: 'Finish setting up',
|
|
55
|
+
tone: 'info'
|
|
56
|
+
},
|
|
57
|
+
connected: {
|
|
58
|
+
label: 'Connected',
|
|
59
|
+
tone: 'success'
|
|
60
|
+
},
|
|
61
|
+
reconnect: {
|
|
62
|
+
label: 'Reconnect needed',
|
|
63
|
+
tone: 'warning'
|
|
64
|
+
},
|
|
65
|
+
error: {
|
|
66
|
+
label: 'Sync failed',
|
|
67
|
+
tone: 'error'
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
/** What a site owner is told when a deployment cannot run connections. */ export const PLUGIN_CONNECTIONS_NOT_CONFIGURED_TEXT = 'Connecting plugins to other services is not available here yet.';
|
|
71
|
+
/** What a site owner is told while the publisher has not set the connection up. */ export function pluginConnectionNotSetUpText(label) {
|
|
72
|
+
return `The plugin's publisher has not finished setting up ${label} yet. You can connect once they have.`;
|
|
73
|
+
}
|
|
74
|
+
/** The query the connect callback adds to the page it returns to. */ export const PLUGIN_CONNECTION_RETURN_PARAMS = {
|
|
75
|
+
listing: 'pluginConnectionListing',
|
|
76
|
+
connection: 'pluginConnection',
|
|
77
|
+
result: 'pluginConnectionResult'
|
|
78
|
+
};
|
|
79
|
+
const RESULT_MESSAGES = {
|
|
80
|
+
connected: {
|
|
81
|
+
variant: 'success',
|
|
82
|
+
message: (label)=>`Connected ${label}.`
|
|
83
|
+
},
|
|
84
|
+
denied: {
|
|
85
|
+
variant: 'warning',
|
|
86
|
+
message: (label)=>`Access to ${label} was not granted, so nothing was connected.`
|
|
87
|
+
},
|
|
88
|
+
expired: {
|
|
89
|
+
variant: 'error',
|
|
90
|
+
message: ()=>'The connection took too long or was already used. Connect again.'
|
|
91
|
+
},
|
|
92
|
+
failed: {
|
|
93
|
+
variant: 'error',
|
|
94
|
+
message: (label)=>`${label} could not be connected. Try again.`
|
|
95
|
+
},
|
|
96
|
+
'not-permitted': {
|
|
97
|
+
variant: 'error',
|
|
98
|
+
message: ()=>'You can no longer connect this here: the site, its plan or your permissions changed.'
|
|
99
|
+
},
|
|
100
|
+
'not-set-up': {
|
|
101
|
+
variant: 'error',
|
|
102
|
+
message: (label)=>pluginConnectionNotSetUpText(label)
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* The outcome the connect callback left on `href` for `listingId`, and the
|
|
107
|
+
* address to replace it with, or `null` when it carries none for it.
|
|
108
|
+
*/ export function readPluginConnectionReturn(href, listingId) {
|
|
109
|
+
var _params_get;
|
|
110
|
+
let url;
|
|
111
|
+
try {
|
|
112
|
+
url = new URL(href);
|
|
113
|
+
} catch (unused) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
const params = url.searchParams;
|
|
117
|
+
const result = params.get(PLUGIN_CONNECTION_RETURN_PARAMS.result);
|
|
118
|
+
if (params.get(PLUGIN_CONNECTION_RETURN_PARAMS.listing) !== listingId) return null;
|
|
119
|
+
if (!result || !(result in RESULT_MESSAGES)) return null;
|
|
120
|
+
const connectionId = (_params_get = params.get(PLUGIN_CONNECTION_RETURN_PARAMS.connection)) != null ? _params_get : '';
|
|
121
|
+
for (const key of Object.values(PLUGIN_CONNECTION_RETURN_PARAMS))params.delete(key);
|
|
122
|
+
return {
|
|
123
|
+
listingId,
|
|
124
|
+
connectionId,
|
|
125
|
+
result: result,
|
|
126
|
+
cleanUrl: `${url.pathname}${url.search}${url.hash}`
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/** The snackbar a returned connect shows. */ export function pluginConnectionResultMessage(result, label) {
|
|
130
|
+
const entry = RESULT_MESSAGES[result];
|
|
131
|
+
return {
|
|
132
|
+
variant: entry.variant,
|
|
133
|
+
message: entry.message(label)
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* When the next scheduled sync runs: what the sync recorded when it says,
|
|
138
|
+
* else the last one plus the pinned manifest's interval. A connection with
|
|
139
|
+
* every choice made and no sync yet runs on the next pass.
|
|
140
|
+
*/ export function pluginConnectionNextSyncAtMs(view, intervalHours) {
|
|
141
|
+
// A failed sync backs off on the server, so the interval no longer says when it runs again.
|
|
142
|
+
if (!view || view.status !== 'connected' || !view.ready) return null;
|
|
143
|
+
if (typeof view.nextSyncAtMs === 'number') return view.nextSyncAtMs;
|
|
144
|
+
if (!intervalHours || intervalHours <= 0) return null;
|
|
145
|
+
if (view.lastSyncAtMs === null) return 'soon';
|
|
146
|
+
return view.lastSyncAtMs + intervalHours * 3600000;
|
|
147
|
+
}
|
|
148
|
+
/** The sync interval a pinned manifest declares for one connection, if any. */ export function declaredSyncIntervalHours(manifest, connectionId) {
|
|
149
|
+
var _found_sync;
|
|
150
|
+
const connections = manifest == null ? void 0 : manifest.connections;
|
|
151
|
+
if (!Array.isArray(connections)) return null;
|
|
152
|
+
const found = connections.find((one)=>Boolean(one) && typeof one === 'object' && one.id === connectionId);
|
|
153
|
+
const hours = found == null ? void 0 : (_found_sync = found.sync) == null ? void 0 : _found_sync.intervalHours;
|
|
154
|
+
return typeof hours === 'number' && Number.isFinite(hours) ? hours : null;
|
|
155
|
+
}
|
|
156
|
+
/** The connections a pinned manifest declares, by id and label: what the card can name before the status answers. */ export function declaredPluginConnections(manifest) {
|
|
157
|
+
const connections = manifest == null ? void 0 : manifest.connections;
|
|
158
|
+
if (!Array.isArray(connections)) return [];
|
|
159
|
+
return connections.flatMap((one)=>{
|
|
160
|
+
if (!one || typeof one !== 'object') return [];
|
|
161
|
+
const { id, label, description } = one;
|
|
162
|
+
if (typeof id !== 'string' || !id || typeof label !== 'string' || !label) return [];
|
|
163
|
+
return [
|
|
164
|
+
_extends({
|
|
165
|
+
id,
|
|
166
|
+
label
|
|
167
|
+
}, typeof description === 'string' && description ? {
|
|
168
|
+
description
|
|
169
|
+
} : {})
|
|
170
|
+
];
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
/** What a site owner is told when "Sync now" comes within a minute of the last sync. */ export function pluginConnectionTooSoonText(label, retryAfterMs) {
|
|
174
|
+
const seconds = retryAfterMs && retryAfterMs > 0 ? Math.ceil(retryAfterMs / 1000) : null;
|
|
175
|
+
return seconds ? `${label} synced moments ago. You can sync it again in ${seconds} seconds.` : `${label} synced moments ago. You can sync it again in a minute.`;
|
|
176
|
+
}
|
|
177
|
+
/** Where a connection's data goes and comes from, as the card says it. */ export function pluginConnectionSharesText(hosts) {
|
|
178
|
+
return hosts && hosts.length ? `Shares data with: ${hosts.join(', ')}` : null;
|
|
179
|
+
}
|
|
180
|
+
/** A date and time as the cards print it. */ export function formatConnectionTime(ms) {
|
|
181
|
+
return new Date(ms).toLocaleString(undefined, {
|
|
182
|
+
dateStyle: 'medium',
|
|
183
|
+
timeStyle: 'short'
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
//# sourceMappingURL=plugin-connection-card.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/model/plugin-connection-card.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 { platformConsoleOrigin } from '@aglyn/aglyn/app-utils/platform-brand'\nimport {\n PLUGIN_CONNECTIONS_API_ROUTES,\n type PluginConnectionStatusEntry,\n type PluginConnectionView,\n} from './plugin-connections'\n\n/*\n * WHAT THE CONNECTION CARDS SAY (AGL-4196), kept apart from React so the\n * console card, the publisher's credentials card and their specs read one\n * answer. Pure: no Firestore, no fetch, no window.\n */\n\n/** The address a publisher registers at the provider as the OAuth app's redirect URI. */\nexport function pluginConnectionRedirectUri(origin: string = platformConsoleOrigin()): string {\n return `${origin.replace(/\\/+$/, '')}/api/${PLUGIN_CONNECTIONS_API_ROUTES.connectCallback}`\n}\n\n/** A view as the card reads it: the sync may also say when it runs next. */\nexport type PluginConnectionCardView = PluginConnectionView & { nextSyncAtMs?: number | null }\n\n/**\n * Where one declared connection stands for a site, in the order the card\n * checks: a deployment without the key first, then the site's own grant,\n * then what the publisher has set up.\n */\nexport type PluginConnectionPhase =\n | 'not-configured'\n | 'not-set-up'\n | 'not-connected'\n | 'choosing'\n | 'connected'\n | 'reconnect'\n | 'error'\n\nexport function pluginConnectionPhase(entry: PluginConnectionStatusEntry, configured: boolean): PluginConnectionPhase {\n if (!configured) return 'not-configured'\n const connection = entry.connection\n if (!connection) return entry.credentialsSet ? 'not-connected' : 'not-set-up'\n if (connection.status === 'reconnect_required') return 'reconnect'\n if (connection.status === 'error') return 'error'\n return connection.ready ? 'connected' : 'choosing'\n}\n\n/** The first declared resource the site has not chosen yet, in order. */\nexport function nextResourceToChoose(\n entry: Pick<PluginConnectionStatusEntry, 'resources' | 'connection'>,\n): { id: string; label: string } | null {\n const choices = entry.connection?.choices ?? {}\n return entry.resources.find((resource) => !choices[resource.id]) ?? null\n}\n\n/** The status chip each phase wears. */\nexport const PLUGIN_CONNECTION_PHASE_CHIP: Readonly<\n Record<PluginConnectionPhase, { label: string; tone: 'neutral' | 'info' | 'success' | 'warning' | 'error' }>\n> = {\n 'not-configured': { label: 'Not available yet', tone: 'neutral' },\n 'not-set-up': { label: 'Waiting on the publisher', tone: 'neutral' },\n 'not-connected': { label: 'Not connected', tone: 'neutral' },\n choosing: { label: 'Finish setting up', tone: 'info' },\n connected: { label: 'Connected', tone: 'success' },\n reconnect: { label: 'Reconnect needed', tone: 'warning' },\n error: { label: 'Sync failed', tone: 'error' },\n}\n\n/** What a site owner is told when a deployment cannot run connections. */\nexport const PLUGIN_CONNECTIONS_NOT_CONFIGURED_TEXT =\n 'Connecting plugins to other services is not available here yet.'\n\n/** What a site owner is told while the publisher has not set the connection up. */\nexport function pluginConnectionNotSetUpText(label: string): string {\n return `The plugin's publisher has not finished setting up ${label} yet. You can connect once they have.`\n}\n\n/** The query the connect callback adds to the page it returns to. */\nexport const PLUGIN_CONNECTION_RETURN_PARAMS = {\n listing: 'pluginConnectionListing',\n connection: 'pluginConnection',\n result: 'pluginConnectionResult',\n} as const\n\nexport type PluginConnectionResult = 'connected' | 'denied' | 'expired' | 'failed' | 'not-permitted' | 'not-set-up'\n\nconst RESULT_MESSAGES: Readonly<\n Record<PluginConnectionResult, { variant: 'success' | 'warning' | 'error'; message: (label: string) => string }>\n> = {\n connected: { variant: 'success', message: (label) => `Connected ${label}.` },\n denied: { variant: 'warning', message: (label) => `Access to ${label} was not granted, so nothing was connected.` },\n expired: { variant: 'error', message: () => 'The connection took too long or was already used. Connect again.' },\n failed: { variant: 'error', message: (label) => `${label} could not be connected. Try again.` },\n 'not-permitted': {\n variant: 'error',\n message: () => 'You can no longer connect this here: the site, its plan or your permissions changed.',\n },\n 'not-set-up': { variant: 'error', message: (label) => pluginConnectionNotSetUpText(label) },\n}\n\nexport interface PluginConnectionReturn {\n listingId: string\n connectionId: string\n result: PluginConnectionResult\n /** The page's address without the three parameters. */\n cleanUrl: string\n}\n\n/**\n * The outcome the connect callback left on `href` for `listingId`, and the\n * address to replace it with, or `null` when it carries none for it.\n */\nexport function readPluginConnectionReturn(href: string, listingId: string): PluginConnectionReturn | null {\n let url: URL\n try {\n url = new URL(href)\n } catch {\n return null\n }\n const params = url.searchParams\n const result = params.get(PLUGIN_CONNECTION_RETURN_PARAMS.result)\n if (params.get(PLUGIN_CONNECTION_RETURN_PARAMS.listing) !== listingId) return null\n if (!result || !(result in RESULT_MESSAGES)) return null\n const connectionId = params.get(PLUGIN_CONNECTION_RETURN_PARAMS.connection) ?? ''\n for (const key of Object.values(PLUGIN_CONNECTION_RETURN_PARAMS)) params.delete(key)\n return {\n listingId,\n connectionId,\n result: result as PluginConnectionResult,\n cleanUrl: `${url.pathname}${url.search}${url.hash}`,\n }\n}\n\n/** The snackbar a returned connect shows. */\nexport function pluginConnectionResultMessage(\n result: PluginConnectionResult,\n label: string,\n): { variant: 'success' | 'warning' | 'error'; message: string } {\n const entry = RESULT_MESSAGES[result]\n return { variant: entry.variant, message: entry.message(label) }\n}\n\n/**\n * When the next scheduled sync runs: what the sync recorded when it says,\n * else the last one plus the pinned manifest's interval. A connection with\n * every choice made and no sync yet runs on the next pass.\n */\nexport function pluginConnectionNextSyncAtMs(\n view: PluginConnectionCardView | null,\n intervalHours: number | null | undefined,\n): number | 'soon' | null {\n // A failed sync backs off on the server, so the interval no longer says when it runs again.\n if (!view || view.status !== 'connected' || !view.ready) return null\n if (typeof view.nextSyncAtMs === 'number') return view.nextSyncAtMs\n if (!intervalHours || intervalHours <= 0) return null\n if (view.lastSyncAtMs === null) return 'soon'\n return view.lastSyncAtMs + intervalHours * 3_600_000\n}\n\n/** The sync interval a pinned manifest declares for one connection, if any. */\nexport function declaredSyncIntervalHours(manifest: unknown, connectionId: string): number | null {\n const connections = (manifest as { connections?: unknown } | null | undefined)?.connections\n if (!Array.isArray(connections)) return null\n const found = connections.find(\n (one): one is { id: string; sync?: { intervalHours?: unknown } } =>\n Boolean(one) && typeof one === 'object' && (one as { id?: unknown }).id === connectionId,\n )\n const hours = found?.sync?.intervalHours\n return typeof hours === 'number' && Number.isFinite(hours) ? hours : null\n}\n\n/** The connections a pinned manifest declares, by id and label: what the card can name before the status answers. */\nexport function declaredPluginConnections(manifest: unknown): Array<{ id: string; label: string; description?: string }> {\n const connections = (manifest as { connections?: unknown } | null | undefined)?.connections\n if (!Array.isArray(connections)) return []\n return connections.flatMap((one) => {\n if (!one || typeof one !== 'object') return []\n const { id, label, description } = one as { id?: unknown; label?: unknown; description?: unknown }\n if (typeof id !== 'string' || !id || typeof label !== 'string' || !label) return []\n return [{ id, label, ...(typeof description === 'string' && description ? { description } : {}) }]\n })\n}\n\n/** What a site owner is told when \"Sync now\" comes within a minute of the last sync. */\nexport function pluginConnectionTooSoonText(label: string, retryAfterMs: number | null): string {\n const seconds = retryAfterMs && retryAfterMs > 0 ? Math.ceil(retryAfterMs / 1000) : null\n return seconds\n ? `${label} synced moments ago. You can sync it again in ${seconds} seconds.`\n : `${label} synced moments ago. You can sync it again in a minute.`\n}\n\n/** Where a connection's data goes and comes from, as the card says it. */\nexport function pluginConnectionSharesText(hosts: readonly string[] | undefined): string | null {\n return hosts && hosts.length ? `Shares data with: ${hosts.join(', ')}` : null\n}\n\n/** A date and time as the cards print it. */\nexport function formatConnectionTime(ms: number): string {\n return new Date(ms).toLocaleString(undefined, { dateStyle: 'medium', timeStyle: 'short' })\n}\n"],"names":["platformConsoleOrigin","PLUGIN_CONNECTIONS_API_ROUTES","pluginConnectionRedirectUri","origin","replace","connectCallback","pluginConnectionPhase","entry","configured","connection","credentialsSet","status","ready","nextResourceToChoose","choices","resources","find","resource","id","PLUGIN_CONNECTION_PHASE_CHIP","label","tone","choosing","connected","reconnect","error","PLUGIN_CONNECTIONS_NOT_CONFIGURED_TEXT","pluginConnectionNotSetUpText","PLUGIN_CONNECTION_RETURN_PARAMS","listing","result","RESULT_MESSAGES","variant","message","denied","expired","failed","readPluginConnectionReturn","href","listingId","params","url","URL","searchParams","get","connectionId","key","Object","values","delete","cleanUrl","pathname","search","hash","pluginConnectionResultMessage","pluginConnectionNextSyncAtMs","view","intervalHours","nextSyncAtMs","lastSyncAtMs","declaredSyncIntervalHours","manifest","found","connections","Array","isArray","one","Boolean","hours","sync","Number","isFinite","declaredPluginConnections","flatMap","description","pluginConnectionTooSoonText","retryAfterMs","seconds","Math","ceil","pluginConnectionSharesText","hosts","length","join","formatConnectionTime","ms","Date","toLocaleString","undefined","dateStyle","timeStyle"],"mappings":"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"}
|
|
@@ -0,0 +1,172 @@
|
|
|
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
|
+
import { type PluginConnectionDeclaration } from '@aglyn/aglyn/app-utils/plugin-connections';
|
|
18
|
+
import type { HostAccessRole } from '@aglyn/aglyn/foundation/definitions/organization.types';
|
|
19
|
+
import type { OrgPlan } from '@aglyn/aglyn/foundation/definitions/org-billing.types';
|
|
20
|
+
import type { PluginFeatureDeclaration, PluginOrgPermissionDeclaration } from '@aglyn/aglyn/plugin-manager/plugin-entitlements';
|
|
21
|
+
import type { PluginPermission } from '@aglyn/aglyn/plugin-manager/plugin-permissions';
|
|
22
|
+
/** Top-level collection of the publisher OAuth app credentials, one per listing and connection. */
|
|
23
|
+
export declare const PLUGIN_CONNECTION_CREDENTIALS_COLLECTION = "pluginConnectionCredentials";
|
|
24
|
+
/** A site's connections: `hosts/{hostId}/pluginConnections`. */
|
|
25
|
+
export declare const PLUGIN_CONNECTIONS_HOST_COLLECTION = "pluginConnections";
|
|
26
|
+
/** The id shared by a listing's credentials and a site's connection: `{listingId}__{connectionId}`. */
|
|
27
|
+
export declare function pluginConnectionDocId(listingId: string, connectionId: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* The id of the record a connection's sync writes, beside the connection in
|
|
30
|
+
* `hosts/{hostId}/pluginConnections` (AGL-4192). The `record-` prefix keeps
|
|
31
|
+
* it apart from the connection (`{listingId}__{connectionId}`) and from a
|
|
32
|
+
* pending connect (`oauth-…`).
|
|
33
|
+
*/
|
|
34
|
+
export declare function pluginConnectionRecordDocId(listingId: string, connectionId: string): string;
|
|
35
|
+
/** One value of a record, typed by its declared field (`datetime` is epoch ms, `url` an https string). */
|
|
36
|
+
export type PluginConnectionRecordValue = string | number | boolean | null;
|
|
37
|
+
/**
|
|
38
|
+
* The record a connection's declared sync last wrote for one site: the
|
|
39
|
+
* declared summary `fields` and the declared `items`, each holding only the
|
|
40
|
+
* keys the manifest's `sync.record` declares, already capped to its limits.
|
|
41
|
+
* The sync replaces it whole; a site with no successful sync has none.
|
|
42
|
+
*/
|
|
43
|
+
export interface PluginConnectionRecordDoc {
|
|
44
|
+
kind: 'record';
|
|
45
|
+
listingId: string;
|
|
46
|
+
connectionId: string;
|
|
47
|
+
/** The listing version whose manifest declared the schema this was mapped with. */
|
|
48
|
+
version: string;
|
|
49
|
+
/** When the sync that wrote it finished, epoch ms. */
|
|
50
|
+
syncedAtMs: number;
|
|
51
|
+
fields: Record<string, PluginConnectionRecordValue>;
|
|
52
|
+
items: Array<Record<string, PluginConnectionRecordValue>>;
|
|
53
|
+
}
|
|
54
|
+
/** The console API paths (served at `/api/<path>`). The callback is registered at providers; never rename it. */
|
|
55
|
+
export declare const PLUGIN_CONNECTIONS_API_ROUTES: {
|
|
56
|
+
readonly connectStart: "plugin-connections/connect/start";
|
|
57
|
+
readonly connectCallback: "plugin-connections/connect/callback";
|
|
58
|
+
readonly resources: "plugin-connections/resources";
|
|
59
|
+
readonly choose: "plugin-connections/choose";
|
|
60
|
+
readonly disconnect: "plugin-connections/disconnect";
|
|
61
|
+
readonly status: "plugin-connections/status";
|
|
62
|
+
readonly credentials: "plugin-connections/credentials";
|
|
63
|
+
/**
|
|
64
|
+
* POST `{ hostId, listingId, connectionId }`: runs the connection's declared
|
|
65
|
+
* sync once and answers `{ connection: PluginConnectionView, synced }`
|
|
66
|
+
* (AGL-4194). Site key and plan, like connect.
|
|
67
|
+
*/
|
|
68
|
+
readonly sync: "plugin-connections/sync";
|
|
69
|
+
/** A site's record of one connection, for the editor's canvas (AGL-4195). */
|
|
70
|
+
readonly record: "plugin-connections/record";
|
|
71
|
+
};
|
|
72
|
+
/** The first path segment every route above shares: the plugin's API prefix. */
|
|
73
|
+
export declare const PLUGIN_CONNECTIONS_API_PREFIX = "plugin-connections";
|
|
74
|
+
/**
|
|
75
|
+
* A site connection's state.
|
|
76
|
+
*
|
|
77
|
+
* - `connected`: the grant works. `ready` on the view says whether every
|
|
78
|
+
* declared resource has been chosen, which a sync waits for.
|
|
79
|
+
* - `reconnect_required`: the provider refused the grant (revoked, expired,
|
|
80
|
+
* scopes withdrawn); only the site owner connecting again fixes it.
|
|
81
|
+
* - `error`: the last sync or refresh failed for another reason; the next
|
|
82
|
+
* run tries again.
|
|
83
|
+
*/
|
|
84
|
+
export type PluginConnectionStatus = 'connected' | 'reconnect_required' | 'error';
|
|
85
|
+
/** One resource the site owner chose: the id the provider gave it and what the picker showed. */
|
|
86
|
+
export interface PluginConnectionChoice {
|
|
87
|
+
id: string;
|
|
88
|
+
label: string;
|
|
89
|
+
}
|
|
90
|
+
/** What the console may see of a site's connection: never a token, a lease or a secret. */
|
|
91
|
+
export interface PluginConnectionView {
|
|
92
|
+
listingId: string;
|
|
93
|
+
connectionId: string;
|
|
94
|
+
provider: string;
|
|
95
|
+
status: PluginConnectionStatus;
|
|
96
|
+
/** The choices made so far, by resource id. */
|
|
97
|
+
choices: Record<string, PluginConnectionChoice>;
|
|
98
|
+
/** Whether every declared resource is chosen. */
|
|
99
|
+
ready: boolean;
|
|
100
|
+
scopes: string[];
|
|
101
|
+
connectedAtMs: number;
|
|
102
|
+
lastSyncAtMs: number | null;
|
|
103
|
+
lastError: string | null;
|
|
104
|
+
}
|
|
105
|
+
/** One declared connection of a plugin installed on a site, with where it stands. */
|
|
106
|
+
export interface PluginConnectionStatusEntry {
|
|
107
|
+
listingId: string;
|
|
108
|
+
connectionId: string;
|
|
109
|
+
provider: string;
|
|
110
|
+
label: string;
|
|
111
|
+
description?: string;
|
|
112
|
+
/** The resources the owner picks, in order. */
|
|
113
|
+
resources: Array<{
|
|
114
|
+
id: string;
|
|
115
|
+
label: string;
|
|
116
|
+
}>;
|
|
117
|
+
/** The site's connection, or `null` before one is made. */
|
|
118
|
+
connection: PluginConnectionView | null;
|
|
119
|
+
/** Whether the publisher has set the listing's OAuth app credentials. */
|
|
120
|
+
credentialsSet: boolean;
|
|
121
|
+
/** Whether the workspace's plan includes connections. */
|
|
122
|
+
planAllows: boolean;
|
|
123
|
+
/** Every host this connection's data goes to or comes from ({@link pluginConnectionRecipients}). */
|
|
124
|
+
hosts: string[];
|
|
125
|
+
}
|
|
126
|
+
/** Whether a listing's OAuth app credentials are set: the only thing ever read back. */
|
|
127
|
+
export interface PluginConnectionCredentialsStatus {
|
|
128
|
+
connectionId: string;
|
|
129
|
+
label: string;
|
|
130
|
+
set: boolean;
|
|
131
|
+
updatedAtMs: number | null;
|
|
132
|
+
}
|
|
133
|
+
/** The plan feature key: connections are on Starter, Pro and Custom, off on Free. */
|
|
134
|
+
export declare const PLUGIN_CONNECTIONS_FEATURE = "pluginConnections";
|
|
135
|
+
/** The plan defaults. The retired plans sit above Pro on the ladder, so they keep it. */
|
|
136
|
+
export declare const PLUGIN_CONNECTIONS_BY_PLAN: Readonly<Record<OrgPlan, boolean>>;
|
|
137
|
+
/** The feature as the marketplace declares it (`registerPluginEntitlements`). */
|
|
138
|
+
export declare const PLUGIN_CONNECTIONS_FEATURE_DECLARATION: PluginFeatureDeclaration;
|
|
139
|
+
/** What a plan without the feature is told. Names the plans, never a price. */
|
|
140
|
+
export declare const PLUGIN_CONNECTIONS_PLAN_REFUSAL = "Connecting a plugin to another service comes with the Starter, Pro and Custom plans.";
|
|
141
|
+
/** The site key: connect, choose, disconnect and read a site's plugin connections. */
|
|
142
|
+
export declare const PLUGIN_CONNECTIONS_SITE_PERMISSION = "marketplace.siteConnections";
|
|
143
|
+
/** The publisher key: set a listing's OAuth app credentials. */
|
|
144
|
+
export declare const PLUGIN_CONNECTIONS_CREDENTIALS_PERMISSION = "marketplace.connectionCredentials";
|
|
145
|
+
/** The site Admin (declared beside the marketplace's other site keys). */
|
|
146
|
+
export declare const PLUGIN_CONNECTIONS_SITE_ROLE_PRESET: Readonly<Record<HostAccessRole, boolean>>;
|
|
147
|
+
/** The site key as the plugin declares it into the org permission catalog. */
|
|
148
|
+
export declare const PLUGIN_CONNECTIONS_SITE_PERMISSION_DECLARATION: PluginOrgPermissionDeclaration;
|
|
149
|
+
/** The publisher key as the plugin declares it; owners and admins hold it by default. */
|
|
150
|
+
export declare const PLUGIN_CONNECTIONS_CREDENTIALS_PERMISSION_DECLARATION: PluginPermission;
|
|
151
|
+
/** The refusal a door answers without a key: names the permission, never a role. */
|
|
152
|
+
export declare function pluginConnectionsPermissionRefusal(key: typeof PLUGIN_CONNECTIONS_SITE_PERMISSION | typeof PLUGIN_CONNECTIONS_CREDENTIALS_PERMISSION): string;
|
|
153
|
+
/**
|
|
154
|
+
* One declared connection's recipients, derived from its declaration alone
|
|
155
|
+
* (AGL-4194): the hosts the site owner's browser is sent to for consent and
|
|
156
|
+
* the platform calls with the site's grant — the authorization, token and
|
|
157
|
+
* revocation endpoints and every `apiHosts` entry, wildcards as declared.
|
|
158
|
+
* The listing's own list of where data goes, per listing: it is never part
|
|
159
|
+
* of Aglyn's published subprocessor list, because the site owner chooses the
|
|
160
|
+
* provider and connects their own account to it.
|
|
161
|
+
*/
|
|
162
|
+
export interface PluginConnectionRecipient {
|
|
163
|
+
connectionId: string;
|
|
164
|
+
provider: string;
|
|
165
|
+
label: string;
|
|
166
|
+
/** Sorted, each once. */
|
|
167
|
+
hosts: string[];
|
|
168
|
+
}
|
|
169
|
+
/** The hosts one declared connection reaches, sorted. */
|
|
170
|
+
export declare function pluginConnectionHosts(connection: PluginConnectionDeclaration): string[];
|
|
171
|
+
/** Each declared connection of a listing version, with the hosts it reaches. */
|
|
172
|
+
export declare function pluginConnectionRecipients(connections: readonly PluginConnectionDeclaration[]): PluginConnectionRecipient[];
|
|
@@ -0,0 +1,119 @@
|
|
|
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
|
+
*/ import { pluginConnectionEndpointHosts } from "@aglyn/aglyn/app-utils/plugin-connections";
|
|
17
|
+
import { MARKETPLACE_PUBLISHER_PERMISSIONS } from "./marketplace-publisher-permissions.js";
|
|
18
|
+
import { MARKETPLACE_CONNECTION_SITE_PERMISSIONS, MARKETPLACE_CONNECTION_SITE_ROLE_PRESET } from "./marketplace-site-permissions.js";
|
|
19
|
+
/*
|
|
20
|
+
* DECLARED CONNECTIONS, THE PLATFORM'S HALF (AGL-4193): THE CONTRACT.
|
|
21
|
+
*
|
|
22
|
+
* A marketplace plugin declares a connection in its manifest
|
|
23
|
+
* (`@aglyn/aglyn/app-utils/plugin-connections`); this plugin, the
|
|
24
|
+
* marketplace, runs it for every plugin it distributes: the publisher's OAuth
|
|
25
|
+
* app credentials per listing, the site's sealed grant, the connect routes,
|
|
26
|
+
* the plan feature and the two permission keys. Light and client-safe: the
|
|
27
|
+
* console card imports the names, views and keys from here, and nothing here
|
|
28
|
+
* reaches Firestore, a provider or React.
|
|
29
|
+
*
|
|
30
|
+
* Where the data lives, all server-only (no client rule opens any of it):
|
|
31
|
+
*
|
|
32
|
+
* pluginConnectionCredentials/{listingId}__{connectionId}
|
|
33
|
+
* the publisher's OAuth client id and secret, both sealed, keyed by
|
|
34
|
+
* the publishing workspace in `orgId` so its erasure sweeps them;
|
|
35
|
+
* hosts/{hostId}/pluginConnections/{listingId}__{connectionId}
|
|
36
|
+
* a site's grant: sealed tokens, its choices, status, sync times;
|
|
37
|
+
* hosts/{hostId}/pluginConnections/oauth-{hash}
|
|
38
|
+
* a pending connect's single-use state record;
|
|
39
|
+
* hosts/{hostId}/pluginConnections/record-{listingId}__{connectionId}
|
|
40
|
+
* the record a connection's declared sync last wrote (AGL-4192):
|
|
41
|
+
* written only by the sync, read only on the server (the site's page
|
|
42
|
+
* render and the console's routes), never by a client rule.
|
|
43
|
+
*
|
|
44
|
+
* The sync (AGL-4194) runs in the console on the fifteen-minute
|
|
45
|
+
* `plugin-console-crons` tick as the `marketplace-connection-sync` job, and
|
|
46
|
+
* for one connection on "Sync now" (`PLUGIN_CONNECTIONS_API_ROUTES.sync`).
|
|
47
|
+
*/ /** Top-level collection of the publisher OAuth app credentials, one per listing and connection. */ export const PLUGIN_CONNECTION_CREDENTIALS_COLLECTION = 'pluginConnectionCredentials';
|
|
48
|
+
/** A site's connections: `hosts/{hostId}/pluginConnections`. */ export const PLUGIN_CONNECTIONS_HOST_COLLECTION = 'pluginConnections';
|
|
49
|
+
/** The id shared by a listing's credentials and a site's connection: `{listingId}__{connectionId}`. */ export function pluginConnectionDocId(listingId, connectionId) {
|
|
50
|
+
return `${listingId}__${connectionId}`;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The id of the record a connection's sync writes, beside the connection in
|
|
54
|
+
* `hosts/{hostId}/pluginConnections` (AGL-4192). The `record-` prefix keeps
|
|
55
|
+
* it apart from the connection (`{listingId}__{connectionId}`) and from a
|
|
56
|
+
* pending connect (`oauth-…`).
|
|
57
|
+
*/ export function pluginConnectionRecordDocId(listingId, connectionId) {
|
|
58
|
+
return `record-${pluginConnectionDocId(listingId, connectionId)}`;
|
|
59
|
+
}
|
|
60
|
+
/** The console API paths (served at `/api/<path>`). The callback is registered at providers; never rename it. */ export const PLUGIN_CONNECTIONS_API_ROUTES = {
|
|
61
|
+
connectStart: 'plugin-connections/connect/start',
|
|
62
|
+
connectCallback: 'plugin-connections/connect/callback',
|
|
63
|
+
resources: 'plugin-connections/resources',
|
|
64
|
+
choose: 'plugin-connections/choose',
|
|
65
|
+
disconnect: 'plugin-connections/disconnect',
|
|
66
|
+
status: 'plugin-connections/status',
|
|
67
|
+
credentials: 'plugin-connections/credentials',
|
|
68
|
+
/**
|
|
69
|
+
* POST `{ hostId, listingId, connectionId }`: runs the connection's declared
|
|
70
|
+
* sync once and answers `{ connection: PluginConnectionView, synced }`
|
|
71
|
+
* (AGL-4194). Site key and plan, like connect.
|
|
72
|
+
*/ sync: 'plugin-connections/sync',
|
|
73
|
+
/** A site's record of one connection, for the editor's canvas (AGL-4195). */ record: 'plugin-connections/record'
|
|
74
|
+
};
|
|
75
|
+
/** The first path segment every route above shares: the plugin's API prefix. */ export const PLUGIN_CONNECTIONS_API_PREFIX = 'plugin-connections';
|
|
76
|
+
/* ------------------------------------------------------------------ */ /* The plan feature */ /* ------------------------------------------------------------------ */ /** The plan feature key: connections are on Starter, Pro and Custom, off on Free. */ export const PLUGIN_CONNECTIONS_FEATURE = 'pluginConnections';
|
|
77
|
+
/** The plan defaults. The retired plans sit above Pro on the ladder, so they keep it. */ export const PLUGIN_CONNECTIONS_BY_PLAN = {
|
|
78
|
+
free: false,
|
|
79
|
+
starter: true,
|
|
80
|
+
pro: true,
|
|
81
|
+
business: true,
|
|
82
|
+
scale: true,
|
|
83
|
+
advanced: true,
|
|
84
|
+
agency: true,
|
|
85
|
+
enterprise: true
|
|
86
|
+
};
|
|
87
|
+
/** The feature as the marketplace declares it (`registerPluginEntitlements`). */ export const PLUGIN_CONNECTIONS_FEATURE_DECLARATION = {
|
|
88
|
+
key: PLUGIN_CONNECTIONS_FEATURE,
|
|
89
|
+
label: 'Plugin connections',
|
|
90
|
+
defaultByPlan: PLUGIN_CONNECTIONS_BY_PLAN
|
|
91
|
+
};
|
|
92
|
+
/** What a plan without the feature is told. Names the plans, never a price. */ export const PLUGIN_CONNECTIONS_PLAN_REFUSAL = 'Connecting a plugin to another service comes with the Starter, Pro and Custom plans.';
|
|
93
|
+
/* ------------------------------------------------------------------ */ /* The permission keys */ /* ------------------------------------------------------------------ */ /** The site key: connect, choose, disconnect and read a site's plugin connections. */ export const PLUGIN_CONNECTIONS_SITE_PERMISSION = 'marketplace.siteConnections';
|
|
94
|
+
/** The publisher key: set a listing's OAuth app credentials. */ export const PLUGIN_CONNECTIONS_CREDENTIALS_PERMISSION = 'marketplace.connectionCredentials';
|
|
95
|
+
/** The site Admin (declared beside the marketplace's other site keys). */ export const PLUGIN_CONNECTIONS_SITE_ROLE_PRESET = MARKETPLACE_CONNECTION_SITE_ROLE_PRESET;
|
|
96
|
+
/** The site key as the plugin declares it into the org permission catalog. */ export const PLUGIN_CONNECTIONS_SITE_PERMISSION_DECLARATION = MARKETPLACE_CONNECTION_SITE_PERMISSIONS[0];
|
|
97
|
+
/** The publisher key as the plugin declares it; owners and admins hold it by default. */ export const PLUGIN_CONNECTIONS_CREDENTIALS_PERMISSION_DECLARATION = MARKETPLACE_PUBLISHER_PERMISSIONS.find((entry)=>entry.key === PLUGIN_CONNECTIONS_CREDENTIALS_PERMISSION);
|
|
98
|
+
/** The refusal a door answers without a key: names the permission, never a role. */ export function pluginConnectionsPermissionRefusal(key) {
|
|
99
|
+
const label = key === PLUGIN_CONNECTIONS_SITE_PERMISSION ? PLUGIN_CONNECTIONS_SITE_PERMISSION_DECLARATION.label : PLUGIN_CONNECTIONS_CREDENTIALS_PERMISSION_DECLARATION.label;
|
|
100
|
+
return `Your role does not include "${label}" — ask an organization admin`;
|
|
101
|
+
}
|
|
102
|
+
/** The hosts one declared connection reaches, sorted. */ export function pluginConnectionHosts(connection) {
|
|
103
|
+
return [
|
|
104
|
+
...new Set([
|
|
105
|
+
...pluginConnectionEndpointHosts(connection),
|
|
106
|
+
...connection.apiHosts
|
|
107
|
+
])
|
|
108
|
+
].sort();
|
|
109
|
+
}
|
|
110
|
+
/** Each declared connection of a listing version, with the hosts it reaches. */ export function pluginConnectionRecipients(connections) {
|
|
111
|
+
return connections.map((connection)=>({
|
|
112
|
+
connectionId: connection.id,
|
|
113
|
+
provider: connection.provider,
|
|
114
|
+
label: connection.label,
|
|
115
|
+
hosts: pluginConnectionHosts(connection)
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
//# sourceMappingURL=plugin-connections.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/model/plugin-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 {\n pluginConnectionEndpointHosts,\n type PluginConnectionDeclaration,\n} from '@aglyn/aglyn/app-utils/plugin-connections'\nimport type { HostAccessRole } from '@aglyn/aglyn/foundation/definitions/organization.types'\nimport type { OrgPlan } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport type { PluginFeatureDeclaration, PluginOrgPermissionDeclaration } from '@aglyn/aglyn/plugin-manager/plugin-entitlements'\nimport type { PluginPermission } from '@aglyn/aglyn/plugin-manager/plugin-permissions'\nimport { MARKETPLACE_PUBLISHER_PERMISSIONS } from './marketplace-publisher-permissions'\nimport {\n MARKETPLACE_CONNECTION_SITE_PERMISSIONS,\n MARKETPLACE_CONNECTION_SITE_ROLE_PRESET,\n} from './marketplace-site-permissions'\n\n/*\n * DECLARED CONNECTIONS, THE PLATFORM'S HALF (AGL-4193): THE CONTRACT.\n *\n * A marketplace plugin declares a connection in its manifest\n * (`@aglyn/aglyn/app-utils/plugin-connections`); this plugin, the\n * marketplace, runs it for every plugin it distributes: the publisher's OAuth\n * app credentials per listing, the site's sealed grant, the connect routes,\n * the plan feature and the two permission keys. Light and client-safe: the\n * console card imports the names, views and keys from here, and nothing here\n * reaches Firestore, a provider or React.\n *\n * Where the data lives, all server-only (no client rule opens any of it):\n *\n * pluginConnectionCredentials/{listingId}__{connectionId}\n * the publisher's OAuth client id and secret, both sealed, keyed by\n * the publishing workspace in `orgId` so its erasure sweeps them;\n * hosts/{hostId}/pluginConnections/{listingId}__{connectionId}\n * a site's grant: sealed tokens, its choices, status, sync times;\n * hosts/{hostId}/pluginConnections/oauth-{hash}\n * a pending connect's single-use state record;\n * hosts/{hostId}/pluginConnections/record-{listingId}__{connectionId}\n * the record a connection's declared sync last wrote (AGL-4192):\n * written only by the sync, read only on the server (the site's page\n * render and the console's routes), never by a client rule.\n *\n * The sync (AGL-4194) runs in the console on the fifteen-minute\n * `plugin-console-crons` tick as the `marketplace-connection-sync` job, and\n * for one connection on \"Sync now\" (`PLUGIN_CONNECTIONS_API_ROUTES.sync`).\n */\n\n/** Top-level collection of the publisher OAuth app credentials, one per listing and connection. */\nexport const PLUGIN_CONNECTION_CREDENTIALS_COLLECTION = 'pluginConnectionCredentials'\n\n/** A site's connections: `hosts/{hostId}/pluginConnections`. */\nexport const PLUGIN_CONNECTIONS_HOST_COLLECTION = 'pluginConnections'\n\n/** The id shared by a listing's credentials and a site's connection: `{listingId}__{connectionId}`. */\nexport function pluginConnectionDocId(listingId: string, connectionId: string): string {\n return `${listingId}__${connectionId}`\n}\n\n/**\n * The id of the record a connection's sync writes, beside the connection in\n * `hosts/{hostId}/pluginConnections` (AGL-4192). The `record-` prefix keeps\n * it apart from the connection (`{listingId}__{connectionId}`) and from a\n * pending connect (`oauth-…`).\n */\nexport function pluginConnectionRecordDocId(listingId: string, connectionId: string): string {\n return `record-${pluginConnectionDocId(listingId, connectionId)}`\n}\n\n/** One value of a record, typed by its declared field (`datetime` is epoch ms, `url` an https string). */\nexport type PluginConnectionRecordValue = string | number | boolean | null\n\n/**\n * The record a connection's declared sync last wrote for one site: the\n * declared summary `fields` and the declared `items`, each holding only the\n * keys the manifest's `sync.record` declares, already capped to its limits.\n * The sync replaces it whole; a site with no successful sync has none.\n */\nexport interface PluginConnectionRecordDoc {\n kind: 'record'\n listingId: string\n connectionId: string\n /** The listing version whose manifest declared the schema this was mapped with. */\n version: string\n /** When the sync that wrote it finished, epoch ms. */\n syncedAtMs: number\n fields: Record<string, PluginConnectionRecordValue>\n items: Array<Record<string, PluginConnectionRecordValue>>\n}\n\n/** The console API paths (served at `/api/<path>`). The callback is registered at providers; never rename it. */\nexport const PLUGIN_CONNECTIONS_API_ROUTES = {\n connectStart: 'plugin-connections/connect/start',\n connectCallback: 'plugin-connections/connect/callback',\n resources: 'plugin-connections/resources',\n choose: 'plugin-connections/choose',\n disconnect: 'plugin-connections/disconnect',\n status: 'plugin-connections/status',\n credentials: 'plugin-connections/credentials',\n /**\n * POST `{ hostId, listingId, connectionId }`: runs the connection's declared\n * sync once and answers `{ connection: PluginConnectionView, synced }`\n * (AGL-4194). Site key and plan, like connect.\n */\n sync: 'plugin-connections/sync',\n /** A site's record of one connection, for the editor's canvas (AGL-4195). */\n record: 'plugin-connections/record',\n} as const\n\n/** The first path segment every route above shares: the plugin's API prefix. */\nexport const PLUGIN_CONNECTIONS_API_PREFIX = 'plugin-connections'\n\n/**\n * A site connection's state.\n *\n * - `connected`: the grant works. `ready` on the view says whether every\n * declared resource has been chosen, which a sync waits for.\n * - `reconnect_required`: the provider refused the grant (revoked, expired,\n * scopes withdrawn); only the site owner connecting again fixes it.\n * - `error`: the last sync or refresh failed for another reason; the next\n * run tries again.\n */\nexport type PluginConnectionStatus = 'connected' | 'reconnect_required' | 'error'\n\n/** One resource the site owner chose: the id the provider gave it and what the picker showed. */\nexport interface PluginConnectionChoice {\n id: string\n label: string\n}\n\n/** What the console may see of a site's connection: never a token, a lease or a secret. */\nexport interface PluginConnectionView {\n listingId: string\n connectionId: string\n provider: string\n status: PluginConnectionStatus\n /** The choices made so far, by resource id. */\n choices: Record<string, PluginConnectionChoice>\n /** Whether every declared resource is chosen. */\n ready: boolean\n scopes: string[]\n connectedAtMs: number\n lastSyncAtMs: number | null\n lastError: string | null\n}\n\n/** One declared connection of a plugin installed on a site, with where it stands. */\nexport interface PluginConnectionStatusEntry {\n listingId: string\n connectionId: string\n provider: string\n label: string\n description?: string\n /** The resources the owner picks, in order. */\n resources: Array<{ id: string; label: string }>\n /** The site's connection, or `null` before one is made. */\n connection: PluginConnectionView | null\n /** Whether the publisher has set the listing's OAuth app credentials. */\n credentialsSet: boolean\n /** Whether the workspace's plan includes connections. */\n planAllows: boolean\n /** Every host this connection's data goes to or comes from ({@link pluginConnectionRecipients}). */\n hosts: string[]\n}\n\n/** Whether a listing's OAuth app credentials are set: the only thing ever read back. */\nexport interface PluginConnectionCredentialsStatus {\n connectionId: string\n label: string\n set: boolean\n updatedAtMs: number | null\n}\n\n/* ------------------------------------------------------------------ */\n/* The plan feature */\n/* ------------------------------------------------------------------ */\n\n/** The plan feature key: connections are on Starter, Pro and Custom, off on Free. */\nexport const PLUGIN_CONNECTIONS_FEATURE = 'pluginConnections'\n\n/** The plan defaults. The retired plans sit above Pro on the ladder, so they keep it. */\nexport const PLUGIN_CONNECTIONS_BY_PLAN: Readonly<Record<OrgPlan, boolean>> = {\n free: false,\n starter: true,\n pro: true,\n business: true,\n scale: true,\n advanced: true,\n agency: true,\n enterprise: true,\n}\n\n/** The feature as the marketplace declares it (`registerPluginEntitlements`). */\nexport const PLUGIN_CONNECTIONS_FEATURE_DECLARATION: PluginFeatureDeclaration = {\n key: PLUGIN_CONNECTIONS_FEATURE,\n label: 'Plugin connections',\n defaultByPlan: PLUGIN_CONNECTIONS_BY_PLAN,\n}\n\n/** What a plan without the feature is told. Names the plans, never a price. */\nexport const PLUGIN_CONNECTIONS_PLAN_REFUSAL =\n 'Connecting a plugin to another service comes with the Starter, Pro and Custom plans.'\n\n/* ------------------------------------------------------------------ */\n/* The permission keys */\n/* ------------------------------------------------------------------ */\n\n/** The site key: connect, choose, disconnect and read a site's plugin connections. */\nexport const PLUGIN_CONNECTIONS_SITE_PERMISSION = 'marketplace.siteConnections'\n\n/** The publisher key: set a listing's OAuth app credentials. */\nexport const PLUGIN_CONNECTIONS_CREDENTIALS_PERMISSION = 'marketplace.connectionCredentials'\n\n/** The site Admin (declared beside the marketplace's other site keys). */\nexport const PLUGIN_CONNECTIONS_SITE_ROLE_PRESET: Readonly<Record<HostAccessRole, boolean>> =\n MARKETPLACE_CONNECTION_SITE_ROLE_PRESET\n\n/** The site key as the plugin declares it into the org permission catalog. */\nexport const PLUGIN_CONNECTIONS_SITE_PERMISSION_DECLARATION: PluginOrgPermissionDeclaration =\n MARKETPLACE_CONNECTION_SITE_PERMISSIONS[0]\n\n/** The publisher key as the plugin declares it; owners and admins hold it by default. */\nexport const PLUGIN_CONNECTIONS_CREDENTIALS_PERMISSION_DECLARATION: PluginPermission =\n MARKETPLACE_PUBLISHER_PERMISSIONS.find((entry) => entry.key === PLUGIN_CONNECTIONS_CREDENTIALS_PERMISSION) as PluginPermission\n\n/** The refusal a door answers without a key: names the permission, never a role. */\nexport function pluginConnectionsPermissionRefusal(\n key: typeof PLUGIN_CONNECTIONS_SITE_PERMISSION | typeof PLUGIN_CONNECTIONS_CREDENTIALS_PERMISSION,\n): string {\n const label =\n key === PLUGIN_CONNECTIONS_SITE_PERMISSION\n ? PLUGIN_CONNECTIONS_SITE_PERMISSION_DECLARATION.label\n : PLUGIN_CONNECTIONS_CREDENTIALS_PERMISSION_DECLARATION.label\n return `Your role does not include \"${label}\" — ask an organization admin`\n}\n\n/* ------------------------------------------------------------------ */\n/* Where a listing's connections send data */\n/* ------------------------------------------------------------------ */\n\n/**\n * One declared connection's recipients, derived from its declaration alone\n * (AGL-4194): the hosts the site owner's browser is sent to for consent and\n * the platform calls with the site's grant — the authorization, token and\n * revocation endpoints and every `apiHosts` entry, wildcards as declared.\n * The listing's own list of where data goes, per listing: it is never part\n * of Aglyn's published subprocessor list, because the site owner chooses the\n * provider and connects their own account to it.\n */\nexport interface PluginConnectionRecipient {\n connectionId: string\n provider: string\n label: string\n /** Sorted, each once. */\n hosts: string[]\n}\n\n/** The hosts one declared connection reaches, sorted. */\nexport function pluginConnectionHosts(connection: PluginConnectionDeclaration): string[] {\n return [...new Set([...pluginConnectionEndpointHosts(connection), ...connection.apiHosts])].sort()\n}\n\n/** Each declared connection of a listing version, with the hosts it reaches. */\nexport function pluginConnectionRecipients(\n connections: readonly PluginConnectionDeclaration[],\n): PluginConnectionRecipient[] {\n return connections.map((connection) => ({\n connectionId: connection.id,\n provider: connection.provider,\n label: connection.label,\n hosts: pluginConnectionHosts(connection),\n }))\n}\n"],"names":["pluginConnectionEndpointHosts","MARKETPLACE_PUBLISHER_PERMISSIONS","MARKETPLACE_CONNECTION_SITE_PERMISSIONS","MARKETPLACE_CONNECTION_SITE_ROLE_PRESET","PLUGIN_CONNECTION_CREDENTIALS_COLLECTION","PLUGIN_CONNECTIONS_HOST_COLLECTION","pluginConnectionDocId","listingId","connectionId","pluginConnectionRecordDocId","PLUGIN_CONNECTIONS_API_ROUTES","connectStart","connectCallback","resources","choose","disconnect","status","credentials","sync","record","PLUGIN_CONNECTIONS_API_PREFIX","PLUGIN_CONNECTIONS_FEATURE","PLUGIN_CONNECTIONS_BY_PLAN","free","starter","pro","business","scale","advanced","agency","enterprise","PLUGIN_CONNECTIONS_FEATURE_DECLARATION","key","label","defaultByPlan","PLUGIN_CONNECTIONS_PLAN_REFUSAL","PLUGIN_CONNECTIONS_SITE_PERMISSION","PLUGIN_CONNECTIONS_CREDENTIALS_PERMISSION","PLUGIN_CONNECTIONS_SITE_ROLE_PRESET","PLUGIN_CONNECTIONS_SITE_PERMISSION_DECLARATION","PLUGIN_CONNECTIONS_CREDENTIALS_PERMISSION_DECLARATION","find","entry","pluginConnectionsPermissionRefusal","pluginConnectionHosts","connection","Set","apiHosts","sort","pluginConnectionRecipients","connections","map","id","provider","hosts"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,6BAA6B,QAExB,4CAA2C;AAKlD,SAASC,iCAAiC,QAAQ,yCAAqC;AACvF,SACEC,uCAAuC,EACvCC,uCAAuC,QAClC,oCAAgC;AAEvC;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BC,GAED,iGAAiG,GACjG,OAAO,MAAMC,2CAA2C,8BAA6B;AAErF,8DAA8D,GAC9D,OAAO,MAAMC,qCAAqC,oBAAmB;AAErE,qGAAqG,GACrG,OAAO,SAASC,sBAAsBC,SAAiB,EAAEC,YAAoB;IAC3E,OAAO,GAAGD,UAAU,EAAE,EAAEC,cAAc;AACxC;AAEA;;;;;CAKC,GACD,OAAO,SAASC,4BAA4BF,SAAiB,EAAEC,YAAoB;IACjF,OAAO,CAAC,OAAO,EAAEF,sBAAsBC,WAAWC,eAAe;AACnE;AAuBA,+GAA+G,GAC/G,OAAO,MAAME,gCAAgC;IAC3CC,cAAc;IACdC,iBAAiB;IACjBC,WAAW;IACXC,QAAQ;IACRC,YAAY;IACZC,QAAQ;IACRC,aAAa;IACb;;;;GAIC,GACDC,MAAM;IACN,2EAA2E,GAC3EC,QAAQ;AACV,EAAU;AAEV,8EAA8E,GAC9E,OAAO,MAAMC,gCAAgC,qBAAoB;AA+DjE,sEAAsE,GACtE,wEAAwE,GACxE,sEAAsE,GAEtE,mFAAmF,GACnF,OAAO,MAAMC,6BAA6B,oBAAmB;AAE7D,uFAAuF,GACvF,OAAO,MAAMC,6BAAiE;IAC5EC,MAAM;IACNC,SAAS;IACTC,KAAK;IACLC,UAAU;IACVC,OAAO;IACPC,UAAU;IACVC,QAAQ;IACRC,YAAY;AACd,EAAC;AAED,+EAA+E,GAC/E,OAAO,MAAMC,yCAAmE;IAC9EC,KAAKX;IACLY,OAAO;IACPC,eAAeZ;AACjB,EAAC;AAED,6EAA6E,GAC7E,OAAO,MAAMa,kCACX,uFAAsF;AAExF,sEAAsE,GACtE,wEAAwE,GACxE,sEAAsE,GAEtE,oFAAoF,GACpF,OAAO,MAAMC,qCAAqC,8BAA6B;AAE/E,8DAA8D,GAC9D,OAAO,MAAMC,4CAA4C,oCAAmC;AAE5F,wEAAwE,GACxE,OAAO,MAAMC,sCACXnC,wCAAuC;AAEzC,4EAA4E,GAC5E,OAAO,MAAMoC,iDACXrC,uCAAuC,CAAC,EAAE,CAAA;AAE5C,uFAAuF,GACvF,OAAO,MAAMsC,wDACXvC,kCAAkCwC,IAAI,CAAC,CAACC,QAAUA,MAAMV,GAAG,KAAKK,2CAA8D;AAEhI,kFAAkF,GAClF,OAAO,SAASM,mCACdX,GAAiG;IAEjG,MAAMC,QACJD,QAAQI,qCACJG,+CAA+CN,KAAK,GACpDO,sDAAsDP,KAAK;IACjE,OAAO,CAAC,4BAA4B,EAAEA,MAAM,6BAA6B,CAAC;AAC5E;AAuBA,uDAAuD,GACvD,OAAO,SAASW,sBAAsBC,UAAuC;IAC3E,OAAO;WAAI,IAAIC,IAAI;eAAI9C,8BAA8B6C;eAAgBA,WAAWE,QAAQ;SAAC;KAAE,CAACC,IAAI;AAClG;AAEA,8EAA8E,GAC9E,OAAO,SAASC,2BACdC,WAAmD;IAEnD,OAAOA,YAAYC,GAAG,CAAC,CAACN,aAAgB,CAAA;YACtCrC,cAAcqC,WAAWO,EAAE;YAC3BC,UAAUR,WAAWQ,QAAQ;YAC7BpB,OAAOY,WAAWZ,KAAK;YACvBqB,OAAOV,sBAAsBC;QAC/B,CAAA;AACF"}
|
package/src/lib/plugin.js
CHANGED
|
@@ -23,6 +23,7 @@ import PluginReviewsSurface from "./components/plugin-reviews-surface.component.
|
|
|
23
23
|
import PluginSiteSetPanel from "./components/plugin-site-set-panel.component.js";
|
|
24
24
|
import OrgPluginInstallsCard from "./components/org-plugin-installs-card.component.js";
|
|
25
25
|
import PluginInstallStatus from "./components/plugin-install-status.component.js";
|
|
26
|
+
import EditorConnectionsProvider from "./components/editor-connections-provider.component.js";
|
|
26
27
|
import StaffMarketplaceOverview from "./components/staff-marketplace-overview.component.js";
|
|
27
28
|
import StarterPickerShelf from "./components/starter-picker-shelf.component.js";
|
|
28
29
|
import TemplateGalleryShelf from "./components/template-gallery-shelf.component.js";
|
|
@@ -32,6 +33,8 @@ import { BUNDLE_ID } from "./constants/bundle-common.js";
|
|
|
32
33
|
import { MARKETPLACE_LICENSES_SECTION_ID } from "./model/marketplace-section-ids.js";
|
|
33
34
|
import { RATING_FIELD } from "./model/rating-field.js";
|
|
34
35
|
import RatingInput from "./components/rating-input.component.js";
|
|
36
|
+
import { lazy } from "react";
|
|
37
|
+
/** Drawn only on a site's page for a plugin that declares connections, so it loads when it is drawn. */ const SitePluginConnectionsCard = lazy(()=>import("./components/site-plugin-connections-card.component.js"));
|
|
35
38
|
/**
|
|
36
39
|
* Marketplace feature plugin (AGL-395). Console-only — marketplace components
|
|
37
40
|
* install into a host's own components collection and render through the
|
|
@@ -91,6 +94,15 @@ import RatingInput from "./components/rating-input.component.js";
|
|
|
91
94
|
widgetId: 'marketplace-plugin-install-status',
|
|
92
95
|
Component: PluginInstallStatus
|
|
93
96
|
},
|
|
97
|
+
// A site's page for one installed plugin (AGL-4196): the services the
|
|
98
|
+
// pinned manifest declares it connects to. This plugin runs the
|
|
99
|
+
// connections, so it draws them; the card checks the site key itself
|
|
100
|
+
// and keeps every control on screen with the reason when it is off.
|
|
101
|
+
{
|
|
102
|
+
slot: 'sitePluginConnections',
|
|
103
|
+
widgetId: 'marketplace-site-plugin-connections',
|
|
104
|
+
Component: SitePluginConnectionsCard
|
|
105
|
+
},
|
|
94
106
|
// The staff overview (AGL-3080): recent purchases and the
|
|
95
107
|
// refund-reversal recovery queue, from this plugin's own purchases.
|
|
96
108
|
{
|
|
@@ -142,7 +154,15 @@ import RatingInput from "./components/rating-input.component.js";
|
|
|
142
154
|
* typo from an id, so it owns saying "no such thing" — which a listing
|
|
143
155
|
* page has to do anyway for one deleted while a link to it was still in
|
|
144
156
|
* someone's inbox.
|
|
145
|
-
*/
|
|
157
|
+
*/ /*
|
|
158
|
+
* The site's declared connections in the editor (AGL-4196): the records a
|
|
159
|
+
* signed element reads, and the variables the binding picker lists, on
|
|
160
|
+
* the Besigner and Preview only. Core declares the context; this plugin
|
|
161
|
+
* runs the connections, so it fills it.
|
|
162
|
+
*/ providers: [
|
|
163
|
+
EditorConnectionsProvider
|
|
164
|
+
],
|
|
165
|
+
orgNavItems: [
|
|
146
166
|
{
|
|
147
167
|
label: 'Marketplace',
|
|
148
168
|
href: '/marketplace',
|