@aglyn/plugins-marketplace 1.0.0-beta.143

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +7 -0
  3. package/package.json +51 -0
  4. package/src/index.d.ts +19 -0
  5. package/src/index.js +20 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/components/artifact-update-dialog.component.d.ts +27 -0
  8. package/src/lib/components/artifact-update-dialog.component.js +244 -0
  9. package/src/lib/components/artifact-update-dialog.component.js.map +1 -0
  10. package/src/lib/components/host-plugins-card.component.d.ts +23 -0
  11. package/src/lib/components/host-plugins-card.component.js +566 -0
  12. package/src/lib/components/host-plugins-card.component.js.map +1 -0
  13. package/src/lib/components/listing-content.component.d.ts +57 -0
  14. package/src/lib/components/listing-content.component.js +1880 -0
  15. package/src/lib/components/listing-content.component.js.map +1 -0
  16. package/src/lib/components/listing-image.component.d.ts +46 -0
  17. package/src/lib/components/listing-image.component.js +38 -0
  18. package/src/lib/components/listing-image.component.js.map +1 -0
  19. package/src/lib/components/listing-reviews.component.d.ts +22 -0
  20. package/src/lib/components/listing-reviews.component.js +419 -0
  21. package/src/lib/components/listing-reviews.component.js.map +1 -0
  22. package/src/lib/components/marketplace-browse.component.d.ts +38 -0
  23. package/src/lib/components/marketplace-browse.component.js +802 -0
  24. package/src/lib/components/marketplace-browse.component.js.map +1 -0
  25. package/src/lib/components/plugin-site-set-panel.component.d.ts +24 -0
  26. package/src/lib/components/plugin-site-set-panel.component.js +137 -0
  27. package/src/lib/components/plugin-site-set-panel.component.js.map +1 -0
  28. package/src/lib/components/plugin-site-set.component.d.ts +43 -0
  29. package/src/lib/components/plugin-site-set.component.js +425 -0
  30. package/src/lib/components/plugin-site-set.component.js.map +1 -0
  31. package/src/lib/components/rating-input.component.d.ts +3 -0
  32. package/src/lib/components/rating-input.component.js +47 -0
  33. package/src/lib/components/rating-input.component.js.map +1 -0
  34. package/src/lib/components/report-target.component.d.ts +36 -0
  35. package/src/lib/components/report-target.component.js +181 -0
  36. package/src/lib/components/report-target.component.js.map +1 -0
  37. package/src/lib/components/uninstall-impact-dialog.component.d.ts +40 -0
  38. package/src/lib/components/uninstall-impact-dialog.component.js +244 -0
  39. package/src/lib/components/uninstall-impact-dialog.component.js.map +1 -0
  40. package/src/lib/constants/bundle-common.d.ts +8 -0
  41. package/src/lib/constants/bundle-common.js +9 -0
  42. package/src/lib/constants/bundle-common.js.map +1 -0
  43. package/src/lib/hooks/use-artifact-update.d.ts +41 -0
  44. package/src/lib/hooks/use-artifact-update.js +169 -0
  45. package/src/lib/hooks/use-artifact-update.js.map +1 -0
  46. package/src/lib/hooks/use-marketplace-actions.d.ts +37 -0
  47. package/src/lib/hooks/use-marketplace-actions.js +523 -0
  48. package/src/lib/hooks/use-marketplace-actions.js.map +1 -0
  49. package/src/lib/model/artifact-merge.d.ts +154 -0
  50. package/src/lib/model/artifact-merge.js +228 -0
  51. package/src/lib/model/artifact-merge.js.map +1 -0
  52. package/src/lib/model/index.d.ts +43 -0
  53. package/src/lib/model/index.js +41 -0
  54. package/src/lib/model/index.js.map +1 -0
  55. package/src/lib/model/marketplace-props.d.ts +57 -0
  56. package/src/lib/model/marketplace-props.js +441 -0
  57. package/src/lib/model/marketplace-props.js.map +1 -0
  58. package/src/lib/model/marketplace.d.ts +833 -0
  59. package/src/lib/model/marketplace.js +929 -0
  60. package/src/lib/model/marketplace.js.map +1 -0
  61. package/src/lib/model/rating-field.d.ts +24 -0
  62. package/src/lib/model/rating-field.js +33 -0
  63. package/src/lib/model/rating-field.js.map +1 -0
  64. package/src/lib/plugin.d.ts +33 -0
  65. package/src/lib/plugin.js +86 -0
  66. package/src/lib/plugin.js.map +1 -0
  67. package/src/lib/server/billing-webhook.d.ts +25 -0
  68. package/src/lib/server/billing-webhook.js +1062 -0
  69. package/src/lib/server/billing-webhook.js.map +1 -0
  70. package/src/lib/server/checkout.d.ts +76 -0
  71. package/src/lib/server/checkout.js +477 -0
  72. package/src/lib/server/checkout.js.map +1 -0
  73. package/src/lib/server/connect.d.ts +24 -0
  74. package/src/lib/server/connect.js +205 -0
  75. package/src/lib/server/connect.js.map +1 -0
  76. package/src/lib/server/install-dataset-schema.d.ts +32 -0
  77. package/src/lib/server/install-dataset-schema.js +255 -0
  78. package/src/lib/server/install-dataset-schema.js.map +1 -0
  79. package/src/lib/server/install-email-starter.d.ts +65 -0
  80. package/src/lib/server/install-email-starter.js +279 -0
  81. package/src/lib/server/install-email-starter.js.map +1 -0
  82. package/src/lib/server/install-email-template.d.ts +34 -0
  83. package/src/lib/server/install-email-template.js +252 -0
  84. package/src/lib/server/install-email-template.js.map +1 -0
  85. package/src/lib/server/install-layout.d.ts +29 -0
  86. package/src/lib/server/install-layout.js +323 -0
  87. package/src/lib/server/install-layout.js.map +1 -0
  88. package/src/lib/server/install-pin-counts.d.ts +173 -0
  89. package/src/lib/server/install-pin-counts.js +248 -0
  90. package/src/lib/server/install-pin-counts.js.map +1 -0
  91. package/src/lib/server/install-plugin.d.ts +18 -0
  92. package/src/lib/server/install-plugin.js +405 -0
  93. package/src/lib/server/install-plugin.js.map +1 -0
  94. package/src/lib/server/install-template.d.ts +37 -0
  95. package/src/lib/server/install-template.js +348 -0
  96. package/src/lib/server/install-template.js.map +1 -0
  97. package/src/lib/server/install-theme.d.ts +42 -0
  98. package/src/lib/server/install-theme.js +313 -0
  99. package/src/lib/server/install-theme.js.map +1 -0
  100. package/src/lib/server/install.d.ts +27 -0
  101. package/src/lib/server/install.js +276 -0
  102. package/src/lib/server/install.js.map +1 -0
  103. package/src/lib/server/listing-versions.d.ts +26 -0
  104. package/src/lib/server/listing-versions.js +378 -0
  105. package/src/lib/server/listing-versions.js.map +1 -0
  106. package/src/lib/server/preview-image.d.ts +37 -0
  107. package/src/lib/server/preview-image.js +216 -0
  108. package/src/lib/server/preview-image.js.map +1 -0
  109. package/src/lib/server/provenance.d.ts +100 -0
  110. package/src/lib/server/provenance.js +118 -0
  111. package/src/lib/server/provenance.js.map +1 -0
  112. package/src/lib/server/publish-dataset-schema.d.ts +31 -0
  113. package/src/lib/server/publish-dataset-schema.js +202 -0
  114. package/src/lib/server/publish-dataset-schema.js.map +1 -0
  115. package/src/lib/server/publish-email-starter.d.ts +44 -0
  116. package/src/lib/server/publish-email-starter.js +231 -0
  117. package/src/lib/server/publish-email-starter.js.map +1 -0
  118. package/src/lib/server/publish-email-template.d.ts +33 -0
  119. package/src/lib/server/publish-email-template.js +211 -0
  120. package/src/lib/server/publish-email-template.js.map +1 -0
  121. package/src/lib/server/publish-layout.d.ts +33 -0
  122. package/src/lib/server/publish-layout.js +243 -0
  123. package/src/lib/server/publish-layout.js.map +1 -0
  124. package/src/lib/server/publish-plugin.d.ts +18 -0
  125. package/src/lib/server/publish-plugin.js +485 -0
  126. package/src/lib/server/publish-plugin.js.map +1 -0
  127. package/src/lib/server/publish-preconditions.d.ts +86 -0
  128. package/src/lib/server/publish-preconditions.js +160 -0
  129. package/src/lib/server/publish-preconditions.js.map +1 -0
  130. package/src/lib/server/publish-template.d.ts +27 -0
  131. package/src/lib/server/publish-template.js +215 -0
  132. package/src/lib/server/publish-template.js.map +1 -0
  133. package/src/lib/server/publish-theme.d.ts +38 -0
  134. package/src/lib/server/publish-theme.js +186 -0
  135. package/src/lib/server/publish-theme.js.map +1 -0
  136. package/src/lib/server/publish.d.ts +26 -0
  137. package/src/lib/server/publish.js +204 -0
  138. package/src/lib/server/publish.js.map +1 -0
  139. package/src/lib/server/publisher-profile-save.d.ts +30 -0
  140. package/src/lib/server/publisher-profile-save.js +226 -0
  141. package/src/lib/server/publisher-profile-save.js.map +1 -0
  142. package/src/lib/server/publisher-profile.d.ts +86 -0
  143. package/src/lib/server/publisher-profile.js +116 -0
  144. package/src/lib/server/publisher-profile.js.map +1 -0
  145. package/src/lib/server/purchase-entitlement.d.ts +83 -0
  146. package/src/lib/server/purchase-entitlement.js +118 -0
  147. package/src/lib/server/purchase-entitlement.js.map +1 -0
  148. package/src/lib/server/report.d.ts +32 -0
  149. package/src/lib/server/report.js +101 -0
  150. package/src/lib/server/report.js.map +1 -0
  151. package/src/lib/server/reviews.d.ts +41 -0
  152. package/src/lib/server/reviews.js +240 -0
  153. package/src/lib/server/reviews.js.map +1 -0
  154. package/src/lib/server/update-artifact.d.ts +19 -0
  155. package/src/lib/server/update-artifact.js +569 -0
  156. package/src/lib/server/update-artifact.js.map +1 -0
  157. package/src/lib/server/verification-request.d.ts +31 -0
  158. package/src/lib/server/verification-request.js +173 -0
  159. package/src/lib/server/verification-request.js.map +1 -0
  160. package/src/lib/server/version-stats.d.ts +202 -0
  161. package/src/lib/server/version-stats.js +305 -0
  162. package/src/lib/server/version-stats.js.map +1 -0
  163. package/src/lib/server.d.ts +30 -0
  164. package/src/lib/server.js +95 -0
  165. package/src/lib/server.js.map +1 -0
@@ -0,0 +1,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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"}