@netlify/spark-ui 1.30.0 → 1.31.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +4 -3
  2. package/dist/chunks/browser.Ca2rTNTu.js +769 -0
  3. package/dist/components/articleCopyActions.d.ts +9 -0
  4. package/dist/components/articleCopyActions.test.d.ts +1 -0
  5. package/dist/components/index.d.ts +2 -2
  6. package/dist/components/preact/DropZone/DropZone.d.ts +69 -14
  7. package/dist/components/preact/DropZone/DropZone.js +109 -84
  8. package/dist/components/preact/DropZone/drop-core/authedDropClient.d.ts +77 -0
  9. package/dist/components/preact/DropZone/drop-core/authedDropClient.js +97 -0
  10. package/dist/components/preact/DropZone/drop-core/buildClient.d.ts +95 -0
  11. package/dist/components/preact/DropZone/drop-core/buildClient.js +92 -0
  12. package/dist/components/preact/DropZone/drop-core/client.d.ts +4 -0
  13. package/dist/components/preact/DropZone/drop-core/client.js +2 -1
  14. package/dist/components/preact/DropZone/drop-core/deploy.d.ts +31 -0
  15. package/dist/components/preact/DropZone/drop-core/deploy.js +36 -18
  16. package/dist/components/preact/DropZone/drop-core/detectBuild.d.ts +26 -0
  17. package/dist/components/preact/DropZone/drop-core/detectBuild.js +80 -53
  18. package/dist/components/preact/DropZone/drop-core/errors.d.ts +38 -0
  19. package/dist/components/preact/DropZone/drop-core/errors.js +50 -5
  20. package/dist/components/preact/DropZone/drop-core/index.d.ts +3 -0
  21. package/dist/components/preact/DropZone/drop-core/index.js +33 -18
  22. package/dist/components/preact/DropZone/drop-core/readFiles.js +1 -1
  23. package/dist/components/preact/DropZone/drop-core/types.d.ts +6 -1
  24. package/dist/components/preact/DropZone/drop-core/zip.d.ts +46 -0
  25. package/dist/components/preact/DropZone/drop-core/zip.js +43 -0
  26. package/dist/components/preact/DropZone/index.d.ts +3 -2
  27. package/dist/components/preact/DropZone/index.js +27 -11
  28. package/dist/components/preact/DropZone/useDropDeploy.d.ts +34 -4
  29. package/dist/components/preact/DropZone/useDropDeploy.js +138 -70
  30. package/dist/components/preact/index.d.ts +2 -1
  31. package/dist/components/preact/index.js +17 -12
  32. package/package.json +5 -2
  33. package/packages/components/ArticleCopyActions.astro +122 -0
  34. package/packages/components/articleCopyActions.test.ts +143 -0
  35. package/packages/components/articleCopyActions.ts +41 -0
  36. package/packages/components/index.ts +15 -0
  37. package/packages/components/preact/DropZone/DropZone.tsx +114 -20
  38. package/packages/components/preact/DropZone/drop-core/authedDropClient.ts +185 -0
  39. package/packages/components/preact/DropZone/drop-core/buildClient.ts +187 -0
  40. package/packages/components/preact/DropZone/drop-core/client.ts +1 -1
  41. package/packages/components/preact/DropZone/drop-core/deploy.ts +65 -0
  42. package/packages/components/preact/DropZone/drop-core/detectBuild.ts +125 -41
  43. package/packages/components/preact/DropZone/drop-core/errors.ts +84 -1
  44. package/packages/components/preact/DropZone/drop-core/index.ts +3 -0
  45. package/packages/components/preact/DropZone/drop-core/readFiles.ts +1 -1
  46. package/packages/components/preact/DropZone/drop-core/types.ts +12 -1
  47. package/packages/components/preact/DropZone/drop-core/zip.ts +118 -0
  48. package/packages/components/preact/DropZone/index.tsx +23 -0
  49. package/packages/components/preact/DropZone/useDropDeploy.ts +189 -30
  50. package/packages/components/preact/index.ts +2 -1
  51. package/dist/chunks/browser.D58rdxmc.js +0 -444
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Wire up the "Copy URL" / "Copy page" buttons rendered by ArticleCopyActions.astro.
3
+ *
4
+ * This runs on every `astro:page-load` (initial load *and* every client-side
5
+ * navigation via `<ClientRouter />`), so the buttons keep working after routing
6
+ * to another article/post. Each wrapper is guarded so re-initialising the same
7
+ * DOM node never attaches duplicate listeners.
8
+ */
9
+ export declare function initArticleCopyActions(root?: ParentNode): void;
@@ -0,0 +1 @@
1
+ export {};
@@ -5,6 +5,6 @@ export { default as SearchLink } from './preact/SearchModal/components/SearchLin
5
5
  export { default as SearchHeading } from './preact/SearchModal/components/SearchHeading/SearchHeading';
