@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.
Files changed (107) hide show
  1. package/README.md +1 -0
  2. package/package.json +15 -14
  3. package/src/lib/components/editor-connections-provider.component.d.ts +37 -0
  4. package/src/lib/components/editor-connections-provider.component.js +161 -0
  5. package/src/lib/components/editor-connections-provider.component.js.map +1 -0
  6. package/src/lib/components/listing-connection-credentials-card.component.d.ts +33 -0
  7. package/src/lib/components/listing-connection-credentials-card.component.js +318 -0
  8. package/src/lib/components/listing-connection-credentials-card.component.js.map +1 -0
  9. package/src/lib/components/listing-detail-page.component.js +10 -2
  10. package/src/lib/components/listing-detail-page.component.js.map +1 -1
  11. package/src/lib/components/site-plugin-connections-card.component.d.ts +38 -0
  12. package/src/lib/components/site-plugin-connections-card.component.js +576 -0
  13. package/src/lib/components/site-plugin-connections-card.component.js.map +1 -0
  14. package/src/lib/declarations.console-server.d.ts +17 -0
  15. package/src/lib/declarations.console-server.js +41 -0
  16. package/src/lib/declarations.console-server.js.map +1 -0
  17. package/src/lib/declarations.js +11 -2
  18. package/src/lib/declarations.js.map +1 -1
  19. package/src/lib/declarations.server.js +68 -1
  20. package/src/lib/declarations.server.js.map +1 -1
  21. package/src/lib/hooks/use-plugin-connections-api.d.ts +89 -0
  22. package/src/lib/hooks/use-plugin-connections-api.js +132 -0
  23. package/src/lib/hooks/use-plugin-connections-api.js.map +1 -0
  24. package/src/lib/model/editor-connections.d.ts +46 -0
  25. package/src/lib/model/editor-connections.js +70 -0
  26. package/src/lib/model/editor-connections.js.map +1 -0
  27. package/src/lib/model/listing-price-floor.js +7 -4
  28. package/src/lib/model/listing-price-floor.js.map +1 -1
  29. package/src/lib/model/marketplace-publisher-permissions.d.ts +3 -1
  30. package/src/lib/model/marketplace-publisher-permissions.js +14 -0
  31. package/src/lib/model/marketplace-publisher-permissions.js.map +1 -1
  32. package/src/lib/model/marketplace-site-permissions.d.ts +9 -0
  33. package/src/lib/model/marketplace-site-permissions.js +29 -0
  34. package/src/lib/model/marketplace-site-permissions.js.map +1 -1
  35. package/src/lib/model/plugin-connection-card.d.ts +88 -0
  36. package/src/lib/model/plugin-connection-card.js +187 -0
  37. package/src/lib/model/plugin-connection-card.js.map +1 -0
  38. package/src/lib/model/plugin-connections.d.ts +172 -0
  39. package/src/lib/model/plugin-connections.js +119 -0
  40. package/src/lib/model/plugin-connections.js.map +1 -0
  41. package/src/lib/plugin.js +21 -1
  42. package/src/lib/plugin.js.map +1 -1
  43. package/src/lib/server/admin-reviews.js +18 -12
  44. package/src/lib/server/admin-reviews.js.map +1 -1
  45. package/src/lib/server/artifact-inventory.js +3 -1
  46. package/src/lib/server/artifact-inventory.js.map +1 -1
  47. package/src/lib/server/checkout.js +4 -0
  48. package/src/lib/server/checkout.js.map +1 -1
  49. package/src/lib/server/connections/cleanup.d.ts +84 -0
  50. package/src/lib/server/connections/cleanup.js +116 -0
  51. package/src/lib/server/connections/cleanup.js.map +1 -0
  52. package/src/lib/server/connections/config.d.ts +42 -0
  53. package/src/lib/server/connections/config.js +48 -0
  54. package/src/lib/server/connections/config.js.map +1 -0
  55. package/src/lib/server/connections/connection-store.d.ts +173 -0
  56. package/src/lib/server/connections/connection-store.js +331 -0
  57. package/src/lib/server/connections/connection-store.js.map +1 -0
  58. package/src/lib/server/connections/credentials-store.d.ts +71 -0
  59. package/src/lib/server/connections/credentials-store.js +78 -0
  60. package/src/lib/server/connections/credentials-store.js.map +1 -0
  61. package/src/lib/server/connections/erasure.d.ts +25 -0
  62. package/src/lib/server/connections/erasure.js +72 -0
  63. package/src/lib/server/connections/erasure.js.map +1 -0
  64. package/src/lib/server/connections/installed-connection.d.ts +64 -0
  65. package/src/lib/server/connections/installed-connection.js +92 -0
  66. package/src/lib/server/connections/installed-connection.js.map +1 -0
  67. package/src/lib/server/connections/oauth-state.d.ts +129 -0
  68. package/src/lib/server/connections/oauth-state.js +252 -0
  69. package/src/lib/server/connections/oauth-state.js.map +1 -0
  70. package/src/lib/server/connections/provider-http.d.ts +99 -0
  71. package/src/lib/server/connections/provider-http.js +253 -0
  72. package/src/lib/server/connections/provider-http.js.map +1 -0
  73. package/src/lib/server/connections/record-announce.d.ts +20 -0
  74. package/src/lib/server/connections/record-announce.js +53 -0
  75. package/src/lib/server/connections/record-announce.js.map +1 -0
  76. package/src/lib/server/connections/record-mapping.d.ts +63 -0
  77. package/src/lib/server/connections/record-mapping.js +151 -0
  78. package/src/lib/server/connections/record-mapping.js.map +1 -0
  79. package/src/lib/server/connections/record-routes.d.ts +17 -0
  80. package/src/lib/server/connections/record-routes.js +96 -0
  81. package/src/lib/server/connections/record-routes.js.map +1 -0
  82. package/src/lib/server/connections/revoke.d.ts +29 -0
  83. package/src/lib/server/connections/revoke.js +52 -0
  84. package/src/lib/server/connections/revoke.js.map +1 -0
  85. package/src/lib/server/connections/routes.d.ts +77 -0
  86. package/src/lib/server/connections/routes.js +706 -0
  87. package/src/lib/server/connections/routes.js.map +1 -0
  88. package/src/lib/server/connections/site-records-platform.d.ts +24 -0
  89. package/src/lib/server/connections/site-records-platform.js +94 -0
  90. package/src/lib/server/connections/site-records-platform.js.map +1 -0
  91. package/src/lib/server/connections/site-records.d.ts +95 -0
  92. package/src/lib/server/connections/site-records.js +221 -0
  93. package/src/lib/server/connections/site-records.js.map +1 -0
  94. package/src/lib/server/connections/sync.d.ts +110 -0
  95. package/src/lib/server/connections/sync.js +414 -0
  96. package/src/lib/server/connections/sync.js.map +1 -0
  97. package/src/lib/server/install-plugin.js +56 -6
  98. package/src/lib/server/install-plugin.js.map +1 -1
  99. package/src/lib/server/publish-plugin.js +5 -2
  100. package/src/lib/server/publish-plugin.js.map +1 -1
  101. package/src/lib/server/revenue-source.js +6 -5
  102. package/src/lib/server/revenue-source.js.map +1 -1
  103. package/src/lib/server.js +60 -0
  104. package/src/lib/server.js.map +1 -1
  105. package/src/lib/testing/memory-firestore.d.ts +34 -0
  106. package/src/lib/testing/memory-firestore.js +250 -0
  107. package/src/lib/testing/memory-firestore.js.map +1 -0
