@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,523 @@
|
|
|
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 { _ as _extends } from "@swc/helpers/_/_extends";
|
|
18
|
+
import { lockdownRefusalText, parseLockdownRefusal } from "@aglyn/aglyn";
|
|
19
|
+
import { readGaClientId } from "@aglyn/aglyn/app-utils/analytics-events";
|
|
20
|
+
import { useLoading } from "@aglyn/shared-ui-jsx";
|
|
21
|
+
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
22
|
+
import { useCallback, useRef } from "react";
|
|
23
|
+
import { useUser } from "@aglyn/tenant-feature-instance";
|
|
24
|
+
import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
|
|
25
|
+
import { listingArtifactType } from "../model/marketplace.js";
|
|
26
|
+
/**
|
|
27
|
+
* Each artifact type has its own installer route (AGL-672): routing
|
|
28
|
+
* everything to the component one silently installed the wrong thing or
|
|
29
|
+
* 404'd. `listingArtifactType` tolerates the legacy `type`/`kind`
|
|
30
|
+
* discriminators (AGL-654).
|
|
31
|
+
*/ function endpointForArtifact(artifactType) {
|
|
32
|
+
switch(artifactType){
|
|
33
|
+
case 'template':
|
|
34
|
+
return 'marketplace/install-template';
|
|
35
|
+
case 'layout':
|
|
36
|
+
return 'marketplace/install-layout';
|
|
37
|
+
case 'plugin':
|
|
38
|
+
return 'marketplace/install-plugin';
|
|
39
|
+
case 'datasetSchema':
|
|
40
|
+
return 'marketplace/install-dataset-schema';
|
|
41
|
+
case 'emailTemplate':
|
|
42
|
+
return 'marketplace/install-email-template';
|
|
43
|
+
case 'emailStarter':
|
|
44
|
+
return 'marketplace/install-email-starter';
|
|
45
|
+
case 'theme':
|
|
46
|
+
return 'marketplace/install-theme';
|
|
47
|
+
default:
|
|
48
|
+
return 'marketplace/install';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Artifact types whose install deliberately does NOT touch the running site
|
|
53
|
+
* (AGL-669/671/657) — the copy must not imply otherwise. Templates and layouts
|
|
54
|
+
* land in the Templates library; an email template lands as an inactive
|
|
55
|
+
* version the owner still has to activate.
|
|
56
|
+
*/ function landingMessage(artifactType, displayName) {
|
|
57
|
+
switch(artifactType){
|
|
58
|
+
case 'template':
|
|
59
|
+
case 'layout':
|
|
60
|
+
return `Saved "${displayName}" to your Templates — nothing is live until ` + 'you use it.';
|
|
61
|
+
case 'emailTemplate':
|
|
62
|
+
return `Saved "${displayName}" as a draft version — activate it in the ` + 'email designer to start sending it.';
|
|
63
|
+
case 'emailStarter':
|
|
64
|
+
// A copy, and saying so is the point: the publisher cannot reach it
|
|
65
|
+
// again, and nothing goes out until a campaign is sent from it.
|
|
66
|
+
return `Added "${displayName}" to your Email templates as your own copy — ` + 'edit it freely, nothing is sent until you send a campaign.';
|
|
67
|
+
case 'datasetSchema':
|
|
68
|
+
return `Created "${displayName}" as a new, empty dataset.`;
|
|
69
|
+
case 'theme':
|
|
70
|
+
// The one type that DOES change the running site on install, so it gets
|
|
71
|
+
// the opposite of the reassurance the others get — plus the way back,
|
|
72
|
+
// because a repainted site is alarming if you cannot see how to undo it.
|
|
73
|
+
return `Applied "${displayName}" to this site. Setup → Theme has a way back ` + 'to your previous theme.';
|
|
74
|
+
default:
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* What to tell someone whose install will never be three-way mergeable
|
|
80
|
+
* (AGL-2339).
|
|
81
|
+
*
|
|
82
|
+
* Every install route returns `baseStored`, and `provenance.ts` says what a
|
|
83
|
+
* false one means: *"a snapshot that cannot be written leaves `baseStored:
|
|
84
|
+
* false` and an artifact that reports itself as not updatable"*. It reported
|
|
85
|
+
* it to NOBODY — zero `.tsx` readers across all seven routes.
|
|
86
|
+
*
|
|
87
|
+
* The consequence is silent and expensive. `hasDivergedFromBase` needs the
|
|
88
|
+
* base snapshot to tell the publisher's change from the user's; without one
|
|
89
|
+
* an update can only overwrite. So a user who customizes a large artifact —
|
|
90
|
+
* one whose content exceeded `ARTIFACT_BASE_MAX_BYTES`, or whose snapshot
|
|
91
|
+
* write failed — LOSES those changes on the next update, having been told
|
|
92
|
+
* nothing at install time. The update dialog does explain itself when the
|
|
93
|
+
* moment arrives (`preview.mergeable` renders `preview.reason`), but by then
|
|
94
|
+
* the choice is take-the-update-or-stay-behind, not "keep a copy first".
|
|
95
|
+
*
|
|
96
|
+
* Persistent rather than a timed toast: it is advice to act on before
|
|
97
|
+
* editing, and it is the only moment this is knowable in advance.
|
|
98
|
+
*/ function baseMissingWarning(displayName) {
|
|
99
|
+
return `"${displayName}" is too large to keep a reference copy of, so a future ` + 'update will REPLACE it rather than merge. Export or note any changes ' + 'you make to it.';
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Shared install/buy handlers for marketplace listings (AGL-95): used by the
|
|
103
|
+
* browse grid and the listing detail page. Install copies the pinned
|
|
104
|
+
* version snapshot server-side (/api/marketplace/install); buy opens a
|
|
105
|
+
* Stripe checkout (/api/marketplace/checkout) when configured.
|
|
106
|
+
*/ export function useMarketplaceActions(hostId, orgId) {
|
|
107
|
+
const { data: user } = useUser();
|
|
108
|
+
const { enqueueSnackbar } = useSnackbar();
|
|
109
|
+
const { queueLoading } = useLoading();
|
|
110
|
+
/**
|
|
111
|
+
* One purchase attempt per listing, minted lazily (AGL-1697).
|
|
112
|
+
*
|
|
113
|
+
* Per-listing rather than per-click: minting inside `buy` would defeat the
|
|
114
|
+
* whole point, because two clicks would mint two keys and the server would
|
|
115
|
+
* see two purchases. Held in a ref rather than state so a re-render cannot
|
|
116
|
+
* rotate a key mid-flight.
|
|
117
|
+
*
|
|
118
|
+
* It retires when the page does. That is the honest boundary for a redirect
|
|
119
|
+
* flow — the buyer leaves for Stripe and comes back to a fresh mount — and it
|
|
120
|
+
* means this key covers the double-click and the retry, while the server's
|
|
121
|
+
* already-purchased check covers the buyer who returns tomorrow.
|
|
122
|
+
*/ const purchaseAttempts = useRef(new Map());
|
|
123
|
+
// `scope` is only meaningful for artifact types that HAVE an org-scoped
|
|
124
|
+
// pin — see INSTALL_TARGETS. Passing it for a template would be ignored
|
|
125
|
+
// server-side, so callers ask the model rather than guessing.
|
|
126
|
+
const install = useCallback(async (listing, scope, options)=>{
|
|
127
|
+
const dequeue = queueLoading();
|
|
128
|
+
try {
|
|
129
|
+
var _payload_foreignMediaScopes, _payload_degradedFieldIds;
|
|
130
|
+
const artifactType = listingArtifactType(listing);
|
|
131
|
+
const endpoint = endpointForArtifact(artifactType);
|
|
132
|
+
const response = await authorizedFetch(user, `/api/${endpoint}`, {
|
|
133
|
+
method: 'POST',
|
|
134
|
+
headers: {
|
|
135
|
+
'Content-Type': 'application/json'
|
|
136
|
+
},
|
|
137
|
+
body: JSON.stringify(_extends({
|
|
138
|
+
listingId: listing.$id,
|
|
139
|
+
hostId
|
|
140
|
+
}, scope ? {
|
|
141
|
+
scope
|
|
142
|
+
} : {}))
|
|
143
|
+
});
|
|
144
|
+
const payload = await response.json();
|
|
145
|
+
if (!response.ok) {
|
|
146
|
+
var _ref;
|
|
147
|
+
// The copy has diverged: hand it to the review rather than telling
|
|
148
|
+
// someone to review something they cannot reach from here.
|
|
149
|
+
if ((payload == null ? void 0 : payload.diverged) && (options == null ? void 0 : options.onDiverged)) {
|
|
150
|
+
return void options.onDiverged();
|
|
151
|
+
}
|
|
152
|
+
// An installs feature lockdown (AGL-1510/1532): "Install failed"
|
|
153
|
+
// reads as a broken listing — the very thing an incident response
|
|
154
|
+
// needs people NOT to conclude while a suspect listing is under
|
|
155
|
+
// investigation. The server says installs are paused; say that.
|
|
156
|
+
const locked = parseLockdownRefusal(response.status, payload);
|
|
157
|
+
if (locked) {
|
|
158
|
+
return void enqueueSnackbar(lockdownRefusalText(locked), {
|
|
159
|
+
variant: 'warning',
|
|
160
|
+
persist: true
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
return void enqueueSnackbar((_ref = payload == null ? void 0 : payload.error) != null ? _ref : 'Install failed', {
|
|
164
|
+
variant: response.status === 402 ? 'warning' : 'error',
|
|
165
|
+
allowDuplicate: true
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
// Types that land inert must not read as "your site changed".
|
|
169
|
+
const landed = landingMessage(artifactType, listing.displayName);
|
|
170
|
+
enqueueSnackbar(landed != null ? landed : payload.updated ? `Updated "${listing.displayName}" to v${payload.version}` : `Installed "${listing.displayName}" — find it under Your components`, {
|
|
171
|
+
variant: 'success',
|
|
172
|
+
persist: false
|
|
173
|
+
});
|
|
174
|
+
// No base snapshot: this copy can never be three-way merged, and a
|
|
175
|
+
// future update will overwrite whatever the user does to it. The
|
|
176
|
+
// route has always said so on the wire; nothing listened (AGL-2339).
|
|
177
|
+
//
|
|
178
|
+
// `=== false` rather than a falsy test: a route that omitted the
|
|
179
|
+
// field entirely has told us nothing, and warning on silence would
|
|
180
|
+
// put a scary, unactionable message on every install from any
|
|
181
|
+
// endpoint that had not been updated yet.
|
|
182
|
+
if (payload.baseStored === false) {
|
|
183
|
+
enqueueSnackbar(baseMissingWarning(listing.displayName), {
|
|
184
|
+
variant: 'warning',
|
|
185
|
+
persist: true
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* What was, and was not, checked about an email somebody else wrote.
|
|
190
|
+
*
|
|
191
|
+
* Said HERE because this is the moment the design enters the
|
|
192
|
+
* workspace, and because the two facts are the ones a sender cannot
|
|
193
|
+
* recover afterwards by looking at the design. `unreviewed` is the
|
|
194
|
+
* ordinary case — email starters are auto-listed like every other
|
|
195
|
+
* copied artifact — so the sentence has to be a statement rather than
|
|
196
|
+
* an alarm, and it must never be silence: an install that said nothing
|
|
197
|
+
* would be read as a review that happened.
|
|
198
|
+
*
|
|
199
|
+
* The link hosts are the inspectable half of the link policy. The
|
|
200
|
+
* remote-asset rule is mechanical and the route enforces it, but where
|
|
201
|
+
* a template points a tenant's own customers is a judgment only the
|
|
202
|
+
* tenant can make, and they can only make it if they are shown the
|
|
203
|
+
* list.
|
|
204
|
+
*/ if (payload.assurance) {
|
|
205
|
+
var _payload_linkHosts;
|
|
206
|
+
const hosts = (_payload_linkHosts = payload.linkHosts) != null ? _payload_linkHosts : [];
|
|
207
|
+
enqueueSnackbar((payload.assurance === 'approved' ? 'Reviewed by Aglyn. ' : 'Nobody at Aglyn has reviewed this design. ') + (hosts.length ? `It links to ${hosts.join(', ')}. ` : 'It links nowhere outside your site. ') + 'Read it before you send from it.', {
|
|
208
|
+
variant: 'info',
|
|
209
|
+
persist: true
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
// A media reference that still resolves against the publisher's own
|
|
213
|
+
// library — served by our CDN, so no third party learns who opened the
|
|
214
|
+
// mail, but the picture behind it is still theirs to change.
|
|
215
|
+
if ((_payload_foreignMediaScopes = payload.foreignMediaScopes) == null ? void 0 : _payload_foreignMediaScopes.length) {
|
|
216
|
+
enqueueSnackbar('Some images in this email are still served from the publisher’s ' + 'own media library, so they can change after you install. ' + 'Replace them with your own to pin them.', {
|
|
217
|
+
variant: 'info',
|
|
218
|
+
persist: false
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
// A schema whose reference fields couldn't be relinked installs with
|
|
222
|
+
// those fields degraded to text — silently changing a field's type
|
|
223
|
+
// would be the kind of thing you discover much later (AGL-657).
|
|
224
|
+
if ((_payload_degradedFieldIds = payload.degradedFieldIds) == null ? void 0 : _payload_degradedFieldIds.length) {
|
|
225
|
+
enqueueSnackbar(`${payload.degradedFieldIds.length} reference field(s) became ` + 'plain text — the datasets they pointed at are not in this ' + 'organization.', {
|
|
226
|
+
variant: 'info',
|
|
227
|
+
persist: false
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
} catch (error) {
|
|
231
|
+
console.error(error);
|
|
232
|
+
enqueueSnackbar('An error has occurred', {
|
|
233
|
+
variant: 'error',
|
|
234
|
+
allowDuplicate: true
|
|
235
|
+
});
|
|
236
|
+
} finally{
|
|
237
|
+
dequeue();
|
|
238
|
+
}
|
|
239
|
+
}, [
|
|
240
|
+
user,
|
|
241
|
+
hostId,
|
|
242
|
+
queueLoading,
|
|
243
|
+
enqueueSnackbar
|
|
244
|
+
]);
|
|
245
|
+
/**
|
|
246
|
+
* Fans an install-targeting plan out to the install routes (AGL-773). Each
|
|
247
|
+
* step is either a single org pin or a host pin on a named site; org steps
|
|
248
|
+
* resolve the org from the acting `hostId`. One summary snackbar reports the
|
|
249
|
+
* whole plan rather than N toasts. Use `resolveInstallPlan` (model) to turn
|
|
250
|
+
* an All-sites / Selected-sites choice into these steps.
|
|
251
|
+
*/ const installPlan = useCallback(async (listing, steps, options)=>{
|
|
252
|
+
if (!steps.length) return;
|
|
253
|
+
const dequeue = queueLoading();
|
|
254
|
+
try {
|
|
255
|
+
const artifactType = listingArtifactType(listing);
|
|
256
|
+
const endpoint = endpointForArtifact(artifactType);
|
|
257
|
+
let installed = 0;
|
|
258
|
+
const errors = [];
|
|
259
|
+
/**
|
|
260
|
+
* Surfaced separately from success (AGL-1017/429): the pin is written
|
|
261
|
+
* either way — it is an explicit choice — but a bundle built for a
|
|
262
|
+
* different host ABI will not load, and a green "Updated" toast alone
|
|
263
|
+
* would leave the workspace believing it did.
|
|
264
|
+
*/ const abiWarnings = new Set();
|
|
265
|
+
/**
|
|
266
|
+
* Sites whose copy landed with no base snapshot (AGL-2339). Counted
|
|
267
|
+
* rather than toasted per step: the fan-out installs the SAME
|
|
268
|
+
* artifact everywhere, so the answer is the same on every site and N
|
|
269
|
+
* identical warnings would read as N different problems.
|
|
270
|
+
*/ let withoutBase = 0;
|
|
271
|
+
/**
|
|
272
|
+
* A feature lockdown refuses every step identically (AGL-1532), so
|
|
273
|
+
* the fan-out stops at the first one and says "installs are paused"
|
|
274
|
+
* ONCE. N identical refusals stacked into the error summary would
|
|
275
|
+
* read as N broken sites — the opposite of what the lock is saying.
|
|
276
|
+
*/ let lockedNotice = null;
|
|
277
|
+
for (const step of steps){
|
|
278
|
+
// Org steps still need a host to resolve the org server-side, so
|
|
279
|
+
// fall back to the acting host; host steps target their own site.
|
|
280
|
+
const targetHostId = step.scope === 'host' ? step.hostId : hostId;
|
|
281
|
+
const response = await authorizedFetch(user, `/api/${endpoint}`, {
|
|
282
|
+
method: 'POST',
|
|
283
|
+
headers: {
|
|
284
|
+
'Content-Type': 'application/json'
|
|
285
|
+
},
|
|
286
|
+
body: JSON.stringify(_extends({
|
|
287
|
+
listingId: listing.$id,
|
|
288
|
+
hostId: targetHostId
|
|
289
|
+
}, step.scope === 'org' ? {
|
|
290
|
+
scope: 'org'
|
|
291
|
+
} : {}))
|
|
292
|
+
});
|
|
293
|
+
const payload = await response.json().catch(()=>({}));
|
|
294
|
+
if (response.ok) {
|
|
295
|
+
installed += 1;
|
|
296
|
+
if (payload == null ? void 0 : payload.hostAbiWarning) {
|
|
297
|
+
abiWarnings.add(String(payload.hostAbiWarning));
|
|
298
|
+
}
|
|
299
|
+
if ((payload == null ? void 0 : payload.baseStored) === false) withoutBase += 1;
|
|
300
|
+
} else {
|
|
301
|
+
var _ref;
|
|
302
|
+
lockedNotice = parseLockdownRefusal(response.status, payload);
|
|
303
|
+
if (lockedNotice) break;
|
|
304
|
+
errors.push((_ref = payload == null ? void 0 : payload.error) != null ? _ref : 'Install failed');
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (lockedNotice) {
|
|
308
|
+
enqueueSnackbar(lockdownRefusalText(lockedNotice), {
|
|
309
|
+
variant: 'warning',
|
|
310
|
+
persist: true
|
|
311
|
+
});
|
|
312
|
+
// Whatever landed before the lock DID land — saying so is the
|
|
313
|
+
// difference between "nothing happened" and a half-applied plan
|
|
314
|
+
// the workspace would otherwise discover much later.
|
|
315
|
+
if (installed) {
|
|
316
|
+
enqueueSnackbar(`${installed} site(s) were already updated before the pause.`, {
|
|
317
|
+
variant: 'info',
|
|
318
|
+
persist: false
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const orgWide = steps.some((step)=>step.scope === 'org');
|
|
324
|
+
const updating = (options == null ? void 0 : options.intent) === 'update';
|
|
325
|
+
const noun = updating ? 'Updated' : landingMessage(artifactType, '') ? 'Saved' : 'Installed';
|
|
326
|
+
const toVersion = updating && (options == null ? void 0 : options.version) != null ? ` to v${options.version}` : '';
|
|
327
|
+
if (installed && !errors.length) {
|
|
328
|
+
enqueueSnackbar(orgWide ? `${noun} "${listing.displayName}"${toVersion} for the whole organization` : `${noun} "${listing.displayName}"${toVersion} on ${installed} site` + (installed === 1 ? '' : 's'), {
|
|
329
|
+
variant: 'success',
|
|
330
|
+
persist: false
|
|
331
|
+
});
|
|
332
|
+
} else if (installed && errors.length) {
|
|
333
|
+
enqueueSnackbar(`${noun} on ${installed} site(s); ${errors.length} failed — ${errors[0]}`, {
|
|
334
|
+
variant: 'warning',
|
|
335
|
+
allowDuplicate: true
|
|
336
|
+
});
|
|
337
|
+
} else {
|
|
338
|
+
var _errors_;
|
|
339
|
+
enqueueSnackbar((_errors_ = errors[0]) != null ? _errors_ : 'Install failed', {
|
|
340
|
+
variant: 'error',
|
|
341
|
+
allowDuplicate: true
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
for (const warning of abiWarnings){
|
|
345
|
+
enqueueSnackbar(warning, {
|
|
346
|
+
variant: 'warning',
|
|
347
|
+
allowDuplicate: true
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
if (withoutBase) {
|
|
351
|
+
enqueueSnackbar(baseMissingWarning(listing.displayName), {
|
|
352
|
+
variant: 'warning',
|
|
353
|
+
persist: true
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
} catch (error) {
|
|
357
|
+
console.error(error);
|
|
358
|
+
enqueueSnackbar('An error has occurred', {
|
|
359
|
+
variant: 'error',
|
|
360
|
+
allowDuplicate: true
|
|
361
|
+
});
|
|
362
|
+
} finally{
|
|
363
|
+
dequeue();
|
|
364
|
+
}
|
|
365
|
+
}, [
|
|
366
|
+
user,
|
|
367
|
+
hostId,
|
|
368
|
+
queueLoading,
|
|
369
|
+
enqueueSnackbar
|
|
370
|
+
]);
|
|
371
|
+
const buy = useCallback(async (listing)=>{
|
|
372
|
+
const dequeue = queueLoading();
|
|
373
|
+
try {
|
|
374
|
+
var _purchaseAttempts_current_get;
|
|
375
|
+
const listingId = String(listing.$id);
|
|
376
|
+
const attemptKey = (_purchaseAttempts_current_get = purchaseAttempts.current.get(listingId)) != null ? _purchaseAttempts_current_get : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
377
|
+
purchaseAttempts.current.set(listingId, attemptKey);
|
|
378
|
+
const response = await authorizedFetch(user, '/api/marketplace/checkout', {
|
|
379
|
+
method: 'POST',
|
|
380
|
+
headers: {
|
|
381
|
+
'Content-Type': 'application/json',
|
|
382
|
+
// Stable across a retry of THIS attempt (AGL-1697), so a
|
|
383
|
+
// double-click or a re-submit after a timeout cannot open a
|
|
384
|
+
// second Stripe session on a live account.
|
|
385
|
+
'Idempotency-Key': attemptKey
|
|
386
|
+
},
|
|
387
|
+
// The browser's GA client id rides along so the SERVER-side
|
|
388
|
+
// `purchase` the Stripe webhook sends can be attributed to the
|
|
389
|
+
// session — and therefore the campaign — that produced it
|
|
390
|
+
// (AGL-1638). The webhook has always read this off the session
|
|
391
|
+
// metadata; until now nothing captured it, so every marketplace
|
|
392
|
+
// sale landed on a synthetic, sessionless GA user and plugin
|
|
393
|
+
// revenue had no acquisition channel at all.
|
|
394
|
+
//
|
|
395
|
+
// Resolves to null within 500ms when gtag is absent (consent
|
|
396
|
+
// refused, ad blocker, analytics unconfigured), so it can never
|
|
397
|
+
// delay or block a purchase — same contract as the console's
|
|
398
|
+
// subscription checkout.
|
|
399
|
+
body: JSON.stringify(_extends({
|
|
400
|
+
listingId: listing.$id,
|
|
401
|
+
hostId
|
|
402
|
+
}, orgId ? {
|
|
403
|
+
orgId
|
|
404
|
+
} : {}, {
|
|
405
|
+
gaClientId: await readGaClientId(process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID)
|
|
406
|
+
}))
|
|
407
|
+
});
|
|
408
|
+
const payload = await response.json();
|
|
409
|
+
// The PAID install door refuses under a lock too (AGL-1545): both
|
|
410
|
+
// `checkout` and `marketplace-installs` gate it. A buyer must not
|
|
411
|
+
// read either lock as a payment failure — same reason the billing
|
|
412
|
+
// page carries this branch (AGL-1532).
|
|
413
|
+
const locked = parseLockdownRefusal(response.status, payload);
|
|
414
|
+
if (locked) {
|
|
415
|
+
return void enqueueSnackbar(lockdownRefusalText(locked), {
|
|
416
|
+
variant: 'warning',
|
|
417
|
+
persist: true
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
if (response.status === 501) {
|
|
421
|
+
return void enqueueSnackbar('Purchases are not configured on this deployment', {
|
|
422
|
+
variant: 'info',
|
|
423
|
+
persist: false
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
if (!response.ok || !(payload == null ? void 0 : payload.url)) {
|
|
427
|
+
var _ref;
|
|
428
|
+
return void enqueueSnackbar((_ref = payload == null ? void 0 : payload.error) != null ? _ref : 'Checkout failed', {
|
|
429
|
+
variant: 'error',
|
|
430
|
+
allowDuplicate: true
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
window.location.assign(payload.url);
|
|
434
|
+
} catch (error) {
|
|
435
|
+
console.error(error);
|
|
436
|
+
enqueueSnackbar('An error has occurred', {
|
|
437
|
+
variant: 'error',
|
|
438
|
+
allowDuplicate: true
|
|
439
|
+
});
|
|
440
|
+
} finally{
|
|
441
|
+
dequeue();
|
|
442
|
+
}
|
|
443
|
+
}, [
|
|
444
|
+
user,
|
|
445
|
+
hostId,
|
|
446
|
+
orgId,
|
|
447
|
+
queueLoading,
|
|
448
|
+
enqueueSnackbar
|
|
449
|
+
]);
|
|
450
|
+
/**
|
|
451
|
+
* Uninstall a plugin listing from the listing detail (AGL-881) — the same
|
|
452
|
+
* `action:'uninstall'` the installed add-ons card uses, so it removes the
|
|
453
|
+
* pin (and the switchboard entry when nothing else pins it) and decrements
|
|
454
|
+
* activeInstalls. Only plugins have a removable pin; other artifact types
|
|
455
|
+
* land in a library and have nothing to uninstall.
|
|
456
|
+
*/ const uninstall = useCallback(async (listing, scope, // Which site's pin to drop (AGL-997). Without this the call always
|
|
457
|
+
// targeted the acting host, so at org scope "uninstall" could only
|
|
458
|
+
// mean "the whole org pin" or "whichever site happened to be acting"
|
|
459
|
+
// — removing the plugin from ONE of several sites was unreachable.
|
|
460
|
+
targetHostId)=>{
|
|
461
|
+
const dequeue = queueLoading();
|
|
462
|
+
try {
|
|
463
|
+
const response = await authorizedFetch(user, '/api/marketplace/install-plugin', {
|
|
464
|
+
method: 'POST',
|
|
465
|
+
headers: {
|
|
466
|
+
'Content-Type': 'application/json'
|
|
467
|
+
},
|
|
468
|
+
body: JSON.stringify(_extends({
|
|
469
|
+
listingId: listing.$id,
|
|
470
|
+
hostId: targetHostId != null ? targetHostId : hostId,
|
|
471
|
+
action: 'uninstall'
|
|
472
|
+
}, scope ? {
|
|
473
|
+
scope
|
|
474
|
+
} : {}))
|
|
475
|
+
});
|
|
476
|
+
const payload = await response.json().catch(()=>({}));
|
|
477
|
+
if (!response.ok) {
|
|
478
|
+
var _ref;
|
|
479
|
+
// Uninstall rides the install-plugin route, so an installs lock
|
|
480
|
+
// refuses it too (AGL-1532) — and "Uninstall failed" during an
|
|
481
|
+
// incident is exactly the wrong sentence for someone trying to
|
|
482
|
+
// remove the listing under investigation.
|
|
483
|
+
const locked = parseLockdownRefusal(response.status, payload);
|
|
484
|
+
if (locked) {
|
|
485
|
+
return void enqueueSnackbar(lockdownRefusalText(locked), {
|
|
486
|
+
variant: 'warning',
|
|
487
|
+
persist: true
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
return void enqueueSnackbar((_ref = payload == null ? void 0 : payload.error) != null ? _ref : 'Uninstall failed', {
|
|
491
|
+
variant: 'error',
|
|
492
|
+
allowDuplicate: true
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
enqueueSnackbar(`Removed "${listing.displayName}"`, {
|
|
496
|
+
variant: 'success',
|
|
497
|
+
persist: false
|
|
498
|
+
});
|
|
499
|
+
} catch (error) {
|
|
500
|
+
console.error(error);
|
|
501
|
+
enqueueSnackbar('An error has occurred', {
|
|
502
|
+
variant: 'error',
|
|
503
|
+
allowDuplicate: true
|
|
504
|
+
});
|
|
505
|
+
} finally{
|
|
506
|
+
dequeue();
|
|
507
|
+
}
|
|
508
|
+
}, [
|
|
509
|
+
user,
|
|
510
|
+
hostId,
|
|
511
|
+
queueLoading,
|
|
512
|
+
enqueueSnackbar
|
|
513
|
+
]);
|
|
514
|
+
return {
|
|
515
|
+
install,
|
|
516
|
+
installPlan,
|
|
517
|
+
buy,
|
|
518
|
+
uninstall
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
export default useMarketplaceActions;
|
|
522
|
+
|
|
523
|
+
//# sourceMappingURL=use-marketplace-actions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/hooks/use-marketplace-actions.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'use client'\n\nimport { lockdownRefusalText, parseLockdownRefusal } from '@aglyn/aglyn'\nimport { readGaClientId } from '@aglyn/aglyn/app-utils/analytics-events'\nimport { useLoading } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useCallback, useRef } from 'react'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport {\n type InstallPlanStep,\n listingArtifactType,\n} from '../model/marketplace'\n\n/**\n * Each artifact type has its own installer route (AGL-672): routing\n * everything to the component one silently installed the wrong thing or\n * 404'd. `listingArtifactType` tolerates the legacy `type`/`kind`\n * discriminators (AGL-654).\n */\nfunction endpointForArtifact(artifactType: string): string {\n switch (artifactType) {\n case 'template':\n return 'marketplace/install-template'\n case 'layout':\n return 'marketplace/install-layout'\n case 'plugin':\n return 'marketplace/install-plugin'\n case 'datasetSchema':\n return 'marketplace/install-dataset-schema'\n case 'emailTemplate':\n return 'marketplace/install-email-template'\n case 'emailStarter':\n return 'marketplace/install-email-starter'\n case 'theme':\n return 'marketplace/install-theme'\n default:\n return 'marketplace/install'\n }\n}\n\n/**\n * Artifact types whose install deliberately does NOT touch the running site\n * (AGL-669/671/657) — the copy must not imply otherwise. Templates and layouts\n * land in the Templates library; an email template lands as an inactive\n * version the owner still has to activate.\n */\nfunction landingMessage(\n artifactType: string,\n displayName: string,\n): string | null {\n switch (artifactType) {\n case 'template':\n case 'layout':\n return (\n `Saved \"${displayName}\" to your Templates — nothing is live until ` +\n 'you use it.'\n )\n case 'emailTemplate':\n return (\n `Saved \"${displayName}\" as a draft version — activate it in the ` +\n 'email designer to start sending it.'\n )\n case 'emailStarter':\n // A copy, and saying so is the point: the publisher cannot reach it\n // again, and nothing goes out until a campaign is sent from it.\n return (\n `Added \"${displayName}\" to your Email templates as your own copy — ` +\n 'edit it freely, nothing is sent until you send a campaign.'\n )\n case 'datasetSchema':\n return `Created \"${displayName}\" as a new, empty dataset.`\n case 'theme':\n // The one type that DOES change the running site on install, so it gets\n // the opposite of the reassurance the others get — plus the way back,\n // because a repainted site is alarming if you cannot see how to undo it.\n return (\n `Applied \"${displayName}\" to this site. Setup → Theme has a way back ` +\n 'to your previous theme.'\n )\n default:\n return null\n }\n}\n\n/**\n * What to tell someone whose install will never be three-way mergeable\n * (AGL-2339).\n *\n * Every install route returns `baseStored`, and `provenance.ts` says what a\n * false one means: *\"a snapshot that cannot be written leaves `baseStored:\n * false` and an artifact that reports itself as not updatable\"*. It reported\n * it to NOBODY — zero `.tsx` readers across all seven routes.\n *\n * The consequence is silent and expensive. `hasDivergedFromBase` needs the\n * base snapshot to tell the publisher's change from the user's; without one\n * an update can only overwrite. So a user who customizes a large artifact —\n * one whose content exceeded `ARTIFACT_BASE_MAX_BYTES`, or whose snapshot\n * write failed — LOSES those changes on the next update, having been told\n * nothing at install time. The update dialog does explain itself when the\n * moment arrives (`preview.mergeable` renders `preview.reason`), but by then\n * the choice is take-the-update-or-stay-behind, not \"keep a copy first\".\n *\n * Persistent rather than a timed toast: it is advice to act on before\n * editing, and it is the only moment this is knowable in advance.\n */\nfunction baseMissingWarning(displayName: string): string {\n return (\n `\"${displayName}\" is too large to keep a reference copy of, so a future ` +\n 'update will REPLACE it rather than merge. Export or note any changes ' +\n 'you make to it.'\n )\n}\n\n/**\n * Shared install/buy handlers for marketplace listings (AGL-95): used by the\n * browse grid and the listing detail page. Install copies the pinned\n * version snapshot server-side (/api/marketplace/install); buy opens a\n * Stripe checkout (/api/marketplace/checkout) when configured.\n */\nexport function useMarketplaceActions(hostId: string, orgId?: string | null) {\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const { queueLoading } = useLoading()\n\n /**\n * One purchase attempt per listing, minted lazily (AGL-1697).\n *\n * Per-listing rather than per-click: minting inside `buy` would defeat the\n * whole point, because two clicks would mint two keys and the server would\n * see two purchases. Held in a ref rather than state so a re-render cannot\n * rotate a key mid-flight.\n *\n * It retires when the page does. That is the honest boundary for a redirect\n * flow — the buyer leaves for Stripe and comes back to a fresh mount — and it\n * means this key covers the double-click and the retry, while the server's\n * already-purchased check covers the buyer who returns tomorrow.\n */\n const purchaseAttempts = useRef(new Map<string, string>())\n\n // `scope` is only meaningful for artifact types that HAVE an org-scoped\n // pin — see INSTALL_TARGETS. Passing it for a template would be ignored\n // server-side, so callers ask the model rather than guessing.\n const install = useCallback(\n async (\n listing: any,\n scope?: 'org' | 'host',\n options?: {\n /**\n * Called instead of an error toast when the install route refuses\n * because the workspace's copy has been EDITED since it was installed\n * (AGL-1018's 409, AGL-1034).\n *\n * The refusal is correct — that install would have destroyed their\n * work — but \"review the update\" is only useful advice if something\n * offers the review. Handling it here rather than at each call site\n * means every surface that installs a copied artifact gets the\n * recovery, including ones written later.\n */\n onDiverged?: () => void\n },\n ) => {\n const dequeue = queueLoading()\n try {\n const artifactType = listingArtifactType(listing)\n const endpoint = endpointForArtifact(artifactType)\n const response = await authorizedFetch(user, `/api/${endpoint}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n listingId: listing.$id,\n hostId,\n ...(scope ? { scope } : {}),\n }),\n })\n const payload = await response.json()\n if (!response.ok) {\n // The copy has diverged: hand it to the review rather than telling\n // someone to review something they cannot reach from here.\n if (payload?.diverged && options?.onDiverged) {\n return void options.onDiverged()\n }\n // An installs feature lockdown (AGL-1510/1532): \"Install failed\"\n // reads as a broken listing — the very thing an incident response\n // needs people NOT to conclude while a suspect listing is under\n // investigation. The server says installs are paused; say that.\n const locked = parseLockdownRefusal(response.status, payload)\n if (locked) {\n return void enqueueSnackbar(lockdownRefusalText(locked), {\n variant: 'warning',\n persist: true,\n })\n }\n return void enqueueSnackbar(payload?.error ?? 'Install failed', {\n variant: response.status === 402 ? 'warning' : 'error',\n allowDuplicate: true,\n })\n }\n // Types that land inert must not read as \"your site changed\".\n const landed = landingMessage(artifactType, listing.displayName)\n enqueueSnackbar(\n landed ??\n (payload.updated\n ? `Updated \"${listing.displayName}\" to v${payload.version}`\n : `Installed \"${listing.displayName}\" — find it under Your components`),\n { variant: 'success', persist: false },\n )\n // No base snapshot: this copy can never be three-way merged, and a\n // future update will overwrite whatever the user does to it. The\n // route has always said so on the wire; nothing listened (AGL-2339).\n //\n // `=== false` rather than a falsy test: a route that omitted the\n // field entirely has told us nothing, and warning on silence would\n // put a scary, unactionable message on every install from any\n // endpoint that had not been updated yet.\n if (payload.baseStored === false) {\n enqueueSnackbar(baseMissingWarning(listing.displayName), {\n variant: 'warning',\n persist: true,\n })\n }\n /**\n * What was, and was not, checked about an email somebody else wrote.\n *\n * Said HERE because this is the moment the design enters the\n * workspace, and because the two facts are the ones a sender cannot\n * recover afterwards by looking at the design. `unreviewed` is the\n * ordinary case — email starters are auto-listed like every other\n * copied artifact — so the sentence has to be a statement rather than\n * an alarm, and it must never be silence: an install that said nothing\n * would be read as a review that happened.\n *\n * The link hosts are the inspectable half of the link policy. The\n * remote-asset rule is mechanical and the route enforces it, but where\n * a template points a tenant's own customers is a judgment only the\n * tenant can make, and they can only make it if they are shown the\n * list.\n */\n if (payload.assurance) {\n const hosts: string[] = payload.linkHosts ?? []\n enqueueSnackbar(\n (payload.assurance === 'approved'\n ? 'Reviewed by Aglyn. '\n : 'Nobody at Aglyn has reviewed this design. ') +\n (hosts.length\n ? `It links to ${hosts.join(', ')}. `\n : 'It links nowhere outside your site. ') +\n 'Read it before you send from it.',\n { variant: 'info', persist: true },\n )\n }\n // A media reference that still resolves against the publisher's own\n // library — served by our CDN, so no third party learns who opened the\n // mail, but the picture behind it is still theirs to change.\n if (payload.foreignMediaScopes?.length) {\n enqueueSnackbar(\n 'Some images in this email are still served from the publisher’s ' +\n 'own media library, so they can change after you install. ' +\n 'Replace them with your own to pin them.',\n { variant: 'info', persist: false },\n )\n }\n // A schema whose reference fields couldn't be relinked installs with\n // those fields degraded to text — silently changing a field's type\n // would be the kind of thing you discover much later (AGL-657).\n if (payload.degradedFieldIds?.length) {\n enqueueSnackbar(\n `${payload.degradedFieldIds.length} reference field(s) became ` +\n 'plain text — the datasets they pointed at are not in this ' +\n 'organization.',\n { variant: 'info', persist: false },\n )\n }\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n dequeue()\n }\n },\n [user, hostId, queueLoading, enqueueSnackbar],\n )\n\n /**\n * Fans an install-targeting plan out to the install routes (AGL-773). Each\n * step is either a single org pin or a host pin on a named site; org steps\n * resolve the org from the acting `hostId`. One summary snackbar reports the\n * whole plan rather than N toasts. Use `resolveInstallPlan` (model) to turn\n * an All-sites / Selected-sites choice into these steps.\n */\n const installPlan = useCallback(\n async (\n listing: any,\n steps: readonly InstallPlanStep[],\n options?: {\n /**\n * Re-pinning something already installed (AGL-1017). Only the wording\n * changes — the route is the same, because moving a pin forward IS\n * installing — but \"Installed on 1 site\" is the wrong sentence to read\n * after pressing Update.\n */\n intent?: 'install' | 'update'\n /** The version being moved to, for the update summary. */\n version?: string | number | null\n },\n ) => {\n if (!steps.length) return\n const dequeue = queueLoading()\n try {\n const artifactType = listingArtifactType(listing)\n const endpoint = endpointForArtifact(artifactType)\n let installed = 0\n const errors: string[] = []\n /**\n * Surfaced separately from success (AGL-1017/429): the pin is written\n * either way — it is an explicit choice — but a bundle built for a\n * different host ABI will not load, and a green \"Updated\" toast alone\n * would leave the workspace believing it did.\n */\n const abiWarnings = new Set<string>()\n /**\n * Sites whose copy landed with no base snapshot (AGL-2339). Counted\n * rather than toasted per step: the fan-out installs the SAME\n * artifact everywhere, so the answer is the same on every site and N\n * identical warnings would read as N different problems.\n */\n let withoutBase = 0\n /**\n * A feature lockdown refuses every step identically (AGL-1532), so\n * the fan-out stops at the first one and says \"installs are paused\"\n * ONCE. N identical refusals stacked into the error summary would\n * read as N broken sites — the opposite of what the lock is saying.\n */\n let lockedNotice: ReturnType<typeof parseLockdownRefusal> = null\n for (const step of steps) {\n // Org steps still need a host to resolve the org server-side, so\n // fall back to the acting host; host steps target their own site.\n const targetHostId = step.scope === 'host' ? step.hostId : hostId\n const response = await authorizedFetch(user, `/api/${endpoint}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n listingId: listing.$id,\n hostId: targetHostId,\n ...(step.scope === 'org' ? { scope: 'org' } : {}),\n }),\n })\n const payload = await response.json().catch(() => ({}))\n if (response.ok) {\n installed += 1\n if (payload?.hostAbiWarning) {\n abiWarnings.add(String(payload.hostAbiWarning))\n }\n if (payload?.baseStored === false) withoutBase += 1\n } else {\n lockedNotice = parseLockdownRefusal(response.status, payload)\n if (lockedNotice) break\n errors.push(payload?.error ?? 'Install failed')\n }\n }\n if (lockedNotice) {\n enqueueSnackbar(lockdownRefusalText(lockedNotice), {\n variant: 'warning',\n persist: true,\n })\n // Whatever landed before the lock DID land — saying so is the\n // difference between \"nothing happened\" and a half-applied plan\n // the workspace would otherwise discover much later.\n if (installed) {\n enqueueSnackbar(\n `${installed} site(s) were already updated before the pause.`,\n { variant: 'info', persist: false },\n )\n }\n return\n }\n const orgWide = steps.some((step) => step.scope === 'org')\n const updating = options?.intent === 'update'\n const noun = updating\n ? 'Updated'\n : landingMessage(artifactType, '')\n ? 'Saved'\n : 'Installed'\n const toVersion =\n updating && options?.version != null ? ` to v${options.version}` : ''\n if (installed && !errors.length) {\n enqueueSnackbar(\n orgWide\n ? `${noun} \"${listing.displayName}\"${toVersion} for the whole organization`\n : `${noun} \"${listing.displayName}\"${toVersion} on ${installed} site` +\n (installed === 1 ? '' : 's'),\n { variant: 'success', persist: false },\n )\n } else if (installed && errors.length) {\n enqueueSnackbar(\n `${noun} on ${installed} site(s); ${errors.length} failed — ${errors[0]}`,\n { variant: 'warning', allowDuplicate: true },\n )\n } else {\n enqueueSnackbar(errors[0] ?? 'Install failed', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n for (const warning of abiWarnings) {\n enqueueSnackbar(warning, { variant: 'warning', allowDuplicate: true })\n }\n if (withoutBase) {\n enqueueSnackbar(baseMissingWarning(listing.displayName), {\n variant: 'warning',\n persist: true,\n })\n }\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n dequeue()\n }\n },\n [user, hostId, queueLoading, enqueueSnackbar],\n )\n\n const buy = useCallback(\n async (listing: any) => {\n const dequeue = queueLoading()\n try {\n const listingId = String(listing.$id)\n const attemptKey =\n purchaseAttempts.current.get(listingId) ??\n `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`\n purchaseAttempts.current.set(listingId, attemptKey)\n const response = await authorizedFetch(\n user,\n '/api/marketplace/checkout',\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n // Stable across a retry of THIS attempt (AGL-1697), so a\n // double-click or a re-submit after a timeout cannot open a\n // second Stripe session on a live account.\n 'Idempotency-Key': attemptKey,\n },\n // The browser's GA client id rides along so the SERVER-side\n // `purchase` the Stripe webhook sends can be attributed to the\n // session — and therefore the campaign — that produced it\n // (AGL-1638). The webhook has always read this off the session\n // metadata; until now nothing captured it, so every marketplace\n // sale landed on a synthetic, sessionless GA user and plugin\n // revenue had no acquisition channel at all.\n //\n // Resolves to null within 500ms when gtag is absent (consent\n // refused, ad blocker, analytics unconfigured), so it can never\n // delay or block a purchase — same contract as the console's\n // subscription checkout.\n body: JSON.stringify({\n listingId: listing.$id,\n hostId,\n // WHICH WORKSPACE IS BUYING (AGL-2331). A purchase licenses an\n // organization, so the acting org is named rather than inferred:\n // at org scope the surface acts through an arbitrary FIRST site,\n // and letting the server derive the licence holder from that site\n // would make which client workspace got the licence depend on the\n // order sites happen to come back in.\n //\n // Advisory, not authority — the server resolves it through the\n // caller's own membership and refuses an org they are not in. An\n // older cached bundle that sends none still works: the server\n // falls back to resolving the org from `hostId` the same way.\n ...(orgId ? { orgId } : {}),\n gaClientId: await readGaClientId(\n process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,\n ),\n }),\n },\n )\n const payload = await response.json()\n // The PAID install door refuses under a lock too (AGL-1545): both\n // `checkout` and `marketplace-installs` gate it. A buyer must not\n // read either lock as a payment failure — same reason the billing\n // page carries this branch (AGL-1532).\n const locked = parseLockdownRefusal(response.status, payload)\n if (locked) {\n return void enqueueSnackbar(lockdownRefusalText(locked), {\n variant: 'warning',\n persist: true,\n })\n }\n if (response.status === 501) {\n return void enqueueSnackbar(\n 'Purchases are not configured on this deployment',\n { variant: 'info', persist: false },\n )\n }\n if (!response.ok || !payload?.url) {\n return void enqueueSnackbar(payload?.error ?? 'Checkout failed', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n window.location.assign(payload.url)\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n dequeue()\n }\n },\n [user, hostId, orgId, queueLoading, enqueueSnackbar],\n )\n\n /**\n * Uninstall a plugin listing from the listing detail (AGL-881) — the same\n * `action:'uninstall'` the installed add-ons card uses, so it removes the\n * pin (and the switchboard entry when nothing else pins it) and decrements\n * activeInstalls. Only plugins have a removable pin; other artifact types\n * land in a library and have nothing to uninstall.\n */\n const uninstall = useCallback(\n async (\n listing: any,\n scope?: 'org' | 'host',\n // Which site's pin to drop (AGL-997). Without this the call always\n // targeted the acting host, so at org scope \"uninstall\" could only\n // mean \"the whole org pin\" or \"whichever site happened to be acting\"\n // — removing the plugin from ONE of several sites was unreachable.\n targetHostId?: string,\n ) => {\n const dequeue = queueLoading()\n try {\n const response = await authorizedFetch(\n user,\n '/api/marketplace/install-plugin',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n listingId: listing.$id,\n hostId: targetHostId ?? hostId,\n action: 'uninstall',\n ...(scope ? { scope } : {}),\n }),\n },\n )\n const payload = await response.json().catch(() => ({}))\n if (!response.ok) {\n // Uninstall rides the install-plugin route, so an installs lock\n // refuses it too (AGL-1532) — and \"Uninstall failed\" during an\n // incident is exactly the wrong sentence for someone trying to\n // remove the listing under investigation.\n const locked = parseLockdownRefusal(response.status, payload)\n if (locked) {\n return void enqueueSnackbar(lockdownRefusalText(locked), {\n variant: 'warning',\n persist: true,\n })\n }\n return void enqueueSnackbar(payload?.error ?? 'Uninstall failed', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n enqueueSnackbar(`Removed \"${listing.displayName}\"`, {\n variant: 'success',\n persist: false,\n })\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n dequeue()\n }\n },\n [user, hostId, queueLoading, enqueueSnackbar],\n )\n\n return { install, installPlan, buy, uninstall }\n}\n\nexport default useMarketplaceActions\n"],"names":["lockdownRefusalText","parseLockdownRefusal","readGaClientId","useLoading","useSnackbar","useCallback","useRef","useUser","authorizedFetch","listingArtifactType","endpointForArtifact","artifactType","landingMessage","displayName","baseMissingWarning","useMarketplaceActions","hostId","orgId","data","user","enqueueSnackbar","queueLoading","purchaseAttempts","Map","install","listing","scope","options","dequeue","payload","endpoint","response","method","headers","body","JSON","stringify","listingId","$id","json","ok","diverged","onDiverged","locked","status","variant","persist","error","allowDuplicate","landed","updated","version","baseStored","assurance","hosts","linkHosts","length","join","foreignMediaScopes","degradedFieldIds","console","installPlan","steps","installed","errors","abiWarnings","Set","withoutBase","lockedNotice","step","targetHostId","catch","hostAbiWarning","add","String","push","orgWide","some","updating","intent","noun","toVersion","warning","buy","attemptKey","current","get","Date","now","toString","Math","random","slice","set","gaClientId","process","env","NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID","url","window","location","assign","uninstall","action"],"mappings":"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"}
|