@aglyn/plugins-marketplace 1.0.0-beta.143

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 (165) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +7 -0
  3. package/package.json +51 -0
  4. package/src/index.d.ts +19 -0
  5. package/src/index.js +20 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/components/artifact-update-dialog.component.d.ts +27 -0
  8. package/src/lib/components/artifact-update-dialog.component.js +244 -0
  9. package/src/lib/components/artifact-update-dialog.component.js.map +1 -0
  10. package/src/lib/components/host-plugins-card.component.d.ts +23 -0
  11. package/src/lib/components/host-plugins-card.component.js +566 -0
  12. package/src/lib/components/host-plugins-card.component.js.map +1 -0
  13. package/src/lib/components/listing-content.component.d.ts +57 -0
  14. package/src/lib/components/listing-content.component.js +1880 -0
  15. package/src/lib/components/listing-content.component.js.map +1 -0
  16. package/src/lib/components/listing-image.component.d.ts +46 -0
  17. package/src/lib/components/listing-image.component.js +38 -0
  18. package/src/lib/components/listing-image.component.js.map +1 -0
  19. package/src/lib/components/listing-reviews.component.d.ts +22 -0
  20. package/src/lib/components/listing-reviews.component.js +419 -0
  21. package/src/lib/components/listing-reviews.component.js.map +1 -0
  22. package/src/lib/components/marketplace-browse.component.d.ts +38 -0
  23. package/src/lib/components/marketplace-browse.component.js +802 -0
  24. package/src/lib/components/marketplace-browse.component.js.map +1 -0
  25. package/src/lib/components/plugin-site-set-panel.component.d.ts +24 -0
  26. package/src/lib/components/plugin-site-set-panel.component.js +137 -0
  27. package/src/lib/components/plugin-site-set-panel.component.js.map +1 -0
  28. package/src/lib/components/plugin-site-set.component.d.ts +43 -0
  29. package/src/lib/components/plugin-site-set.component.js +425 -0
  30. package/src/lib/components/plugin-site-set.component.js.map +1 -0
  31. package/src/lib/components/rating-input.component.d.ts +3 -0
  32. package/src/lib/components/rating-input.component.js +47 -0
  33. package/src/lib/components/rating-input.component.js.map +1 -0
  34. package/src/lib/components/report-target.component.d.ts +36 -0
  35. package/src/lib/components/report-target.component.js +181 -0
  36. package/src/lib/components/report-target.component.js.map +1 -0
  37. package/src/lib/components/uninstall-impact-dialog.component.d.ts +40 -0
  38. package/src/lib/components/uninstall-impact-dialog.component.js +244 -0
  39. package/src/lib/components/uninstall-impact-dialog.component.js.map +1 -0
  40. package/src/lib/constants/bundle-common.d.ts +8 -0
  41. package/src/lib/constants/bundle-common.js +9 -0
  42. package/src/lib/constants/bundle-common.js.map +1 -0
  43. package/src/lib/hooks/use-artifact-update.d.ts +41 -0
  44. package/src/lib/hooks/use-artifact-update.js +169 -0
  45. package/src/lib/hooks/use-artifact-update.js.map +1 -0
  46. package/src/lib/hooks/use-marketplace-actions.d.ts +37 -0
  47. package/src/lib/hooks/use-marketplace-actions.js +523 -0
  48. package/src/lib/hooks/use-marketplace-actions.js.map +1 -0
  49. package/src/lib/model/artifact-merge.d.ts +154 -0
  50. package/src/lib/model/artifact-merge.js +228 -0
  51. package/src/lib/model/artifact-merge.js.map +1 -0
  52. package/src/lib/model/index.d.ts +43 -0
  53. package/src/lib/model/index.js +41 -0
  54. package/src/lib/model/index.js.map +1 -0
  55. package/src/lib/model/marketplace-props.d.ts +57 -0
  56. package/src/lib/model/marketplace-props.js +441 -0
  57. package/src/lib/model/marketplace-props.js.map +1 -0
  58. package/src/lib/model/marketplace.d.ts +833 -0
  59. package/src/lib/model/marketplace.js +929 -0
  60. package/src/lib/model/marketplace.js.map +1 -0
  61. package/src/lib/model/rating-field.d.ts +24 -0
  62. package/src/lib/model/rating-field.js +33 -0
  63. package/src/lib/model/rating-field.js.map +1 -0
  64. package/src/lib/plugin.d.ts +33 -0
  65. package/src/lib/plugin.js +86 -0
  66. package/src/lib/plugin.js.map +1 -0
  67. package/src/lib/server/billing-webhook.d.ts +25 -0
  68. package/src/lib/server/billing-webhook.js +1062 -0
  69. package/src/lib/server/billing-webhook.js.map +1 -0
  70. package/src/lib/server/checkout.d.ts +76 -0
  71. package/src/lib/server/checkout.js +477 -0
  72. package/src/lib/server/checkout.js.map +1 -0
  73. package/src/lib/server/connect.d.ts +24 -0
  74. package/src/lib/server/connect.js +205 -0
  75. package/src/lib/server/connect.js.map +1 -0
  76. package/src/lib/server/install-dataset-schema.d.ts +32 -0
  77. package/src/lib/server/install-dataset-schema.js +255 -0
  78. package/src/lib/server/install-dataset-schema.js.map +1 -0
  79. package/src/lib/server/install-email-starter.d.ts +65 -0
  80. package/src/lib/server/install-email-starter.js +279 -0
  81. package/src/lib/server/install-email-starter.js.map +1 -0
  82. package/src/lib/server/install-email-template.d.ts +34 -0
  83. package/src/lib/server/install-email-template.js +252 -0
  84. package/src/lib/server/install-email-template.js.map +1 -0
  85. package/src/lib/server/install-layout.d.ts +29 -0
  86. package/src/lib/server/install-layout.js +323 -0
  87. package/src/lib/server/install-layout.js.map +1 -0
  88. package/src/lib/server/install-pin-counts.d.ts +173 -0
  89. package/src/lib/server/install-pin-counts.js +248 -0
  90. package/src/lib/server/install-pin-counts.js.map +1 -0
  91. package/src/lib/server/install-plugin.d.ts +18 -0
  92. package/src/lib/server/install-plugin.js +405 -0
  93. package/src/lib/server/install-plugin.js.map +1 -0
  94. package/src/lib/server/install-template.d.ts +37 -0
  95. package/src/lib/server/install-template.js +348 -0
  96. package/src/lib/server/install-template.js.map +1 -0
  97. package/src/lib/server/install-theme.d.ts +42 -0
  98. package/src/lib/server/install-theme.js +313 -0
  99. package/src/lib/server/install-theme.js.map +1 -0
  100. package/src/lib/server/install.d.ts +27 -0
  101. package/src/lib/server/install.js +276 -0
  102. package/src/lib/server/install.js.map +1 -0
  103. package/src/lib/server/listing-versions.d.ts +26 -0
  104. package/src/lib/server/listing-versions.js +378 -0
  105. package/src/lib/server/listing-versions.js.map +1 -0
  106. package/src/lib/server/preview-image.d.ts +37 -0
  107. package/src/lib/server/preview-image.js +216 -0
  108. package/src/lib/server/preview-image.js.map +1 -0
  109. package/src/lib/server/provenance.d.ts +100 -0
  110. package/src/lib/server/provenance.js +118 -0
  111. package/src/lib/server/provenance.js.map +1 -0
  112. package/src/lib/server/publish-dataset-schema.d.ts +31 -0
  113. package/src/lib/server/publish-dataset-schema.js +202 -0
  114. package/src/lib/server/publish-dataset-schema.js.map +1 -0
  115. package/src/lib/server/publish-email-starter.d.ts +44 -0
  116. package/src/lib/server/publish-email-starter.js +231 -0
  117. package/src/lib/server/publish-email-starter.js.map +1 -0
  118. package/src/lib/server/publish-email-template.d.ts +33 -0
  119. package/src/lib/server/publish-email-template.js +211 -0
  120. package/src/lib/server/publish-email-template.js.map +1 -0
  121. package/src/lib/server/publish-layout.d.ts +33 -0
  122. package/src/lib/server/publish-layout.js +243 -0
  123. package/src/lib/server/publish-layout.js.map +1 -0
  124. package/src/lib/server/publish-plugin.d.ts +18 -0
  125. package/src/lib/server/publish-plugin.js +485 -0
  126. package/src/lib/server/publish-plugin.js.map +1 -0
  127. package/src/lib/server/publish-preconditions.d.ts +86 -0
  128. package/src/lib/server/publish-preconditions.js +160 -0
  129. package/src/lib/server/publish-preconditions.js.map +1 -0
  130. package/src/lib/server/publish-template.d.ts +27 -0
  131. package/src/lib/server/publish-template.js +215 -0
  132. package/src/lib/server/publish-template.js.map +1 -0
  133. package/src/lib/server/publish-theme.d.ts +38 -0
  134. package/src/lib/server/publish-theme.js +186 -0
  135. package/src/lib/server/publish-theme.js.map +1 -0
  136. package/src/lib/server/publish.d.ts +26 -0
  137. package/src/lib/server/publish.js +204 -0
  138. package/src/lib/server/publish.js.map +1 -0
  139. package/src/lib/server/publisher-profile-save.d.ts +30 -0
  140. package/src/lib/server/publisher-profile-save.js +226 -0
  141. package/src/lib/server/publisher-profile-save.js.map +1 -0
  142. package/src/lib/server/publisher-profile.d.ts +86 -0
  143. package/src/lib/server/publisher-profile.js +116 -0
  144. package/src/lib/server/publisher-profile.js.map +1 -0
  145. package/src/lib/server/purchase-entitlement.d.ts +83 -0
  146. package/src/lib/server/purchase-entitlement.js +118 -0
  147. package/src/lib/server/purchase-entitlement.js.map +1 -0
  148. package/src/lib/server/report.d.ts +32 -0
  149. package/src/lib/server/report.js +101 -0
  150. package/src/lib/server/report.js.map +1 -0
  151. package/src/lib/server/reviews.d.ts +41 -0
  152. package/src/lib/server/reviews.js +240 -0
  153. package/src/lib/server/reviews.js.map +1 -0
  154. package/src/lib/server/update-artifact.d.ts +19 -0
  155. package/src/lib/server/update-artifact.js +569 -0
  156. package/src/lib/server/update-artifact.js.map +1 -0
  157. package/src/lib/server/verification-request.d.ts +31 -0
  158. package/src/lib/server/verification-request.js +173 -0
  159. package/src/lib/server/verification-request.js.map +1 -0
  160. package/src/lib/server/version-stats.d.ts +202 -0
  161. package/src/lib/server/version-stats.js +305 -0
  162. package/src/lib/server/version-stats.js.map +1 -0
  163. package/src/lib/server.d.ts +30 -0
  164. package/src/lib/server.js +95 -0
  165. package/src/lib/server.js.map +1 -0
