@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.
- package/LICENSE +201 -0
- package/README.md +7 -0
- package/package.json +51 -0
- package/src/index.d.ts +19 -0
- package/src/index.js +20 -0
- package/src/index.js.map +1 -0
- package/src/lib/components/artifact-update-dialog.component.d.ts +27 -0
- package/src/lib/components/artifact-update-dialog.component.js +244 -0
- package/src/lib/components/artifact-update-dialog.component.js.map +1 -0
- package/src/lib/components/host-plugins-card.component.d.ts +23 -0
- package/src/lib/components/host-plugins-card.component.js +566 -0
- package/src/lib/components/host-plugins-card.component.js.map +1 -0
- package/src/lib/components/listing-content.component.d.ts +57 -0
- package/src/lib/components/listing-content.component.js +1880 -0
- package/src/lib/components/listing-content.component.js.map +1 -0
- package/src/lib/components/listing-image.component.d.ts +46 -0
- package/src/lib/components/listing-image.component.js +38 -0
- package/src/lib/components/listing-image.component.js.map +1 -0
- package/src/lib/components/listing-reviews.component.d.ts +22 -0
- package/src/lib/components/listing-reviews.component.js +419 -0
- package/src/lib/components/listing-reviews.component.js.map +1 -0
- package/src/lib/components/marketplace-browse.component.d.ts +38 -0
- package/src/lib/components/marketplace-browse.component.js +802 -0
- package/src/lib/components/marketplace-browse.component.js.map +1 -0
- package/src/lib/components/plugin-site-set-panel.component.d.ts +24 -0
- package/src/lib/components/plugin-site-set-panel.component.js +137 -0
- package/src/lib/components/plugin-site-set-panel.component.js.map +1 -0
- package/src/lib/components/plugin-site-set.component.d.ts +43 -0
- package/src/lib/components/plugin-site-set.component.js +425 -0
- package/src/lib/components/plugin-site-set.component.js.map +1 -0
- package/src/lib/components/rating-input.component.d.ts +3 -0
- package/src/lib/components/rating-input.component.js +47 -0
- package/src/lib/components/rating-input.component.js.map +1 -0
- package/src/lib/components/report-target.component.d.ts +36 -0
- package/src/lib/components/report-target.component.js +181 -0
- package/src/lib/components/report-target.component.js.map +1 -0
- package/src/lib/components/uninstall-impact-dialog.component.d.ts +40 -0
- package/src/lib/components/uninstall-impact-dialog.component.js +244 -0
- package/src/lib/components/uninstall-impact-dialog.component.js.map +1 -0
- package/src/lib/constants/bundle-common.d.ts +8 -0
- package/src/lib/constants/bundle-common.js +9 -0
- package/src/lib/constants/bundle-common.js.map +1 -0
- package/src/lib/hooks/use-artifact-update.d.ts +41 -0
- package/src/lib/hooks/use-artifact-update.js +169 -0
- package/src/lib/hooks/use-artifact-update.js.map +1 -0
- package/src/lib/hooks/use-marketplace-actions.d.ts +37 -0
- package/src/lib/hooks/use-marketplace-actions.js +523 -0
- package/src/lib/hooks/use-marketplace-actions.js.map +1 -0
- package/src/lib/model/artifact-merge.d.ts +154 -0
- package/src/lib/model/artifact-merge.js +228 -0
- package/src/lib/model/artifact-merge.js.map +1 -0
- package/src/lib/model/index.d.ts +43 -0
- package/src/lib/model/index.js +41 -0
- package/src/lib/model/index.js.map +1 -0
- package/src/lib/model/marketplace-props.d.ts +57 -0
- package/src/lib/model/marketplace-props.js +441 -0
- package/src/lib/model/marketplace-props.js.map +1 -0
- package/src/lib/model/marketplace.d.ts +833 -0
- package/src/lib/model/marketplace.js +929 -0
- package/src/lib/model/marketplace.js.map +1 -0
- package/src/lib/model/rating-field.d.ts +24 -0
- package/src/lib/model/rating-field.js +33 -0
- package/src/lib/model/rating-field.js.map +1 -0
- package/src/lib/plugin.d.ts +33 -0
- package/src/lib/plugin.js +86 -0
- package/src/lib/plugin.js.map +1 -0
- package/src/lib/server/billing-webhook.d.ts +25 -0
- package/src/lib/server/billing-webhook.js +1062 -0
- package/src/lib/server/billing-webhook.js.map +1 -0
- package/src/lib/server/checkout.d.ts +76 -0
- package/src/lib/server/checkout.js +477 -0
- package/src/lib/server/checkout.js.map +1 -0
- package/src/lib/server/connect.d.ts +24 -0
- package/src/lib/server/connect.js +205 -0
- package/src/lib/server/connect.js.map +1 -0
- package/src/lib/server/install-dataset-schema.d.ts +32 -0
- package/src/lib/server/install-dataset-schema.js +255 -0
- package/src/lib/server/install-dataset-schema.js.map +1 -0
- package/src/lib/server/install-email-starter.d.ts +65 -0
- package/src/lib/server/install-email-starter.js +279 -0
- package/src/lib/server/install-email-starter.js.map +1 -0
- package/src/lib/server/install-email-template.d.ts +34 -0
- package/src/lib/server/install-email-template.js +252 -0
- package/src/lib/server/install-email-template.js.map +1 -0
- package/src/lib/server/install-layout.d.ts +29 -0
- package/src/lib/server/install-layout.js +323 -0
- package/src/lib/server/install-layout.js.map +1 -0
- package/src/lib/server/install-pin-counts.d.ts +173 -0
- package/src/lib/server/install-pin-counts.js +248 -0
- package/src/lib/server/install-pin-counts.js.map +1 -0
- package/src/lib/server/install-plugin.d.ts +18 -0
- package/src/lib/server/install-plugin.js +405 -0
- package/src/lib/server/install-plugin.js.map +1 -0
- package/src/lib/server/install-template.d.ts +37 -0
- package/src/lib/server/install-template.js +348 -0
- package/src/lib/server/install-template.js.map +1 -0
- package/src/lib/server/install-theme.d.ts +42 -0
- package/src/lib/server/install-theme.js +313 -0
- package/src/lib/server/install-theme.js.map +1 -0
- package/src/lib/server/install.d.ts +27 -0
- package/src/lib/server/install.js +276 -0
- package/src/lib/server/install.js.map +1 -0
- package/src/lib/server/listing-versions.d.ts +26 -0
- package/src/lib/server/listing-versions.js +378 -0
- package/src/lib/server/listing-versions.js.map +1 -0
- package/src/lib/server/preview-image.d.ts +37 -0
- package/src/lib/server/preview-image.js +216 -0
- package/src/lib/server/preview-image.js.map +1 -0
- package/src/lib/server/provenance.d.ts +100 -0
- package/src/lib/server/provenance.js +118 -0
- package/src/lib/server/provenance.js.map +1 -0
- package/src/lib/server/publish-dataset-schema.d.ts +31 -0
- package/src/lib/server/publish-dataset-schema.js +202 -0
- package/src/lib/server/publish-dataset-schema.js.map +1 -0
- package/src/lib/server/publish-email-starter.d.ts +44 -0
- package/src/lib/server/publish-email-starter.js +231 -0
- package/src/lib/server/publish-email-starter.js.map +1 -0
- package/src/lib/server/publish-email-template.d.ts +33 -0
- package/src/lib/server/publish-email-template.js +211 -0
- package/src/lib/server/publish-email-template.js.map +1 -0
- package/src/lib/server/publish-layout.d.ts +33 -0
- package/src/lib/server/publish-layout.js +243 -0
- package/src/lib/server/publish-layout.js.map +1 -0
- package/src/lib/server/publish-plugin.d.ts +18 -0
- package/src/lib/server/publish-plugin.js +485 -0
- package/src/lib/server/publish-plugin.js.map +1 -0
- package/src/lib/server/publish-preconditions.d.ts +86 -0
- package/src/lib/server/publish-preconditions.js +160 -0
- package/src/lib/server/publish-preconditions.js.map +1 -0
- package/src/lib/server/publish-template.d.ts +27 -0
- package/src/lib/server/publish-template.js +215 -0
- package/src/lib/server/publish-template.js.map +1 -0
- package/src/lib/server/publish-theme.d.ts +38 -0
- package/src/lib/server/publish-theme.js +186 -0
- package/src/lib/server/publish-theme.js.map +1 -0
- package/src/lib/server/publish.d.ts +26 -0
- package/src/lib/server/publish.js +204 -0
- package/src/lib/server/publish.js.map +1 -0
- package/src/lib/server/publisher-profile-save.d.ts +30 -0
- package/src/lib/server/publisher-profile-save.js +226 -0
- package/src/lib/server/publisher-profile-save.js.map +1 -0
- package/src/lib/server/publisher-profile.d.ts +86 -0
- package/src/lib/server/publisher-profile.js +116 -0
- package/src/lib/server/publisher-profile.js.map +1 -0
- package/src/lib/server/purchase-entitlement.d.ts +83 -0
- package/src/lib/server/purchase-entitlement.js +118 -0
- package/src/lib/server/purchase-entitlement.js.map +1 -0
- package/src/lib/server/report.d.ts +32 -0
- package/src/lib/server/report.js +101 -0
- package/src/lib/server/report.js.map +1 -0
- package/src/lib/server/reviews.d.ts +41 -0
- package/src/lib/server/reviews.js +240 -0
- package/src/lib/server/reviews.js.map +1 -0
- package/src/lib/server/update-artifact.d.ts +19 -0
- package/src/lib/server/update-artifact.js +569 -0
- package/src/lib/server/update-artifact.js.map +1 -0
- package/src/lib/server/verification-request.d.ts +31 -0
- package/src/lib/server/verification-request.js +173 -0
- package/src/lib/server/verification-request.js.map +1 -0
- package/src/lib/server/version-stats.d.ts +202 -0
- package/src/lib/server/version-stats.js +305 -0
- package/src/lib/server/version-stats.js.map +1 -0
- package/src/lib/server.d.ts +30 -0
- package/src/lib/server.js +95 -0
- 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;
|