6
6
  export { default as SearchToggle } from './preact/SearchModal/components/SearchToggle/SearchToggle';
7
7
  export { default as SearchModal } from './preact/SearchModal/components/SearchModal/SearchModal';
8
- export { DropZone, AnonymousDropClient, deployFiles, readDataTransfer, readFileList, } from './preact/DropZone';
9
- export type { DropClient, DropResponse, Digest, DeployResult, DeployProgress, DeployPhase, ProcessedFile, } from './preact/DropZone';
8
+ export { DropZone, AnonymousDropClient, AuthenticatedBuildClient, AuthenticatedDropClient, deployBuild, deployFiles, detectBuild, inferBuildSettings, readDataTransfer, readFileList, zipFiles, } from './preact/DropZone';
9
+ export type { AuthenticatedBuildClientConfig, AuthenticatedDropClientConfig, BuildClient, BuildDeployResult, BuildDetection, BuildSite, DeployedBuild, DeployedSite, DropClient, DropResponse, Digest, DeployResult, DeployProgress, DeployPhase, InferredBuildSettings, ProcessedFile, } from './preact/DropZone';
10
10
  export { type IconName } from './preact/Icon';
@@ -1,6 +1,6 @@
1
1
  import { ComponentChildren } from 'preact';
2
- import { BuildDetection, DropClient } from './drop-core';
3
- import { DeployedSite, DeployError, TrackFn } from './useDropDeploy';
2
+ import { BuildClient, BuildDetection, DropClient } from './drop-core';
3
+ import { DeployedBuild, DeployedSite, DeployError, TrackFn } from './useDropDeploy';
4
4
  interface Props {
5
5
  /** Content the drop layer sits on top of. The zone is invisible until you drag over it. */
6
6
  children?: ComponentChildren;
@@ -10,23 +10,74 @@ interface Props {
10
10
  client?: DropClient;
11
11
  /** Build the "claim this site" URL from the assigned site name (subdomain). */
12
12
  claimUrl?: (siteName: string) => string;
13
+ /**
14
+ * Where an authenticated static deploy sends the visitor — the site already
15
+ * lives in their account, so there is no claim step. Defaults to the new
16
+ * site's project overview on app.netlify.com.
17
+ */
18
+ siteDashboardUrl?: (siteName: string) => string;
13
19
  /**
14
20
  * Where to send a build-required drop (framework source, build command,
15
- * functions) when `onBuildRequired` isn't set. Such projects can't deploy
16
- * through the anonymous static drop API, so by default the user is redirected
17
- * here — the app signup page with `next=/drop` — to build on the
18
- * authenticated Drop page. Override to point at your own destination.
21
+ * functions) that can't be built here — no `getUploadToken`/`buildClient`, or a
22
+ * visitor who isn't logged in. Such projects can't deploy through the anonymous
23
+ * static drop API, so by default the user is redirected here — the app signup
24
+ * page with `next=/drop` — to build on the authenticated Drop page. Override to
25
+ * point at your own destination.
19
26
  */
20
27
  buildSignupUrl?: string;
21
28
  /**
22
- * Called when a build-required drop is detected, instead of the default
23
- * `buildSignupUrl` redirect. Use it to own the hand-off yourself (custom
24
- * routing, persisting the drop, gating on auth). When set, the component does
25
- * not redirect. The static drop path is unaffected either way.
29
+ * Mint a short-lived bearer token for direct api.netlify.com uploads — pass it
30
+ * to switch on the whole authenticated path for logged-in visitors: static
31
+ * drops deploy straight into their account (then `siteDashboardUrl`), and
32
+ * build-required drops build in place instead of being handed off to the app.
33
+ * On netlify.com this reads `GET /access-control/generate-access-control-token`.
34
+ * Resolving `null` means "no session": static drops fall back to the anonymous
35
+ * claim flow, build drops to `buildSignupUrl`.
36
+ * See [Logged-in visitors](#logged-in-visitors).
37
+ */
38
+ getUploadToken?: () => Promise<string | null>;
39
+ /**
40
+ * Inject a custom `BuildClient` (tests, staging, a different account flow).
41
+ * Takes precedence over `getUploadToken` for the build path.
42
+ */
43
+ buildClient?: BuildClient;
44
+ /**
45
+ * Inject a custom authenticated `DropClient` for static drops (tests, staging,
46
+ * a different account flow). Takes precedence over `getUploadToken` for the
47
+ * static path. Distinct from `client`, which is the anonymous fallback.
48
+ */
49
+ authenticatedDropClient?: DropClient;
50
+ /** Create the visitor's new project in this account. Omit for their default account. */
51
+ accountSlug?: string;
52
+ /**
53
+ * Where an enqueued build sends the visitor. Defaults to the new site's
54
+ * project overview, same as a static deploy; `build.deployId` is available for
55
+ * consumers who want the deploy-log page instead.
56
+ */
57
+ buildDeployUrl?: (build: DeployedBuild) => string;
58
+ /**
59
+ * Poll until the build finishes before handing off, instead of redirecting as
60
+ * soon as it's enqueued. The trade-off: waiting means a failing build surfaces
61
+ * its own error message here (via `onError` and the overlay) rather than the
62
+ * visitor discovering it on the deploy page — at the cost of holding them on a
63
+ * spinner for the length of the build, which is minutes. Off by default.
64
+ */
65
+ waitForBuild?: boolean;
66
+ /**
67
+ * Called when a build-required drop is detected, instead of building it or
68
+ * redirecting. Use it to own the hand-off yourself (custom routing, persisting
69
+ * the drop, gating on auth). When set, the component neither builds nor
70
+ * redirects. The static drop path is unaffected either way.
26
71
  */
27
72
  onBuildRequired?: (info: BuildDetection) => void;
28
73
  /** Called once the deploy is live. */
29
74
  onDeploy?: (result: DeployedSite) => void;
75
+ /**
76
+ * Called once a build-required drop has been zipped, uploaded and enqueued —
77
+ * before the redirect to `buildDeployUrl`. The site exists at this point but
78
+ * won't serve content until the build finishes.
79
+ */
80
+ onBuildDeploy?: (build: DeployedBuild) => void;
30
81
  /**
31
82
  * Called when a deploy fails, with a humanized `message` (ready to show a
32
83
  * user — the same copy the built-in overlay uses) and the raw `reason`. Pair
@@ -48,10 +99,14 @@ interface Props {
48
99
  * Emitted events (all include `dropzone_id` when `analyticsId` is set):
49
100
  * - `dropzone_files_dropped` — files dropped via drag ({ method: 'drag', file_count, file_types })
50
101
  * - `dropzone_browse_opened` — file picker opened ({ method: 'keyboard' | 'click' })
51
- * - `dropzone_build_required` — drop needs a build; handed off to signup ({ method, reason, file_count, file_types })
52
- * - `dropzone_deploy_succeeded` — deploy went live ({ method, site_name, file_count, file_types })
53
- * - `dropzone_deploy_failed` — deploy errored out ({ method, reason, file_count?, file_types })
102
+ * - `dropzone_build_required` — drop needs a build (built here, or handed off) ({ method, reason, file_count, file_types })
103
+ * - `dropzone_deploy_succeeded` — static deploy went live ({ method, site_name, file_count, authenticated, auth_fallback?, file_types })
104
+ * - `dropzone_build_deploy_succeeded` — build enqueued for a logged-in visitor ({ method, reason, site_name, file_count, framework?, file_types })
105
+ * - `dropzone_deploy_failed` — deploy or build errored out ({ method, reason, file_count?, file_types })
54
106
  *
107
+ * `authenticated` says whether the deploy landed in the visitor's account;
108
+ * `auth_fallback: true` is sent only when an authenticated attempt bounced
109
+ * (stale auth hint) and the drop completed anonymously — omitted otherwise.
55
110
  * `file_types` is the sorted, de-duplicated list of MIME types in the
56
111
  * selection, with `application/octet-stream` standing in wherever the browser
57
112
  * reports none (a dragged folder, an `.exe`). It is omitted when the browser
@@ -68,5 +123,5 @@ interface Props {
68
123
  analyticsId?: string;
69
124
  className?: string;
70
125
  }
71
- export default function DropZone({ children, apiBase, client, claimUrl, buildSignupUrl, onBuildRequired, onDeploy, onError, showStatus, onTrack, analyticsId, className, }: Props): import("preact").JSX.Element;
126
+ export default function DropZone({ children, apiBase, client, claimUrl, siteDashboardUrl, buildSignupUrl, getUploadToken, buildClient, authenticatedDropClient, accountSlug, buildDeployUrl, waitForBuild, onBuildRequired, onDeploy, onBuildDeploy, onError, showStatus, onTrack, analyticsId, className, }: Props): import("preact").JSX.Element;
72
127
  export {};
@@ -1,159 +1,184 @@
1
1
  import { u as e } from "../../../chunks/jsxRuntime.module.Bzuv3cXw.js";
2
- import { A, d as I, y as P } from "../../../chunks/hooks.module.DRcxknIz.js";
3
- import { readFileList as U, readDataTransfer as j } from "./drop-core/readFiles.js";
2
+ import { A as j, d as V, y as G } from "../../../chunks/hooks.module.DRcxknIz.js";
3
+ import { readFileList as K, readDataTransfer as L } from "./drop-core/readFiles.js";
4
4
  import { getFileTypes as D } from "./drop-core/fileTypes.js";
5
5
  import "./drop-core/detectBuild.js";
6
- import { useDropDeploy as B } from "./useDropDeploy.js";
6
+ import "./drop-core/zip.js";
7
+ import { useDropDeploy as Z } from "./useDropDeploy.js";
7
8
  import { Fragment as w } from "preact";
8
- import '../../../assets/DropZone.css';const M = 120, N = (n) => `https://app.netlify.com/drop/${n}`, O = "https://app.netlify.com/signup?next=/drop&utm_campaign=drop";
9
- function W({
10
- children: n,
11
- apiBase: l,
12
- client: i,
13
- claimUrl: s = N,
14
- buildSignupUrl: u = O,
15
- onBuildRequired: T,
16
- onDeploy: C,
17
- onError: _,
9
+ import '../../../assets/DropZone.css';const q = 120, H = (r) => `https://app.netlify.com/drop/${r}`, J = (r) => `https://app.netlify.com/projects/${r}`, Q = "https://app.netlify.com/signup?next=/drop&utm_campaign=drop", W = ({ siteName: r }) => `https://app.netlify.com/projects/${r}`;
10
+ function se({
11
+ children: r,
12
+ apiBase: a,
13
+ client: l,
14
+ claimUrl: c = H,
15
+ siteDashboardUrl: u = J,
16
+ buildSignupUrl: A = Q,
17
+ getUploadToken: T,
18
+ buildClient: C,
19
+ authenticatedDropClient: S,
20
+ accountSlug: _,
21
+ buildDeployUrl: $ = W,
22
+ waitForBuild: B,
23
+ onBuildRequired: U,
24
+ onDeploy: P,
25
+ onBuildDeploy: R,
26
+ onError: x,
18
27
  showStatus: f = !0,
19
28
  onTrack: d,
20
29
  analyticsId: g,
21
- className: S
30
+ className: E
22
31
  }) {
23
- const m = (r, t = {}) => d == null ? void 0 : d(r, g ? { dropzone_id: g, ...t } : t), { status: a, warning: h, busy: o, deploy: v, reset: R } = B({
24
- apiBase: l,
25
- client: i,
26
- claimUrl: s,
27
- buildSignupUrl: u,
28
- onBuildRequired: T,
29
- onDeploy: C,
30
- onError: _,
31
- track: m
32
- }), { active: p, keepActive: $, endDrag: x } = V(), y = A(null), b = (r) => {
33
- var t;
34
- m("dropzone_browse_opened", { method: r }), (t = y.current) == null || t.click();
35
- }, E = f && (p || o || a.kind === "error"), F = [
32
+ const y = (t, n = {}) => d == null ? void 0 : d(t, g ? { dropzone_id: g, ...n } : n), { status: s, warning: b, busy: i, deploy: m, reset: F } = Z({
33
+ apiBase: a,
34
+ client: l,
35
+ claimUrl: c,
36
+ siteDashboardUrl: u,
37
+ buildSignupUrl: A,
38
+ getUploadToken: T,
39
+ buildClient: C,
40
+ authenticatedDropClient: S,
41
+ accountSlug: _,
42
+ buildDeployUrl: $,
43
+ waitForBuild: B,
44
+ onBuildRequired: U,
45
+ onDeploy: P,
46
+ onBuildDeploy: R,
47
+ onError: x,
48
+ track: y
49
+ }), { active: p, keepActive: I, endDrag: M } = X(), h = j(null), v = (t) => {
50
+ var n;
51
+ y("dropzone_browse_opened", { method: t }), (n = h.current) == null || n.click();
52
+ }, N = f && (p || i || s.kind === "error"), O = [
36
53
  "n-dropzone",
37
54
  p && "is-active",
38
- o && "is-busy",
55
+ i && "is-busy",
39
56
  !f && "is-bare",
40
- S
57
+ E
41
58
  ].filter(Boolean).join(" ");
42
59
  return /* @__PURE__ */ e(
43
60
  "div",
44
61
  {
45
- class: F,
46
- "data-state": a.kind,
62
+ class: O,
63
+ "data-state": s.kind,
47
64
  role: "button",
48
- tabIndex: o ? -1 : 0,
65
+ tabIndex: i ? -1 : 0,
49
66
  "aria-label": "Drop a site folder or zip here to deploy it, or press Enter to browse for files",
50
- "aria-busy": o,
51
- onDragOver: (r) => {
52
- o || (r.preventDefault(), $());
67
+ "aria-busy": i,
68
+ onDragOver: (t) => {
69
+ i || (t.preventDefault(), I());
53
70
  },
54
- onDrop: (r) => {
55
- if (r.preventDefault(), x(), o) return;
56
- const t = r.dataTransfer;
57
- t && v(() => j(t), "drag", D(t.files));
71
+ onDrop: (t) => {
72
+ if (t.preventDefault(), M(), i) return;
73
+ const n = t.dataTransfer;
74
+ n && m(() => L(n), "drag", D(n.files));
58
75
  },
59
- onClick: (r) => {
60
- !o && r.target === r.currentTarget && b("click");
76
+ onClick: (t) => {
77
+ !i && t.target === t.currentTarget && v("click");
61
78
  },
62
- onKeyDown: (r) => {
63
- o || (r.key === "Enter" || r.key === " ") && (r.preventDefault(), b("keyboard"));
79
+ onKeyDown: (t) => {
80
+ i || (t.key === "Enter" || t.key === " ") && (t.preventDefault(), v("keyboard"));
64
81
  },
65
82
  children: [
66
- /* @__PURE__ */ e("div", { class: "n-dropzone-content", children: n }),
83
+ /* @__PURE__ */ e("div", { class: "n-dropzone-content", children: r }),
67
84
  /* @__PURE__ */ e(
68
85
  "input",
69
86
  {
70
- ref: y,
87
+ ref: h,
71
88
  type: "file",
72
89
  hidden: !0,
73
90
  multiple: !0,
74
91
  webkitdirectory: "",
75
92
  directory: "",
76
- onChange: (r) => {
77
- var z;
78
- const t = r.currentTarget;
79
- if ((z = t.files) != null && z.length) {
80
- const k = t.files;
81
- v(() => U(k), "browse", D(k));
93
+ onChange: (t) => {
94
+ var k;
95
+ const n = t.currentTarget;
96
+ if ((k = n.files) != null && k.length) {
97
+ const z = n.files;
98
+ m(() => K(z), "browse", D(z));
82
99
  }
83
- t.value = "";
100
+ n.value = "";
84
101
  }
85
102
  }
86
103
  ),
87
- E && /* @__PURE__ */ e(
104
+ N && /* @__PURE__ */ e(
88
105
  "div",
89
106
  {
90
107
  class: "n-dropzone-overlay n-fadeIn",
91
108
  children: [
92
- /* @__PURE__ */ e(G, { status: a, active: p, onReset: R }),
93
- h && a.kind !== "error" && o && /* @__PURE__ */ e("p", { class: "n-dropzone-warning", role: "status", children: h })
109
+ /* @__PURE__ */ e(Y, { status: s, active: p, onReset: F }),
110
+ b && s.kind !== "error" && i && /* @__PURE__ */ e("p", { class: "n-dropzone-warning", role: "status", children: b })
94
111
  ]
95
112
  },
96
- a.kind === "idle" ? "active" : a.kind
113
+ s.kind === "idle" ? "active" : s.kind
97
114
  )
98
115
  ]
99
116
  }
100
117
  );
101
118
  }
102
- function V() {
103
- const [n, l] = I(!1), i = A();
104
- return P(() => () => clearTimeout(i.current), []), { active: n, keepActive: () => {
105
- l(!0), clearTimeout(i.current), i.current = setTimeout(() => l(!1), M);
119
+ function X() {
120
+ const [r, a] = V(!1), l = j();
121
+ return G(() => () => clearTimeout(l.current), []), { active: r, keepActive: () => {
122
+ a(!0), clearTimeout(l.current), l.current = setTimeout(() => a(!1), q);
106
123
  }, endDrag: () => {
107
- clearTimeout(i.current), l(!1);
124
+ clearTimeout(l.current), a(!1);
108
125
  } };
109
126
  }
110
- function G({
111
- status: n,
112
- active: l,
113
- onReset: i
127
+ function Y({
128
+ status: r,
129
+ active: a,
130
+ onReset: l
114
131
  }) {
115
- switch (n.kind) {
132
+ switch (r.kind) {
116
133
  // Idle-but-active means a drag is hovering the zone.
117
134
  case "idle":
118
- return l ? /* @__PURE__ */ e("p", { class: "n-dropzone-hint", children: "Drop to deploy" }) : null;
135
+ return a ? /* @__PURE__ */ e("p", { class: "n-dropzone-hint", children: "Drop to deploy" }) : null;
119
136
  case "reading":
120
- return /* @__PURE__ */ e(c, { label: "Reading files…" });
137
+ return /* @__PURE__ */ e(o, { label: "Reading files…" });
138
+ case "zipping":
139
+ return /* @__PURE__ */ e(o, { label: "Packaging your project…" });
121
140
  case "creating":
122
- return /* @__PURE__ */ e(c, { label: "Creating deploy…" });
141
+ return /* @__PURE__ */ e(o, { label: "Creating deploy…" });
123
142
  case "processing":
124
- return /* @__PURE__ */ e(c, { label: "Processing — almost live…" });
143
+ return /* @__PURE__ */ e(o, { label: "Processing — almost live…" });
144
+ case "building":
145
+ return /* @__PURE__ */ e(o, { label: "Building your project…" });
125
146
  case "redirecting":
126
- return /* @__PURE__ */ e(
127
- c,
128
- {
129
- label: n.target === "signup" ? "This project needs a build — taking you to Netlify…" : "Site’s live — taking you to claim it…"
130
- }
131
- );
147
+ switch (r.target) {
148
+ case "signup":
149
+ return /* @__PURE__ */ e(o, { label: "This project needs a build — taking you to Netlify…" });
150
+ case "build":
151
+ return /* @__PURE__ */ e(o, { label: "Build started — taking you to your new project…" });
152
+ case "claim":
153
+ return /* @__PURE__ */ e(o, { label: "Site’s live — taking you to claim it…" });
154
+ case "dashboard":
155
+ return /* @__PURE__ */ e(o, { label: "Site’s live — taking you to your new project…" });
156
+ }
132
157
  case "uploading": {
133
- const s = n.total ? Math.round(n.uploaded / n.total * 100) : 0;
158
+ const c = r.total ? Math.round(r.uploaded / r.total * 100) : 0;
134
159
  return /* @__PURE__ */ e(w, { children: [
135
- /* @__PURE__ */ e(c, { label: `Uploading ${n.uploaded}/${n.total} files…` }),
136
- /* @__PURE__ */ e("div", { class: "n-dropzone-progress", role: "progressbar", "aria-valuenow": s, children: /* @__PURE__ */ e("div", { class: "n-dropzone-progress-bar", style: { width: `${s}%` } }) })
160
+ /* @__PURE__ */ e(o, { label: `Uploading ${r.uploaded}/${r.total} files…` }),
161
+ /* @__PURE__ */ e("div", { class: "n-dropzone-progress", role: "progressbar", "aria-valuenow": c, children: /* @__PURE__ */ e("div", { class: "n-dropzone-progress-bar", style: { width: `${c}%` } }) })
137
162
  ] });
138
163
  }
139
164
  case "error":
140
165
  return /* @__PURE__ */ e(w, { children: [
141
166
  /* @__PURE__ */ e("p", { class: "n-dropzone-title n-dropzone-title--error", children: "Something went wrong" }),
142
- /* @__PURE__ */ e("p", { class: "n-dropzone-subtitle", role: "alert", children: n.message }),
143
- /* @__PURE__ */ e("button", { type: "button", class: "n-dropzone-browse", onClick: i, children: "Try again" })
167
+ /* @__PURE__ */ e("p", { class: "n-dropzone-subtitle", role: "alert", children: r.message }),
168
+ /* @__PURE__ */ e("button", { type: "button", class: "n-dropzone-browse", onClick: l, children: "Try again" })
144
169
  ] });
145
170
  }
146
171
  }
147
- function c({ label: n }) {
172
+ function o({ label: r }) {
148
173
  return /* @__PURE__ */ e("div", { class: "n-dropzone-loading", children: [
149
174
  /* @__PURE__ */ e("span", { class: "n-dropzone-spinner", "aria-hidden": "true" }),
150
175
  /* @__PURE__ */ e("p", { class: "n-dropzone-subtitle", "aria-live": "polite", children: [
151
- n,
176
+ r,
152
177
  " ",
153
178
  /* @__PURE__ */ e("span", { class: "n-dropzone-nudge", children: "Please don’t refresh." })
154
179
  ] })
155
180
  ] });
156
181
  }
157
182
  export {
158
- W as default
183
+ se as default
159
184
  };
@@ -0,0 +1,77 @@
1
+ import { DropClient } from './client';
2
+ import { Digest, DropResponse } from './types';
3
+ export interface AuthenticatedDropClientConfig {
4
+ /**
5
+ * API base for every call. Requests go straight to `api.netlify.com`, whose
6
+ * `/api/*` CORS policy allows any origin, rather than through a consumer's
7
+ * access-control rewrite — that proxy allowlists paths per origin, and site
8
+ * and deploy creation are not on the list for netlify.com (they 400 with
9
+ * `x-debug: request origin not allowed to call endpoint`).
10
+ */
11
+ apiBase?: string;
12
+ /**
13
+ * Mint a short-lived bearer token authorizing the calls. On netlify.com this
14
+ * is `GET /access-control/generate-access-control-token`, whose
15
+ * `accessControlToken` the API decrypts back into the user's access token.
16
+ * Resolving `null` means "no session" and surfaces as `NotAuthenticatedError`.
17
+ */
18
+ getUploadToken: () => Promise<string | null>;
19
+ /** Create the site in this account. Omit to use the user's default account. */
20
+ accountSlug?: string;
21
+ /** How often to re-read the deploy while waiting. Default 1s. */
22
+ pollIntervalMs?: number;
23
+ /** How long to wait before giving up on the deploy. Default 10 minutes. */
24
+ timeoutMs?: number;
25
+ }
26
+ /**
27
+ * Authenticated drop client for logged-in visitors. Unlike `AnonymousDropClient`
28
+ * — which creates an account-less site the visitor must later claim — this
29
+ * deploys straight into the visitor's account via the sites/deploys API,
30
+ * mirroring what app.netlify.com does for a logged-in drag-and-drop deploy.
31
+ * There is no claim step, so the caller should hand off to the site's dashboard
32
+ * page rather than the claim page.
33
+ *
34
+ * Every request goes direct to `api.netlify.com` with the short-lived bearer
35
+ * from `getUploadToken()`. Routing the JSON calls through a consumer's
36
+ * access-control rewrite instead (cookie session, no token in the browser)
37
+ * would be the tighter design, but that proxy allowlists paths per origin and
38
+ * site/deploy creation isn't allowed from netlify.com — it 400s before
39
+ * reaching the API. Revisit if that allowlist gains these paths.
40
+ *
41
+ * Error-mapping invariant: only pre-mutation failures — a null upload token, or
42
+ * a 401/403 creating the site — throw `NotAuthenticatedError`, which is the
43
+ * signal the drop flow may safely retry anonymously. Once the site exists, an
44
+ * auth failure throws a plain status-carrying error instead: falling back at
45
+ * that point would strand an empty site in the account *and* deploy a duplicate
46
+ * anonymously.
47
+ */
48
+ export declare class AuthenticatedDropClient implements DropClient {
49
+ private apiBase;
50
+ private getUploadToken;
51
+ private accountSlug?;
52
+ private pollIntervalMs;
53
+ private timeoutMs;
54
+ constructor({ apiBase, getUploadToken, accountSlug, pollIntervalMs, timeoutMs, }: AuthenticatedDropClientConfig);
55
+ /**
56
+ * The token here is the upload bearer, not a drop token — it authorizes every
57
+ * call this client makes. Minted once per deploy, before anything is created,
58
+ * so a missing session fails the whole attempt cleanly.
59
+ */
60
+ getToken(): Promise<string>;
61
+ /**
62
+ * Two calls: create the site in the visitor's account, then the deploy on it.
63
+ * Mapped into the anonymous `DropResponse` shape so `deployFiles` runs
64
+ * unchanged over either client.
65
+ */
66
+ createDeploy(files: Digest, token: string): Promise<DropResponse>;
67
+ uploadFile(deployId: string, path: string, content: ArrayBuffer, token: string): Promise<void>;
68
+ /**
69
+ * Poll until the deploy settles: resolve on `ready`, throw on
70
+ * `error`/`rejected`, throw on timeout. Reuses the bearer minted at the start
71
+ * of the deploy, so a deploy that outlives the token's TTL will start seeing
72
+ * 401s here — those are ignored like any other non-ok read and the loop runs
73
+ * to its timeout rather than failing fast. Acceptable while static deploys
74
+ * finish in seconds; re-mint per poll if that stops being true.
75
+ */
76
+ waitUntilReady(deploy: DropResponse, token: string): Promise<void>;
77
+ }
@@ -0,0 +1,97 @@
1
+ var c = Object.defineProperty;
2
+ var h = (r, t, e) => t in r ? c(r, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : r[t] = e;
3
+ var n = (r, t, e) => h(r, typeof t != "symbol" ? t + "" : t, e);
4
+ import { apiError as l } from "./client.js";
5
+ import { NotAuthenticatedError as p, SiteCreateError as u } from "./errors.js";
6
+ const y = "https://api.netlify.com/api/v1", w = 1e3, f = 10 * 60 * 1e3, m = /* @__PURE__ */ new Set(["ready", "error", "rejected"]);
7
+ class g {
8
+ constructor({
9
+ apiBase: t = y,
10
+ getUploadToken: e,
11
+ accountSlug: i,
12
+ pollIntervalMs: o = w,
13
+ timeoutMs: s = f
14
+ }) {
15
+ n(this, "apiBase");
16
+ n(this, "getUploadToken");
17
+ n(this, "accountSlug");
18
+ n(this, "pollIntervalMs");
19
+ n(this, "timeoutMs");
20
+ this.apiBase = t, this.getUploadToken = e, this.accountSlug = i, this.pollIntervalMs = o, this.timeoutMs = s;
21
+ }
22
+ /**
23
+ * The token here is the upload bearer, not a drop token — it authorizes every
24
+ * call this client makes. Minted once per deploy, before anything is created,
25
+ * so a missing session fails the whole attempt cleanly.
26
+ */
27
+ async getToken() {
28
+ const t = await this.getUploadToken();
29
+ if (!t) throw new p();
30
+ return t;
31
+ }
32
+ /**
33
+ * Two calls: create the site in the visitor's account, then the deploy on it.
34
+ * Mapped into the anonymous `DropResponse` shape so `deployFiles` runs
35
+ * unchanged over either client.
36
+ */
37
+ async createDeploy(t, e) {
38
+ const i = this.accountSlug ? `/${this.accountSlug}/sites` : "/sites", o = await fetch(`${this.apiBase}${i}`, {
39
+ method: "POST",
40
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${e}` },
41
+ body: JSON.stringify({ created_via: "drop" })
42
+ });
43
+ if (o.status === 401 || o.status === 403) throw new p();
44
+ if (!o.ok) throw new u(o.status);
45
+ const s = await o.json(), a = await fetch(`${this.apiBase}/sites/${s.id}/deploys`, {
46
+ method: "POST",
47
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${e}` },
48
+ body: JSON.stringify({ files: t, deploy_source: "drop" })
49
+ });
50
+ if (!a.ok) throw l("deploy create", a.status);
51
+ const d = await a.json();
52
+ return {
53
+ id: s.id,
54
+ deploy_id: d.id,
55
+ subdomain: s.name,
56
+ required: d.required ?? []
57
+ };
58
+ }
59
+ async uploadFile(t, e, i, o) {
60
+ const s = await fetch(`${this.apiBase}/deploys/${t}/files${e}`, {
61
+ method: "PUT",
62
+ headers: { "Content-Type": "application/octet-stream", Authorization: `Bearer ${o}` },
63
+ body: i
64
+ });
65
+ if (!s.ok) throw l(`upload ${e}`, s.status);
66
+ }
67
+ /**
68
+ * Poll until the deploy settles: resolve on `ready`, throw on
69
+ * `error`/`rejected`, throw on timeout. Reuses the bearer minted at the start
70
+ * of the deploy, so a deploy that outlives the token's TTL will start seeing
71
+ * 401s here — those are ignored like any other non-ok read and the loop runs
72
+ * to its timeout rather than failing fast. Acceptable while static deploys
73
+ * finish in seconds; re-mint per poll if that stops being true.
74
+ */
75
+ async waitUntilReady(t, e) {
76
+ const i = this.timeoutMs / this.pollIntervalMs;
77
+ for (let o = 0; o < i; o++) {
78
+ const s = await fetch(`${this.apiBase}/deploys/${t.deploy_id}`, {
79
+ headers: { Authorization: `Bearer ${e}` }
80
+ });
81
+ if (s.ok) {
82
+ const a = await s.json();
83
+ if (a.state && m.has(a.state)) {
84
+ if (a.state === "ready") return;
85
+ throw new Error(
86
+ a.error_message ? `deploy failed: ${a.error_message}` : "deploy failed"
87
+ );
88
+ }
89
+ }
90
+ await new Promise((a) => setTimeout(a, this.pollIntervalMs));
91
+ }
92
+ throw new Error("Deploy did not become ready in time");
93
+ }
94
+ }
95
+ export {
96
+ g as AuthenticatedDropClient
97
+ };