@@ -0,0 +1,276 @@
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 { checkEntitlement, createResourceUid, decodeStoredNodes, encodeStoredNodes } from "@aglyn/aglyn/server";
18
+ import { firebaseAdmin, getOrgForHost } from "@aglyn/tenant-data-admin";
19
+ import { resolveOrgPermissions } from "@aglyn/tenant-runtime/org-permissions";
20
+ import { isPrivateListing } from "../model/marketplace.js";
21
+ import { readPublishedProps } from "../model/marketplace-props.js";
22
+ import { canActAsPublisher } from "./publisher-profile.js";
23
+ import { hasDivergedFromBase, recordInstallProvenance } from "./provenance.js";
24
+ import { requirePurchase } from "./purchase-entitlement.js";
25
+ import { recordVersionMove } from "./version-stats.js";
26
+ /**
27
+ * Installs (or updates) a marketplace listing into a host (AGL-44/46).
28
+ * Server-side because version snapshots are not client-readable — paid
29
+ * content would otherwise be free to anyone who read the subcollection.
30
+ * Access: the listing is free, the caller bought it (webhook-written
31
+ * purchase record), or the caller published it. Installing copies the
32
+ * latest version into `hosts/{hostId}/components` with `marketplace` source
33
+ * metadata; re-installing updates the same component doc in place.
34
+ */ export const installHandler = async (req, res)=>{
35
+ var _ref, _ref1, _req_headers_authorization;
36
+ var _req_body, _req_body1;
37
+ if (req.method !== 'POST') {
38
+ return res.status(405).json({
39
+ error: 'Method not allowed'
40
+ });
41
+ }
42
+ const listingId = String((_ref = (_req_body = req.body) == null ? void 0 : _req_body.listingId) != null ? _ref : '');
43
+ const hostId = String((_ref1 = (_req_body1 = req.body) == null ? void 0 : _req_body1.hostId) != null ? _ref1 : '');
44
+ if (!listingId || !hostId) {
45
+ return res.status(400).json({
46
+ error: 'Missing listingId or hostId'
47
+ });
48
+ }
49
+ const authorization = String((_req_headers_authorization = req.headers.authorization) != null ? _req_headers_authorization : '');
50
+ const idToken = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : undefined;
51
+ if (!idToken) return res.status(401).json({
52
+ error: 'Unauthenticated'
53
+ });
54
+ try {
55
+ var _hostSnapshot_get, _listing_priceUsd, _membership_orgId, _version_nodes, _existing_docs__get;
56
+ var _this;
57
+ const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken);
58
+ // Org-role permission gate (AGL-238).
59
+ const membership = await resolveOrgPermissions(decoded.uid, {
60
+ hostId
61
+ });
62
+ if (!membership.permissions.installPlugins) {
63
+ return res.status(403).json({
64
+ error: 'Your organization role does not allow installing from the marketplace'
65
+ });
66
+ }
67
+ const firestore = firebaseAdmin.app().firestore();
68
+ const hostRef = firestore.collection('hosts').doc(hostId);
69
+ const hostSnapshot = await hostRef.get();
70
+ if (!hostSnapshot.exists) {
71
+ return res.status(404).json({
72
+ error: 'Unknown site'
73
+ });
74
+ }
75
+ const memberRole = ((_hostSnapshot_get = hostSnapshot.get('memberRoles')) != null ? _hostSnapshot_get : {})[decoded.uid];
76
+ if (memberRole !== 'admin' && memberRole !== 'editor') {
77
+ return res.status(403).json({
78
+ error: 'Not a site admin or editor'
79
+ });
80
+ }
81
+ // The SAME gate the console's own create path applies to this exact
82
+ // document (AGL-2072). `/api/hosts/resources` declares
83
+ // `reusableComponent: { collection: 'components', entitlement:
84
+ // 'reusableComponents' }` and says why: a reusable component renders on
85
+ // the LIVE SITE, so the Starter+ gate has to be server-enforced rather
86
+ // than merely hidden in the console (AGL-473). This route writes a
87
+ // byte-for-byte equivalent doc into `hosts/{hostId}/components` and asked
88
+ // nothing — so a free org installed from the marketplace and got the
89
+ // working feature its own console would have refused.
90
+ //
91
+ // The assumption that hid it is written down at
92
+ // `apps/console/app/api/hosts/import/route.ts:329-331`:
93
+ // "`reusableComponents` is a BOOLEAN entitlement, true on every plan that
94
+ // can reach here". True of import, which is Pro+. False here — any plan
95
+ // reaches the marketplace, and free listings cost nothing to install.
96
+ //
97
+ // Ahead of `requirePurchase`, deliberately, and matching
98
+ // `install-dataset-schema.ts:89`: refusing on plan AFTER taking someone's
99
+ // money for the listing would be the worse order. The whole org doc is
100
+ // read (`getOrgDoc` projects nothing), so `resolveEffectivePlan` sees the
101
+ // subscription status and a LAPSED org is refused exactly as a free one
102
+ // is — the gate is re-asked per install, never inherited from whatever
103
+ // plan was in force when an earlier copy was installed.
104
+ const org = (_this = await getOrgForHost(hostId)) == null ? void 0 : _this.org;
105
+ if (!checkEntitlement(org, 'reusableComponents')) {
106
+ return res.status(403).json({
107
+ error: 'Reusable components require a Starter plan or higher'
108
+ });
109
+ }
110
+ const listingRef = firestore.collection('marketplaceListings').doc(listingId);
111
+ const listingSnapshot = await listingRef.get();
112
+ const listing = listingSnapshot.data();
113
+ if (!listing || listing.deletedAt || // Staff takedown blocks new installs on EVERY artifact type
114
+ // (AGL-2290). AGL-948 extended takedown past plugins in the browse
115
+ // predicate and in `resolveMarketplacePluginVersion`, but the gate that
116
+ // decides whether content is HANDED OVER was only ever added to
117
+ // `install-plugin.ts`. So a component, theme, template, layout, email
118
+ // template or dataset schema that staff had taken down stayed
119
+ // installable by anyone holding its listing id — which makes takedown a
120
+ // suggestion for six of the seven artifact types.
121
+ //
122
+ // No owner exemption, matching `install-plugin.ts`: a takedown is a
123
+ // moderation decision about the artifact, not about who is asking.
124
+ listing.hiddenAt) {
125
+ return res.status(404).json({
126
+ error: 'Unknown listing'
127
+ });
128
+ }
129
+ const priceUsd = Number((_listing_priceUsd = listing.priceUsd) != null ? _listing_priceUsd : 0);
130
+ // The publisher installs their own listing for free. Org-owned now
131
+ // (AGL-652), so this is a role check — comparing a uid to an org id
132
+ // would never match and would charge publishers for their own work.
133
+ const ownsListing = await canActAsPublisher(firestore, decoded.uid, listing.profileId);
134
+ // Private listings install ONLY for the owning org (AGL-2290).
135
+ //
136
+ // `install-plugin.ts` has carried this since AGL-968; the other six never
137
+ // did, so a private component, theme, template, layout, email template or
138
+ // dataset schema was installable by anyone who knew its listing id. Browse
139
+ // hides them and the detail page 404s, but neither is a control — the
140
+ // route is.
141
+ if (isPrivateListing(listing) && !ownsListing) {
142
+ return res.status(404).json({
143
+ error: 'Unknown listing'
144
+ });
145
+ }
146
+ // A FULLY refunded purchase no longer entitles (AGL-1546). The gate this
147
+ // route once inlined is now shared (AGL-1699): it was the only one of
148
+ // eight paid-content paths that had learned the refund rule.
149
+ const unpaid = await requirePurchase({
150
+ firestore,
151
+ buyerUid: decoded.uid,
152
+ // THE ORG THE LICENCE HAS TO COVER (AGL-2331). `membership.orgId` is
153
+ // resolved server-side from the caller's own membership by the
154
+ // permission gate above — never a request-body field — so this is the
155
+ // workspace the install actually lands in, and the only one a purchase
156
+ // can entitle here.
157
+ buyerOrgId: (_membership_orgId = membership.orgId) != null ? _membership_orgId : '',
158
+ listingId,
159
+ priceUsd,
160
+ ownsListing
161
+ });
162
+ if (unpaid) return res.status(402).json(unpaid);
163
+ const versionSnapshot = await listingRef.collection('versions').doc(String(listing.latestVersion)).get();
164
+ const version = versionSnapshot.data();
165
+ if (!(version == null ? void 0 : version.nodes) || !(version == null ? void 0 : version.rootId)) {
166
+ return res.status(500).json({
167
+ error: 'Listing version missing'
168
+ });
169
+ }
170
+ // The properties the tree binds to (AGL-2933), held to the sanitizer again
171
+ // on the way in. `undefined` for a version published before they were
172
+ // carried, which leaves a re-installed copy's own properties in place.
173
+ const props = readPublishedProps(version.props);
174
+ const componentsRef = hostRef.collection('components');
175
+ const existing = await componentsRef.where('marketplace.listingId', '==', listingId).limit(1).get();
176
+ const componentRef = existing.empty ? componentsRef.doc(createResourceUid()) : existing.docs[0].ref;
177
+ // Re-installing over a copy the workspace has EDITED is the silent
178
+ // destructive overwrite AGL-1018 exists to stop. The install still happens
179
+ // when it is asked for explicitly (`mode: 'replace'`, which the update
180
+ // dialog sends after showing what goes), but it is never the default.
181
+ if (!existing.empty) {
182
+ var _req_body2;
183
+ const diverged = await hasDivergedFromBase({
184
+ firestore,
185
+ sha256: existing.docs[0].get('installedFrom.sha256'),
186
+ current: {
187
+ rootId: existing.docs[0].get('rootId'),
188
+ // DECODED, because the comparison is by value: the base snapshot
189
+ // recorded at install is a node map and `hasDivergedFromBase`
190
+ // hashes a stable stringification of both sides. A stored `Bytes`
191
+ // matches no map, so an untouched copy would read as edited and
192
+ // every re-install would 409 (AGL-1151).
193
+ nodes: decodeStoredNodes(existing.docs[0].get('nodes'))
194
+ }
195
+ });
196
+ if (diverged && ((_req_body2 = req.body) == null ? void 0 : _req_body2.mode) !== 'replace') {
197
+ return res.status(409).json({
198
+ error: 'Your copy of this component has been edited since it was ' + 'installed. Review the update to see what would change.',
199
+ diverged: true
200
+ });
201
+ }
202
+ }
203
+ const now = firebaseAdmin.firestore.FieldValue.serverTimestamp();
204
+ // Provenance + base snapshot (AGL-1015). The snapshot holds only the
205
+ // vendored tree — the display name and description come from the listing
206
+ // and the user may rename their copy, so including them would make every
207
+ // rename read as a diverged component.
208
+ const provenance = await recordInstallProvenance({
209
+ firestore,
210
+ listingId,
211
+ listing,
212
+ version: listing.latestVersion,
213
+ artifactType: 'component',
214
+ content: {
215
+ rootId: version.rootId,
216
+ nodes: version.nodes
217
+ }
218
+ });
219
+ await componentRef.set(_extends({
220
+ displayName: listing.displayName
221
+ }, listing.description && {
222
+ description: listing.description
223
+ }, {
224
+ rootId: version.rootId,
225
+ // Compressed at rest, matching what a promote writes onto this same
226
+ // document (AGL-1151). The provenance base above keeps the decoded
227
+ // map: it is compared by value, never rendered.
228
+ nodes: Buffer.from(encodeStoredNodes((_version_nodes = version.nodes) != null ? _version_nodes : {}))
229
+ }, props && {
230
+ props
231
+ }, {
232
+ deletedAt: null,
233
+ marketplace: {
234
+ listingId,
235
+ profileId: listing.profileId,
236
+ version: listing.latestVersion
237
+ },
238
+ installedFrom: provenance.installedFrom
239
+ }, existing.empty && {
240
+ createdAt: now
241
+ }, {
242
+ updatedAt: now
243
+ }), {
244
+ merge: true
245
+ });
246
+ // Per-version tally (AGL-1036): a re-install moves this copy off whatever
247
+ // version it was on and onto the latest.
248
+ await recordVersionMove({
249
+ firestore,
250
+ listingRef,
251
+ artifactType: 'component',
252
+ from: existing.empty ? null : (_existing_docs__get = existing.docs[0].get('installedFrom.version')) != null ? _existing_docs__get : existing.docs[0].get('marketplace.version'),
253
+ to: listing.latestVersion
254
+ });
255
+ // First installs count toward the browse "Most installed" sort
256
+ // (AGL-95); updates don't inflate it.
257
+ if (existing.empty) {
258
+ await listingRef.update({
259
+ installCount: firebaseAdmin.firestore.FieldValue.increment(1)
260
+ }).catch(()=>undefined);
261
+ }
262
+ return res.status(200).json({
263
+ installed: true,
264
+ updated: !existing.empty,
265
+ version: listing.latestVersion,
266
+ baseStored: provenance.baseStored
267
+ });
268
+ } catch (error) {
269
+ console.error(error);
270
+ return res.status(500).json({
271
+ error: 'Install failed'
272
+ });
273
+ }
274
+ };
275
+
276
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/server/install.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 checkEntitlement,\n createResourceUid,\n decodeStoredNodes,\n encodeStoredNodes,\n} from '@aglyn/aglyn/server'\nimport { firebaseAdmin, getOrgForHost } from '@aglyn/tenant-data-admin'\nimport { type PluginApiHandler } from '@aglyn/aglyn/server'\nimport { resolveOrgPermissions } from '@aglyn/tenant-runtime/org-permissions'\nimport { isPrivateListing } from '../model/marketplace'\nimport { readPublishedProps } from '../model/marketplace-props'\nimport { canActAsPublisher } from './publisher-profile'\nimport { hasDivergedFromBase, recordInstallProvenance } from './provenance'\nimport { requirePurchase } from './purchase-entitlement'\nimport { recordVersionMove } from './version-stats'\n\n/**\n * Installs (or updates) a marketplace listing into a host (AGL-44/46).\n * Server-side because version snapshots are not client-readable — paid\n * content would otherwise be free to anyone who read the subcollection.\n * Access: the listing is free, the caller bought it (webhook-written\n * purchase record), or the caller published it. Installing copies the\n * latest version into `hosts/{hostId}/components` with `marketplace` source\n * metadata; re-installing updates the same component doc in place.\n */\nexport const installHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n const listingId = String(req.body?.listingId ?? '')\n const hostId = String(req.body?.hostId ?? '')\n if (!listingId || !hostId) {\n return res.status(400).json({ error: 'Missing listingId or hostId' })\n }\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n\n try {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n // Org-role permission gate (AGL-238).\n const membership = await resolveOrgPermissions(decoded.uid, { hostId })\n if (!membership.permissions.installPlugins) {\n return res.status(403).json({\n error: 'Your organization role does not allow installing from the marketplace',\n })\n }\n const firestore = firebaseAdmin.app().firestore()\n\n const hostRef = firestore.collection('hosts').doc(hostId)\n const hostSnapshot = await hostRef.get()\n if (!hostSnapshot.exists) {\n return res.status(404).json({ error: 'Unknown site' })\n }\n const memberRole = (hostSnapshot.get('memberRoles') ?? {})[decoded.uid]\n if (memberRole !== 'admin' && memberRole !== 'editor') {\n return res.status(403).json({ error: 'Not a site admin or editor' })\n }\n\n // The SAME gate the console's own create path applies to this exact\n // document (AGL-2072). `/api/hosts/resources` declares\n // `reusableComponent: { collection: 'components', entitlement:\n // 'reusableComponents' }` and says why: a reusable component renders on\n // the LIVE SITE, so the Starter+ gate has to be server-enforced rather\n // than merely hidden in the console (AGL-473). This route writes a\n // byte-for-byte equivalent doc into `hosts/{hostId}/components` and asked\n // nothing — so a free org installed from the marketplace and got the\n // working feature its own console would have refused.\n //\n // The assumption that hid it is written down at\n // `apps/console/app/api/hosts/import/route.ts:329-331`:\n // \"`reusableComponents` is a BOOLEAN entitlement, true on every plan that\n // can reach here\". True of import, which is Pro+. False here — any plan\n // reaches the marketplace, and free listings cost nothing to install.\n //\n // Ahead of `requirePurchase`, deliberately, and matching\n // `install-dataset-schema.ts:89`: refusing on plan AFTER taking someone's\n // money for the listing would be the worse order. The whole org doc is\n // read (`getOrgDoc` projects nothing), so `resolveEffectivePlan` sees the\n // subscription status and a LAPSED org is refused exactly as a free one\n // is — the gate is re-asked per install, never inherited from whatever\n // plan was in force when an earlier copy was installed.\n const org = (await getOrgForHost(hostId))?.org\n if (!checkEntitlement(org as any, 'reusableComponents')) {\n return res.status(403).json({\n error: 'Reusable components require a Starter plan or higher',\n })\n }\n\n const listingRef = firestore\n .collection('marketplaceListings')\n .doc(listingId)\n const listingSnapshot = await listingRef.get()\n const listing = listingSnapshot.data() as any\n if (\n !listing ||\n listing.deletedAt ||\n // Staff takedown blocks new installs on EVERY artifact type\n // (AGL-2290). AGL-948 extended takedown past plugins in the browse\n // predicate and in `resolveMarketplacePluginVersion`, but the gate that\n // decides whether content is HANDED OVER was only ever added to\n // `install-plugin.ts`. So a component, theme, template, layout, email\n // template or dataset schema that staff had taken down stayed\n // installable by anyone holding its listing id — which makes takedown a\n // suggestion for six of the seven artifact types.\n //\n // No owner exemption, matching `install-plugin.ts`: a takedown is a\n // moderation decision about the artifact, not about who is asking.\n listing.hiddenAt\n ) {\n return res.status(404).json({ error: 'Unknown listing' })\n }\n\n const priceUsd = Number(listing.priceUsd ?? 0)\n // The publisher installs their own listing for free. Org-owned now\n // (AGL-652), so this is a role check — comparing a uid to an org id\n // would never match and would charge publishers for their own work.\n const ownsListing = await canActAsPublisher(\n firestore,\n decoded.uid,\n listing.profileId,\n )\n // Private listings install ONLY for the owning org (AGL-2290).\n //\n // `install-plugin.ts` has carried this since AGL-968; the other six never\n // did, so a private component, theme, template, layout, email template or\n // dataset schema was installable by anyone who knew its listing id. Browse\n // hides them and the detail page 404s, but neither is a control — the\n // route is.\n if (isPrivateListing(listing) && !ownsListing) {\n return res.status(404).json({ error: 'Unknown listing' })\n }\n // A FULLY refunded purchase no longer entitles (AGL-1546). The gate this\n // route once inlined is now shared (AGL-1699): it was the only one of\n // eight paid-content paths that had learned the refund rule.\n const unpaid = await requirePurchase({\n firestore,\n buyerUid: decoded.uid,\n // THE ORG THE LICENCE HAS TO COVER (AGL-2331). `membership.orgId` is\n // resolved server-side from the caller's own membership by the\n // permission gate above — never a request-body field — so this is the\n // workspace the install actually lands in, and the only one a purchase\n // can entitle here.\n buyerOrgId: membership.orgId ?? '',\n listingId,\n priceUsd,\n ownsListing,\n })\n if (unpaid) return res.status(402).json(unpaid)\n\n const versionSnapshot = await listingRef\n .collection('versions')\n .doc(String(listing.latestVersion))\n .get()\n const version = versionSnapshot.data() as any\n if (!version?.nodes || !version?.rootId) {\n return res.status(500).json({ error: 'Listing version missing' })\n }\n\n // The properties the tree binds to (AGL-2933), held to the sanitizer again\n // on the way in. `undefined` for a version published before they were\n // carried, which leaves a re-installed copy's own properties in place.\n const props = readPublishedProps(version.props)\n\n const componentsRef = hostRef.collection('components')\n const existing = await componentsRef\n .where('marketplace.listingId', '==', listingId)\n .limit(1)\n .get()\n const componentRef = existing.empty\n ? componentsRef.doc(createResourceUid())\n : existing.docs[0].ref\n // Re-installing over a copy the workspace has EDITED is the silent\n // destructive overwrite AGL-1018 exists to stop. The install still happens\n // when it is asked for explicitly (`mode: 'replace'`, which the update\n // dialog sends after showing what goes), but it is never the default.\n if (!existing.empty) {\n const diverged = await hasDivergedFromBase({\n firestore,\n sha256: existing.docs[0].get('installedFrom.sha256'),\n current: {\n rootId: existing.docs[0].get('rootId'),\n // DECODED, because the comparison is by value: the base snapshot\n // recorded at install is a node map and `hasDivergedFromBase`\n // hashes a stable stringification of both sides. A stored `Bytes`\n // matches no map, so an untouched copy would read as edited and\n // every re-install would 409 (AGL-1151).\n nodes: decodeStoredNodes(existing.docs[0].get('nodes')),\n },\n })\n if (diverged && req.body?.mode !== 'replace') {\n return res.status(409).json({\n error:\n 'Your copy of this component has been edited since it was ' +\n 'installed. Review the update to see what would change.',\n diverged: true,\n })\n }\n }\n const now = firebaseAdmin.firestore.FieldValue.serverTimestamp()\n // Provenance + base snapshot (AGL-1015). The snapshot holds only the\n // vendored tree — the display name and description come from the listing\n // and the user may rename their copy, so including them would make every\n // rename read as a diverged component.\n const provenance = await recordInstallProvenance({\n firestore,\n listingId,\n listing,\n version: listing.latestVersion,\n artifactType: 'component',\n content: { rootId: version.rootId, nodes: version.nodes },\n })\n await componentRef.set(\n {\n displayName: listing.displayName,\n ...(listing.description && { description: listing.description }),\n rootId: version.rootId,\n // Compressed at rest, matching what a promote writes onto this same\n // document (AGL-1151). The provenance base above keeps the decoded\n // map: it is compared by value, never rendered.\n nodes: Buffer.from(encodeStoredNodes(version.nodes ?? {})!),\n // Beside the tree, as a publish writes them onto this document. The\n // first version the console mints for this component copies them\n // from here (AGL-2932).\n ...(props && { props }),\n deletedAt: null,\n marketplace: {\n listingId,\n profileId: listing.profileId,\n version: listing.latestVersion,\n },\n installedFrom: provenance.installedFrom,\n ...(existing.empty && { createdAt: now }),\n updatedAt: now,\n },\n { merge: true },\n )\n // Per-version tally (AGL-1036): a re-install moves this copy off whatever\n // version it was on and onto the latest.\n await recordVersionMove({\n firestore,\n listingRef,\n artifactType: 'component',\n from: existing.empty\n ? null\n : (existing.docs[0].get('installedFrom.version') ??\n existing.docs[0].get('marketplace.version')),\n to: listing.latestVersion,\n })\n // First installs count toward the browse \"Most installed\" sort\n // (AGL-95); updates don't inflate it.\n if (existing.empty) {\n await listingRef\n .update({\n installCount: firebaseAdmin.firestore.FieldValue.increment(1),\n })\n .catch(() => undefined)\n }\n return res.status(200).json({\n installed: true,\n updated: !existing.empty,\n version: listing.latestVersion,\n baseStored: provenance.baseStored,\n })\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Install failed' })\n }\n}\n"],"names":["checkEntitlement","createResourceUid","decodeStoredNodes","encodeStoredNodes","firebaseAdmin","getOrgForHost","resolveOrgPermissions","isPrivateListing","readPublishedProps","canActAsPublisher","hasDivergedFromBase","recordInstallProvenance","requirePurchase","recordVersionMove","installHandler","req","res","method","status","json","error","listingId","String","body","hostId","authorization","headers","idToken","startsWith","slice","length","undefined","hostSnapshot","listing","membership","version","existing","decoded","app","auth","verifyIdToken","uid","permissions","installPlugins","firestore","hostRef","collection","doc","get","exists","memberRole","org","listingRef","listingSnapshot","data","deletedAt","hiddenAt","priceUsd","Number","ownsListing","profileId","unpaid","buyerUid","buyerOrgId","orgId","versionSnapshot","latestVersion","nodes","rootId","props","componentsRef","where","limit","componentRef","empty","docs","ref","diverged","sha256","current","mode","now","FieldValue","serverTimestamp","provenance","artifactType","content","set","displayName","description","Buffer","from","marketplace","installedFrom","createdAt","updatedAt","merge","to","update","installCount","increment","catch","installed","updated","baseStored","console"],"mappings":"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"}
@@ -0,0 +1,26 @@
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 PluginApiHandler } from '@aglyn/aglyn/server';
18
+ /**
19
+ * Public version history for a plugin listing (AGL-431). The
20
+ * `pluginVersions` docs are server-only (they carry publish internals),
21
+ * but their changelog/trust/compat fields are exactly what a buyer needs
22
+ * on the detail page — so this handler exposes THAT subset and nothing
23
+ * else (no objectPath, no signature; sha stays out simply because the
24
+ * client has no use for it).
25
+ */
26
+ export declare const listingVersionsHandler: PluginApiHandler;