package/README.md CHANGED
@@ -20,6 +20,7 @@ Peer dependencies: `react`, `@mui/material`, `firebase`, `next`, and `firebase-a
20
20
  - `orgPluginInstalls`: the workspace's installed marketplace plugins, on its Plugins page.
21
21
  - `pluginInstallStatus`: whether an installation runs the newest installable version, on its installation page.
22
22
  - `pluginSiteSet`: which sites one installation applies to.
23
+ - `sitePluginConnections`: a site's connections to the services an installed plugin's manifest declares (connect, choose, sync, disconnect), on the site's page for that plugin.
23
24
  - `staffOverview`: recent purchases and the refund-reversal recovery queue, on the staff overview.
24
25
  - `templateGallery`: the "Marketplace templates" shelf of a site's template gallery, installing through `install-template`.
25
26
  - `templateInstallStatus`: "Update available" on a library row whose template it installed, re-installing through `install-template`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/plugins-marketplace",
3
- "version": "1.15.0",
3
+ "version": "1.15.2",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,19 +25,20 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.15.0",
29
- "@aglyn/aglyn-markdown-editor": "1.15.0",
30
- "@aglyn/shared-data-enums": "1.15.0",
31
- "@aglyn/shared-data-mdi": "1.15.0",
32
- "@aglyn/shared-ui-jsx": "1.15.0",
33
- "@aglyn/shared-ui-next": "1.15.0",
34
- "@aglyn/shared-ui-snackstack": "1.15.0",
35
- "@aglyn/shared-util-email": "1.15.0",
36
- "@aglyn/shared-util-http": "1.15.0",
37
- "@aglyn/shared-util-timestamp": "1.15.0",
38
- "@aglyn/tenant-data-admin": "1.15.0",
39
- "@aglyn/tenant-feature-instance": "1.15.0",
40
- "@aglyn/tenant-runtime": "1.15.0",
28
+ "@aglyn/aglyn": "1.15.2",
29
+ "@aglyn/aglyn-markdown-editor": "1.15.2",
30
+ "@aglyn/shared-data-enums": "1.15.2",
31
+ "@aglyn/shared-data-mdi": "1.15.2",
32
+ "@aglyn/shared-ui-jsx": "1.15.2",
33
+ "@aglyn/shared-ui-next": "1.15.2",
34
+ "@aglyn/shared-ui-snackstack": "1.15.2",
35
+ "@aglyn/shared-util-email": "1.15.2",
36
+ "@aglyn/shared-util-http": "1.15.2",
37
+ "@aglyn/shared-util-timestamp": "1.15.2",
38
+ "@aglyn/shared-util-tools": "1.15.2",
39
+ "@aglyn/tenant-data-admin": "1.15.2",
40
+ "@aglyn/tenant-feature-instance": "1.15.2",
41
+ "@aglyn/tenant-runtime": "1.15.2",
41
42
  "@swc/helpers": "0.5.23"
42
43
  },
43
44
  "peerDependencies": {
@@ -0,0 +1,37 @@
1
+ import { type EditorPluginConnections } from '@aglyn/aglyn/app-utils/plugin-connection-editor';
2
+ import { type Firestore } from 'firebase/firestore';
3
+ import { type ReactNode } from 'react';
4
+ export interface EditorConnectionsProviderProps {
5
+ orgId?: string | null;
6
+ hostId?: string | null;
7
+ children?: ReactNode;
8
+ /** Test seam: the console path. */
9
+ pathname?: string;
10
+ /** Test seam: reads the site's realm installs, pins and records. */
11
+ load?: (input: {
12
+ orgId: string;
13
+ hostId: string;
14
+ }) => Promise<EditorPluginConnections | undefined>;
15
+ }
16
+ type Fetcher = (path: string) => Promise<Response>;
17
+ /** Everything the editor is handed for one site, read once. */
18
+ export declare function loadEditorPluginConnections(deps: {
19
+ firestore: Firestore;
20
+ fetcher: Fetcher;
21
+ }, input: {
22
+ orgId: string;
23
+ hostId: string;
24
+ }): Promise<EditorPluginConnections | undefined>;
25
+ /**
26
+ * The site's declared connections, for the editor (AGL-4196): registered as a
27
+ * console provider, active only on the Besigner and Preview, where the
28
+ * canvas hands each signed element its record and the binding picker lists
29
+ * each record's fields by name. A refusal (no site key, no plan) or a failed
30
+ * read leaves the context empty: elements read `null`, as on a site that has
31
+ * not connected.
32
+ */
33
+ export declare function EditorConnectionsProvider(props: EditorConnectionsProviderProps): import("react").JSX.Element;
34
+ export declare namespace EditorConnectionsProvider {
35
+ var displayName: string;
36
+ }
37
+ export default EditorConnectionsProvider;
@@ -0,0 +1,161 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { jsx as _jsx } from "react/jsx-runtime";
18
+ import { EditorPluginConnectionsContext } from "@aglyn/aglyn/app-utils/plugin-connection-editor";
19
+ import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
20
+ import { useFirestore, useUser } from "@aglyn/tenant-feature-instance";
21
+ import { doc, getDoc } from "firebase/firestore";
22
+ import { usePathname } from "next/navigation";
23
+ import { useEffect, useState } from "react";
24
+ import { editorPluginConnections, syncingConnectionsOf } from "../model/editor-connections.js";
25
+ import { PLUGIN_CONNECTIONS_API_ROUTES } from "../model/plugin-connections.js";
26
+ /** The console's editor surfaces: the Besigner and Preview of any document. */ const EDITOR_PATH = /\/(besigner|preview)(\/|$)/;
27
+ /** A pin of `listingId` on the site, else the workspace's. */ async function readPin(firestore, hostId, orgId, listingId) {
28
+ for (const path of [
29
+ [
30
+ 'hosts',
31
+ hostId,
32
+ 'installs',
33
+ listingId
34
+ ],
35
+ [
36
+ 'orgs',
37
+ orgId,
38
+ 'installs',
39
+ listingId
40
+ ]
41
+ ]){
42
+ try {
43
+ const snapshot = await getDoc(doc(firestore, path[0], path[1], path[2], path[3]));
44
+ if (snapshot.exists()) return snapshot.data();
45
+ } catch (unused) {
46
+ // A pin this member cannot read is one the editor does without.
47
+ }
48
+ }
49
+ return null;
50
+ }
51
+ /** The record of one connection, or `null` quietly: no key, no plan, no sync yet. */ async function readRecord(fetcher, input) {
52
+ try {
53
+ var _record_fields;
54
+ const response = await fetcher(`/api/${PLUGIN_CONNECTIONS_API_ROUTES.record}?${new URLSearchParams(input).toString()}`);
55
+ if (!response.ok) return null;
56
+ const answer = await response.json().catch(()=>null);
57
+ const record = answer == null ? void 0 : answer.record;
58
+ if (!record || typeof record !== 'object') return null;
59
+ return {
60
+ fields: (_record_fields = record['fields']) != null ? _record_fields : {},
61
+ items: Array.isArray(record['items']) ? record['items'] : [],
62
+ syncedAtMs: Number(record['syncedAtMs']) || 0
63
+ };
64
+ } catch (unused) {
65
+ return null;
66
+ }
67
+ }
68
+ /** Everything the editor is handed for one site, read once. */ export async function loadEditorPluginConnections(deps, input) {
69
+ let installs;
70
+ try {
71
+ var _installs;
72
+ const response = await deps.fetcher(`/api/orgs/realm-plugins?${new URLSearchParams(input).toString()}`);
73
+ if (!response.ok) return undefined;
74
+ installs = ((_installs = (await response.json()).installs) != null ? _installs : []).filter((install)=>typeof (install == null ? void 0 : install.listingId) === 'string' && install.listingId);
75
+ } catch (unused) {
76
+ return undefined;
77
+ }
78
+ const listings = [];
79
+ for (const install of installs){
80
+ const pin = await readPin(deps.firestore, input.hostId, input.orgId, install.listingId);
81
+ const connections = syncingConnectionsOf(pin == null ? void 0 : pin.manifest);
82
+ if (connections.length) listings.push({
83
+ listingId: install.listingId,
84
+ connections
85
+ });
86
+ }
87
+ if (!listings.length) return undefined;
88
+ const records = {};
89
+ await Promise.all(listings.flatMap((listing)=>listing.connections.map(async (connection)=>{
90
+ var _records, _listing_listingId, _;
91
+ const record = await readRecord(deps.fetcher, {
92
+ hostId: input.hostId,
93
+ listingId: listing.listingId,
94
+ connectionId: connection.id
95
+ });
96
+ if (record) ((_ = (_records = records)[_listing_listingId = listing.listingId]) != null ? _ : _records[_listing_listingId] = {})[connection.id] = record;
97
+ })));
98
+ return editorPluginConnections({
99
+ installs,
100
+ listings,
101
+ records
102
+ });
103
+ }
104
+ /**
105
+ * The site's declared connections, for the editor (AGL-4196): registered as a
106
+ * console provider, active only on the Besigner and Preview, where the
107
+ * canvas hands each signed element its record and the binding picker lists
108
+ * each record's fields by name. A refusal (no site key, no plan) or a failed
109
+ * read leaves the context empty: elements read `null`, as on a site that has
110
+ * not connected.
111
+ */ export function EditorConnectionsProvider(props) {
112
+ var _ref, _props_pathname, _ref1;
113
+ const { orgId, hostId, children } = props;
114
+ const routerPath = usePathname();
115
+ const pathname = (_ref = (_props_pathname = props.pathname) != null ? _props_pathname : routerPath) != null ? _ref : '';
116
+ const firestore = useFirestore();
117
+ const { data: user } = useUser();
118
+ const [value, setValue] = useState(undefined);
119
+ const active = Boolean(orgId && hostId && EDITOR_PATH.test(pathname));
120
+ const load = props.load;
121
+ const uid = (_ref1 = user == null ? void 0 : user.uid) != null ? _ref1 : '';
122
+ useEffect(()=>{
123
+ if (!active || !orgId || !hostId || !load && !uid) {
124
+ setValue(undefined);
125
+ return undefined;
126
+ }
127
+ let live = true;
128
+ const read = load != null ? load : (input)=>loadEditorPluginConnections({
129
+ firestore,
130
+ fetcher: (path)=>authorizedFetch(user, path)
131
+ }, input);
132
+ void read({
133
+ orgId,
134
+ hostId
135
+ }).then((next)=>{
136
+ if (live) setValue(next);
137
+ }).catch(()=>{
138
+ if (live) setValue(undefined);
139
+ });
140
+ return ()=>{
141
+ live = false;
142
+ };
143
+ // `user` churns with token refreshes; the member, the site and the surface name the read.
144
+ // eslint-disable-next-line react-hooks/exhaustive-deps
145
+ }, [
146
+ active,
147
+ orgId,
148
+ hostId,
149
+ uid,
150
+ firestore,
151
+ load
152
+ ]);
153
+ return /*#__PURE__*/ _jsx(EditorPluginConnectionsContext.Provider, {
154
+ value: value,
155
+ children: children
156
+ });
157
+ }
158
+ EditorConnectionsProvider.displayName = 'EditorConnectionsProvider';
159
+ export default EditorConnectionsProvider;
160
+
161
+ //# sourceMappingURL=editor-connections-provider.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/editor-connections-provider.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 {\n EditorPluginConnectionsContext,\n type EditorPluginConnections,\n} from '@aglyn/aglyn/app-utils/plugin-connection-editor'\nimport type { PluginConnectionSiteRecord } from '@aglyn/aglyn/app-utils/plugin-connection-records'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useFirestore, useUser } from '@aglyn/tenant-feature-instance'\nimport { doc, getDoc, type Firestore } from 'firebase/firestore'\nimport { usePathname } from 'next/navigation'\nimport { useEffect, useState, type ReactNode } from 'react'\nimport {\n editorPluginConnections,\n syncingConnectionsOf,\n type EditorListingConnections,\n type EditorRealmInstall,\n} from '../model/editor-connections'\nimport { PLUGIN_CONNECTIONS_API_ROUTES } from '../model/plugin-connections'\n\n/** The console's editor surfaces: the Besigner and Preview of any document. */\nconst EDITOR_PATH = /\\/(besigner|preview)(\\/|$)/\n\nexport interface EditorConnectionsProviderProps {\n orgId?: string | null\n hostId?: string | null\n children?: ReactNode\n /** Test seam: the console path. */\n pathname?: string\n /** Test seam: reads the site's realm installs, pins and records. */\n load?: (input: { orgId: string; hostId: string }) => Promise<EditorPluginConnections | undefined>\n}\n\ntype Fetcher = (path: string) => Promise<Response>\n\n/** A pin of `listingId` on the site, else the workspace's. */\nasync function readPin(firestore: Firestore, hostId: string, orgId: string, listingId: string): Promise<unknown> {\n for (const path of [['hosts', hostId, 'installs', listingId], ['orgs', orgId, 'installs', listingId]] as const) {\n try {\n const snapshot = await getDoc(doc(firestore, path[0], path[1], path[2], path[3]))\n if (snapshot.exists()) return snapshot.data()\n } catch {\n // A pin this member cannot read is one the editor does without.\n }\n }\n return null\n}\n\n/** The record of one connection, or `null` quietly: no key, no plan, no sync yet. */\nasync function readRecord(\n fetcher: Fetcher,\n input: { hostId: string; listingId: string; connectionId: string },\n): Promise<PluginConnectionSiteRecord | null> {\n try {\n const response = await fetcher(`/api/${PLUGIN_CONNECTIONS_API_ROUTES.record}?${new URLSearchParams(input).toString()}`)\n if (!response.ok) return null\n const answer = (await response.json().catch(() => null)) as { record?: Record<string, unknown> | null } | null\n const record = answer?.record\n if (!record || typeof record !== 'object') return null\n return {\n fields: (record['fields'] ?? {}) as PluginConnectionSiteRecord['fields'],\n items: (Array.isArray(record['items']) ? record['items'] : []) as PluginConnectionSiteRecord['items'],\n syncedAtMs: Number(record['syncedAtMs']) || 0,\n }\n } catch {\n return null\n }\n}\n\n/** Everything the editor is handed for one site, read once. */\nexport async function loadEditorPluginConnections(\n deps: { firestore: Firestore; fetcher: Fetcher },\n input: { orgId: string; hostId: string },\n): Promise<EditorPluginConnections | undefined> {\n let installs: EditorRealmInstall[]\n try {\n const response = await deps.fetcher(`/api/orgs/realm-plugins?${new URLSearchParams(input).toString()}`)\n if (!response.ok) return undefined\n installs = (((await response.json()) as { installs?: EditorRealmInstall[] }).installs ?? []).filter(\n (install) => typeof install?.listingId === 'string' && install.listingId,\n )\n } catch {\n return undefined\n }\n const listings: EditorListingConnections[] = []\n for (const install of installs) {\n const pin = (await readPin(deps.firestore, input.hostId, input.orgId, install.listingId)) as { manifest?: unknown } | null\n const connections = syncingConnectionsOf(pin?.manifest)\n if (connections.length) listings.push({ listingId: install.listingId, connections })\n }\n if (!listings.length) return undefined\n const records: Record<string, Record<string, PluginConnectionSiteRecord>> = {}\n await Promise.all(\n listings.flatMap((listing) =>\n listing.connections.map(async (connection) => {\n const record = await readRecord(deps.fetcher, {\n hostId: input.hostId,\n listingId: listing.listingId,\n connectionId: connection.id,\n })\n if (record) (records[listing.listingId] ??= {})[connection.id] = record\n }),\n ),\n )\n return editorPluginConnections({ installs, listings, records })\n}\n\n/**\n * The site's declared connections, for the editor (AGL-4196): registered as a\n * console provider, active only on the Besigner and Preview, where the\n * canvas hands each signed element its record and the binding picker lists\n * each record's fields by name. A refusal (no site key, no plan) or a failed\n * read leaves the context empty: elements read `null`, as on a site that has\n * not connected.\n */\nexport function EditorConnectionsProvider(props: EditorConnectionsProviderProps) {\n const { orgId, hostId, children } = props\n const routerPath = usePathname()\n const pathname = props.pathname ?? routerPath ?? ''\n const firestore = useFirestore()\n const { data: user } = useUser()\n const [value, setValue] = useState<EditorPluginConnections | undefined>(undefined)\n const active = Boolean(orgId && hostId && EDITOR_PATH.test(pathname))\n const load = props.load\n const uid = (user as { uid?: string } | null | undefined)?.uid ?? ''\n\n useEffect(() => {\n if (!active || !orgId || !hostId || (!load && !uid)) {\n setValue(undefined)\n return undefined\n }\n let live = true\n const read =\n load ?? ((input) => loadEditorPluginConnections({ firestore, fetcher: (path) => authorizedFetch(user as never, path) }, input))\n void read({ orgId, hostId })\n .then((next) => {\n if (live) setValue(next)\n })\n .catch(() => {\n if (live) setValue(undefined)\n })\n return () => {\n live = false\n }\n // `user` churns with token refreshes; the member, the site and the surface name the read.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [active, orgId, hostId, uid, firestore, load])\n\n return <EditorPluginConnectionsContext.Provider value={value}>{children}</EditorPluginConnectionsContext.Provider>\n}\nEditorConnectionsProvider.displayName = 'EditorConnectionsProvider'\n\nexport default EditorConnectionsProvider\n"],"names":["EditorPluginConnectionsContext","authorizedFetch","useFirestore","useUser","doc","getDoc","usePathname","useEffect","useState","editorPluginConnections","syncingConnectionsOf","PLUGIN_CONNECTIONS_API_ROUTES","EDITOR_PATH","readPin","firestore","hostId","orgId","listingId","path","snapshot","exists","data","readRecord","fetcher","input","record","response","URLSearchParams","toString","ok","answer","json","catch","fields","items","Array","isArray","syncedAtMs","Number","loadEditorPluginConnections","deps","installs","undefined","filter","install","listings","pin","connections","manifest","length","push","records","Promise","all","flatMap","listing","map","connection","connectionId","id","EditorConnectionsProvider","props","children","routerPath","pathname","user","value","setValue","active","Boolean","test","load","uid","live","read","then","next","Provider","displayName"],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { type PluginConnectionCredentialsApi } from '../hooks/use-plugin-connections-api';
2
+ export interface ListingConnectionCredentialsCardProps {
3
+ listingId: string;
4
+ /**
5
+ * The viewer's resolved permissions, from the shell. Setting and clearing
6
+ * the credentials need "Set a listing's connection credentials", as the
7
+ * route does; without it the card stays, its buttons off and the
8
+ * permission named. An absent key reads as allowed: the route still decides.
9
+ */
10
+ permissions?: Record<string, boolean | undefined>;
11
+ /** Test seam: the routes. The signed-in member's by default. */
12
+ api?: PluginConnectionCredentialsApi;
13
+ /** Test seam: the console origin the redirect URI is built on. */
14
+ origin?: string;
15
+ }
16
+ /**
17
+ * THE PUBLISHER'S OAUTH APP FOR EACH DECLARED CONNECTION (AGL-4196).
18
+ *
19
+ * A plugin that declares a connection signs site owners in with its
20
+ * publisher's own OAuth app, and the platform holds that app's client ID and
21
+ * secret sealed. This card sets, replaces and clears them. It is
22
+ * write-only: the route never answers either value, so all it can ever show
23
+ * is "Set" or "Not set", and the redirect URI the publisher registers at the
24
+ * provider.
25
+ *
26
+ * Drawn for the listing's publisher only, and only for a listing whose latest
27
+ * version declares a connection: the route lists none otherwise.
28
+ */
29
+ export declare function ListingConnectionCredentialsCard(props: ListingConnectionCredentialsCardProps): import("react").JSX.Element;
30
+ export declare namespace ListingConnectionCredentialsCard {
31
+ var displayName: string;
32
+ }
33
+ export default ListingConnectionCredentialsCard;
@@ -0,0 +1,318 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
18
+ import { pluginDocsHelp } from "@aglyn/aglyn/app-utils/docs-help";
19
+ import { CardDisplay, useConfirmationContext } from "@aglyn/shared-ui-jsx";
20
+ import { BlockedControl } from "@aglyn/shared-ui-jsx/components/blocked-control.component";
21
+ import { CopyField } from "@aglyn/shared-ui-jsx/components/copy-field.component";
22
+ import { StatusChip } from "@aglyn/shared-ui-jsx/components/status-chip.component";
23
+ import { useSnackbar } from "@aglyn/shared-ui-snackstack";
24
+ import { Alert, Button, CircularProgress, Stack, TextField, Typography } from "@mui/material";
25
+ import { useCallback, useEffect, useState } from "react";
26
+ import { PluginConnectionRouteError, usePluginConnectionCredentialsApi } from "../hooks/use-plugin-connections-api.js";
27
+ import { formatConnectionTime, PLUGIN_CONNECTIONS_NOT_CONFIGURED_TEXT, pluginConnectionRedirectUri } from "../model/plugin-connection-card.js";
28
+ import { PLUGIN_CONNECTIONS_CREDENTIALS_PERMISSION, pluginConnectionsPermissionRefusal } from "../model/plugin-connections.js";
29
+ const HELP = pluginDocsHelp('publisherHandbook', {
30
+ anchor: '#connection-credentials'
31
+ });
32
+ /**
33
+ * THE PUBLISHER'S OAUTH APP FOR EACH DECLARED CONNECTION (AGL-4196).
34
+ *
35
+ * A plugin that declares a connection signs site owners in with its
36
+ * publisher's own OAuth app, and the platform holds that app's client ID and
37
+ * secret sealed. This card sets, replaces and clears them. It is
38
+ * write-only: the route never answers either value, so all it can ever show
39
+ * is "Set" or "Not set", and the redirect URI the publisher registers at the
40
+ * provider.
41
+ *
42
+ * Drawn for the listing's publisher only, and only for a listing whose latest
43
+ * version declares a connection: the route lists none otherwise.
44
+ */ export function ListingConnectionCredentialsCard(props) {
45
+ var _props_api;
46
+ const { listingId, permissions } = props;
47
+ const routes = usePluginConnectionCredentialsApi();
48
+ const api = (_props_api = props.api) != null ? _props_api : routes;
49
+ const [load, setLoad] = useState({
50
+ status: 'loading'
51
+ });
52
+ const canSet = (permissions == null ? void 0 : permissions[PLUGIN_CONNECTIONS_CREDENTIALS_PERMISSION]) !== false;
53
+ const redirectUri = pluginConnectionRedirectUri(props.origin);
54
+ const refresh = useCallback(async ()=>{
55
+ try {
56
+ setLoad({
57
+ status: 'ready',
58
+ answer: await api.read(listingId)
59
+ });
60
+ } catch (error) {
61
+ var _ref;
62
+ const refusal = error instanceof PluginConnectionRouteError ? error : null;
63
+ setLoad({
64
+ status: 'error',
65
+ message: (_ref = refusal == null ? void 0 : refusal.message) != null ? _ref : 'The connection credentials could not be read. Try again.',
66
+ refused: (refusal == null ? void 0 : refusal.reason) === 'permission'
67
+ });
68
+ }
69
+ }, [
70
+ api,
71
+ listingId
72
+ ]);
73
+ useEffect(()=>{
74
+ if (listingId) void refresh();
75
+ }, [
76
+ listingId,
77
+ refresh
78
+ ]);
79
+ if (load.status === 'loading') return null;
80
+ if (load.status === 'error') {
81
+ return /*#__PURE__*/ _jsx(CardDisplay, {
82
+ header: 'Connection credentials',
83
+ help: HELP,
84
+ contentGutterX: true,
85
+ contentGutterY: true,
86
+ children: /*#__PURE__*/ _jsx(Alert, {
87
+ severity: load.refused ? 'info' : 'error',
88
+ children: load.message
89
+ })
90
+ });
91
+ }
92
+ const { answer } = load;
93
+ if (!answer.credentials.length) return null;
94
+ return /*#__PURE__*/ _jsx(Stack, {
95
+ spacing: 3,
96
+ children: answer.credentials.map((entry)=>/*#__PURE__*/ _jsx(ConnectionCredentials, {
97
+ listingId: listingId,
98
+ entry: entry,
99
+ configured: answer.configured,
100
+ canSet: canSet,
101
+ redirectUri: redirectUri,
102
+ api: api,
103
+ onAnswer: (next)=>setLoad({
104
+ status: 'ready',
105
+ answer: next
106
+ })
107
+ }, entry.connectionId))
108
+ });
109
+ }
110
+ ListingConnectionCredentialsCard.displayName = 'ListingConnectionCredentialsCard';
111
+ export default ListingConnectionCredentialsCard;
112
+ /** One declared connection's OAuth app: set or not, and the form that replaces it. */ function ConnectionCredentials(props) {
113
+ const { listingId, entry, configured, canSet, redirectUri, api, onAnswer } = props;
114
+ const { enqueueSnackbar } = useSnackbar();
115
+ const { confirm } = useConfirmationContext();
116
+ const [editing, setEditing] = useState(false);
117
+ const [clientId, setClientId] = useState('');
118
+ const [clientSecret, setClientSecret] = useState('');
119
+ const [busy, setBusy] = useState(null);
120
+ const [error, setError] = useState(null);
121
+ const refusal = pluginConnectionsPermissionRefusal(PLUGIN_CONNECTIONS_CREDENTIALS_PERMISSION);
122
+ const blockedReason = !canSet ? refusal : !configured ? PLUGIN_CONNECTIONS_NOT_CONFIGURED_TEXT : null;
123
+ const close = ()=>{
124
+ setEditing(false);
125
+ setClientId('');
126
+ setClientSecret('');
127
+ setError(null);
128
+ };
129
+ const save = async ()=>{
130
+ setBusy('save');
131
+ setError(null);
132
+ try {
133
+ const answer = await api.save({
134
+ listingId,
135
+ connectionId: entry.connectionId,
136
+ clientId: clientId.trim(),
137
+ clientSecret: clientSecret.trim()
138
+ });
139
+ close();
140
+ onAnswer(answer);
141
+ enqueueSnackbar(`${entry.label} credentials saved.`, {
142
+ variant: 'success'
143
+ });
144
+ } catch (failure) {
145
+ setError(failure instanceof Error ? failure.message : String(failure));
146
+ } finally{
147
+ setBusy(null);
148
+ }
149
+ };
150
+ const clear = async ()=>{
151
+ const ok = await Promise.resolve(confirm({
152
+ title: `Clear the ${entry.label} credentials?`,
153
+ description: 'Sites already connected stop syncing, and nobody can connect until you set new ones.',
154
+ confirmationText: 'Clear',
155
+ confirmationButtonProps: {
156
+ color: 'warning',
157
+ variant: 'contained'
158
+ }
159
+ })).then(()=>true).catch(()=>false);
160
+ if (!ok) return;
161
+ setBusy('clear');
162
+ setError(null);
163
+ try {
164
+ onAnswer(await api.clear({
165
+ listingId,
166
+ connectionId: entry.connectionId
167
+ }));
168
+ enqueueSnackbar(`${entry.label} credentials cleared.`, {
169
+ variant: 'success'
170
+ });
171
+ } catch (failure) {
172
+ setError(failure instanceof Error ? failure.message : String(failure));
173
+ } finally{
174
+ setBusy(null);
175
+ }
176
+ };
177
+ const action = editing ? /*#__PURE__*/ _jsxs(Stack, {
178
+ direction: "row",
179
+ spacing: 1,
180
+ children: [
181
+ /*#__PURE__*/ _jsx(Button, {
182
+ color: "inherit",
183
+ disabled: busy !== null,
184
+ onClick: close,
185
+ children: 'Cancel'
186
+ }),
187
+ /*#__PURE__*/ _jsx(Button, {
188
+ variant: "contained",
189
+ disabled: busy !== null || !clientId.trim() || !clientSecret.trim(),
190
+ onClick: ()=>void save(),
191
+ children: busy === 'save' ? 'Saving…' : 'Save'
192
+ })
193
+ ]
194
+ }) : /*#__PURE__*/ _jsxs(Stack, {
195
+ direction: "row",
196
+ spacing: 1,
197
+ children: [
198
+ /*#__PURE__*/ _jsx(BlockedControl, {
199
+ blocked: Boolean(blockedReason),
200
+ reason: blockedReason != null ? blockedReason : '',
201
+ children: /*#__PURE__*/ _jsx(Button, {
202
+ variant: entry.set ? 'outlined' : 'contained',
203
+ disabled: busy !== null,
204
+ onClick: ()=>setEditing(true),
205
+ children: entry.set ? 'Replace' : 'Set credentials'
206
+ })
207
+ }),
208
+ entry.set ? /*#__PURE__*/ _jsx(BlockedControl, {
209
+ blocked: !canSet,
210
+ reason: refusal,
211
+ children: /*#__PURE__*/ _jsx(Button, {
212
+ color: "warning",
213
+ disabled: busy !== null,
214
+ onClick: ()=>void clear(),
215
+ children: busy === 'clear' ? 'Clearing…' : 'Clear'
216
+ })
217
+ }) : null
218
+ ]
219
+ });
220
+ return /*#__PURE__*/ _jsx(CardDisplay, {
221
+ header: `Connection credentials: ${entry.label}`,
222
+ help: HELP,
223
+ contentGutterX: true,
224
+ contentGutterY: true,
225
+ HeaderProps: {
226
+ action
227
+ },
228
+ children: /*#__PURE__*/ _jsxs(Stack, {
229
+ spacing: 2,
230
+ children: [
231
+ /*#__PURE__*/ _jsxs(Stack, {
232
+ direction: "row",
233
+ spacing: 1,
234
+ sx: {
235
+ alignItems: 'center',
236
+ flexWrap: 'wrap',
237
+ gap: 1
238
+ },
239
+ children: [
240
+ /*#__PURE__*/ _jsx(StatusChip, {
241
+ label: entry.set ? 'Set' : 'Not set',
242
+ tone: entry.set ? 'success' : 'neutral',
243
+ "data-testid": "plugin-connection-credentials-status"
244
+ }),
245
+ entry.set && entry.updatedAtMs !== null ? /*#__PURE__*/ _jsx(Typography, {
246
+ variant: "body2",
247
+ color: "text.secondary",
248
+ children: `Last changed ${formatConnectionTime(entry.updatedAtMs)}`
249
+ }) : null
250
+ ]
251
+ }),
252
+ /*#__PURE__*/ _jsx(Typography, {
253
+ variant: "body2",
254
+ color: "text.secondary",
255
+ children: entry.set ? 'Sites sign in to this service with your OAuth app. The client ID and secret are never shown again; replace them to change them.' : 'Until these are set, sites that install your plugin cannot connect this service.'
256
+ }),
257
+ !configured ? /*#__PURE__*/ _jsx(Alert, {
258
+ severity: "info",
259
+ children: PLUGIN_CONNECTIONS_NOT_CONFIGURED_TEXT
260
+ }) : null,
261
+ !canSet ? /*#__PURE__*/ _jsx(Alert, {
262
+ severity: "info",
263
+ children: refusal
264
+ }) : null,
265
+ /*#__PURE__*/ _jsx(CopyField, {
266
+ label: "Redirect URI",
267
+ value: redirectUri,
268
+ helperText: "Register this address at the provider as your OAuth app's redirect URI, exactly as shown."
269
+ }),
270
+ editing ? /*#__PURE__*/ _jsxs(Stack, {
271
+ spacing: 2,
272
+ children: [
273
+ /*#__PURE__*/ _jsx(TextField, {
274
+ label: "Client ID",
275
+ size: "small",
276
+ value: clientId,
277
+ autoComplete: "off",
278
+ disabled: busy !== null,
279
+ onChange: (event)=>setClientId(event.target.value),
280
+ slotProps: {
281
+ htmlInput: {
282
+ spellCheck: false,
283
+ 'data-1p-ignore': true
284
+ }
285
+ }
286
+ }),
287
+ /*#__PURE__*/ _jsx(TextField, {
288
+ label: "Client secret",
289
+ size: "small",
290
+ type: "password",
291
+ value: clientSecret,
292
+ autoComplete: "new-password",
293
+ disabled: busy !== null,
294
+ onChange: (event)=>setClientSecret(event.target.value),
295
+ helperText: "Stored sealed. It cannot be read back, by you or anyone else.",
296
+ slotProps: {
297
+ htmlInput: {
298
+ spellCheck: false,
299
+ 'data-1p-ignore': true
300
+ }
301
+ }
302
+ })
303
+ ]
304
+ }) : null,
305
+ busy === 'clear' ? /*#__PURE__*/ _jsx(CircularProgress, {
306
+ size: 18
307
+ }) : null,
308
+ error ? /*#__PURE__*/ _jsx(Alert, {
309
+ severity: "error",
310
+ onClose: ()=>setError(null),
311
+ children: error
312
+ }) : null
313
+ ]
314
+ })
315
+ });
316
+ }
317
+
318
+ //# sourceMappingURL=listing-connection-credentials-card.component.js.map