@netlify/spark-ui 1.31.0-alpha.1 → 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.
@@ -47,13 +47,6 @@ interface Props {
47
47
  * static path. Distinct from `client`, which is the anonymous fallback.
48
48
  */
49
49
  authenticatedDropClient?: DropClient;
50
- /**
51
- * API root for the authenticated paths' JSON calls, which go through your
52
- * access-control rewrite so the session cookie authorizes them. Defaults to
53
- * `/access-control/bb-api/api/v1`. (Uploads always go direct to
54
- * `api.netlify.com` — the proxy can't carry bodies that large.)
55
- */
56
- proxyBase?: string;
57
50
  /** Create the visitor's new project in this account. Omit for their default account. */
58
51
  accountSlug?: string;
59
52
  /**
@@ -130,5 +123,5 @@ interface Props {
130
123
  analyticsId?: string;
131
124
  className?: string;
132
125
  }
133
- export default function DropZone({ children, apiBase, client, claimUrl, siteDashboardUrl, buildSignupUrl, getUploadToken, buildClient, authenticatedDropClient, proxyBase, accountSlug, buildDeployUrl, waitForBuild, onBuildRequired, onDeploy, onBuildDeploy, 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;
134
127
  export {};
@@ -1,36 +1,35 @@
1
1
  import { u as e } from "../../../chunks/jsxRuntime.module.Bzuv3cXw.js";
2
- import { A as j, d as G, y as K } from "../../../chunks/hooks.module.DRcxknIz.js";
3
- import { readFileList as L, readDataTransfer as Z } 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
6
  import "./drop-core/zip.js";
7
- import { useDropDeploy as q } from "./useDropDeploy.js";
7
+ import { useDropDeploy as Z } from "./useDropDeploy.js";
8
8
  import { Fragment as w } from "preact";
9
- import '../../../assets/DropZone.css';const H = 120, J = (r) => `https://app.netlify.com/drop/${r}`, Q = (r) => `https://app.netlify.com/projects/${r}`, W = "https://app.netlify.com/signup?next=/drop&utm_campaign=drop", X = ({ siteName: r }) => `https://app.netlify.com/projects/${r}`;
10
- function ce({
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
11
  children: r,
12
12
  apiBase: a,
13
13
  client: l,
14
- claimUrl: c = J,
15
- siteDashboardUrl: u = Q,
16
- buildSignupUrl: A = W,
14
+ claimUrl: c = H,
15
+ siteDashboardUrl: u = J,
16
+ buildSignupUrl: A = Q,
17
17
  getUploadToken: T,
18
18
  buildClient: C,
19
19
  authenticatedDropClient: S,
20
- proxyBase: _,
21
- accountSlug: $,
22
- buildDeployUrl: B = X,
23
- waitForBuild: U,
24
- onBuildRequired: P,
25
- onDeploy: R,
26
- onBuildDeploy: x,
27
- onError: E,
20
+ accountSlug: _,
21
+ buildDeployUrl: $ = W,
22
+ waitForBuild: B,
23
+ onBuildRequired: U,
24
+ onDeploy: P,
25
+ onBuildDeploy: R,
26
+ onError: x,
28
27
  showStatus: f = !0,
29
28
  onTrack: d,
30
29
  analyticsId: g,
31
- className: F
30
+ className: E
32
31
  }) {
33
- 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: I } = q({
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({
34
33
  apiBase: a,
35
34
  client: l,
36
35
  claimUrl: c,
@@ -39,41 +38,40 @@ function ce({
39
38
  getUploadToken: T,
40
39
  buildClient: C,
41
40
  authenticatedDropClient: S,
42
- proxyBase: _,
43
- accountSlug: $,
44
- buildDeployUrl: B,
45
- waitForBuild: U,
46
- onBuildRequired: P,
47
- onDeploy: R,
48
- onBuildDeploy: x,
49
- onError: E,
41
+ accountSlug: _,
42
+ buildDeployUrl: $,
43
+ waitForBuild: B,
44
+ onBuildRequired: U,
45
+ onDeploy: P,
46
+ onBuildDeploy: R,
47
+ onError: x,
50
48
  track: y
51
- }), { active: p, keepActive: M, endDrag: N } = Y(), h = j(null), v = (t) => {
49
+ }), { active: p, keepActive: I, endDrag: M } = X(), h = j(null), v = (t) => {
52
50
  var n;
53
51
  y("dropzone_browse_opened", { method: t }), (n = h.current) == null || n.click();
54
- }, O = f && (p || i || s.kind === "error"), V = [
52
+ }, N = f && (p || i || s.kind === "error"), O = [
55
53
  "n-dropzone",
56
54
  p && "is-active",
57
55
  i && "is-busy",
58
56
  !f && "is-bare",
59
- F
57
+ E
60
58
  ].filter(Boolean).join(" ");
61
59
  return /* @__PURE__ */ e(
62
60
  "div",
63
61
  {
64
- class: V,
62
+ class: O,
65
63
  "data-state": s.kind,
66
64
  role: "button",
67
65
  tabIndex: i ? -1 : 0,
68
66
  "aria-label": "Drop a site folder or zip here to deploy it, or press Enter to browse for files",
69
67
  "aria-busy": i,
70
68
  onDragOver: (t) => {
71
- i || (t.preventDefault(), M());
69
+ i || (t.preventDefault(), I());
72
70
  },
73
71
  onDrop: (t) => {
74
- if (t.preventDefault(), N(), i) return;
72
+ if (t.preventDefault(), M(), i) return;
75
73
  const n = t.dataTransfer;
76
- n && m(() => Z(n), "drag", D(n.files));
74
+ n && m(() => L(n), "drag", D(n.files));
77
75
  },
78
76
  onClick: (t) => {
79
77
  !i && t.target === t.currentTarget && v("click");
@@ -97,18 +95,18 @@ function ce({
97
95
  const n = t.currentTarget;
98
96
  if ((k = n.files) != null && k.length) {
99
97
  const z = n.files;
100
- m(() => L(z), "browse", D(z));
98
+ m(() => K(z), "browse", D(z));
101
99
  }
102
100
  n.value = "";
103
101
  }
104
102
  }
105
103
  ),
106
- O && /* @__PURE__ */ e(
104
+ N && /* @__PURE__ */ e(
107
105
  "div",
108
106
  {
109
107
  class: "n-dropzone-overlay n-fadeIn",
110
108
  children: [
111
- /* @__PURE__ */ e(ee, { status: s, active: p, onReset: I }),
109
+ /* @__PURE__ */ e(Y, { status: s, active: p, onReset: F }),
112
110
  b && s.kind !== "error" && i && /* @__PURE__ */ e("p", { class: "n-dropzone-warning", role: "status", children: b })
113
111
  ]
114
112
  },
@@ -118,15 +116,15 @@ function ce({
118
116
  }
119
117
  );
120
118
  }
121
- function Y() {
122
- const [r, a] = G(!1), l = j();
123
- return K(() => () => clearTimeout(l.current), []), { active: r, keepActive: () => {
124
- a(!0), clearTimeout(l.current), l.current = setTimeout(() => a(!1), H);
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);
125
123
  }, endDrag: () => {
126
124
  clearTimeout(l.current), a(!1);
127
125
  } };
128
126
  }
129
- function ee({
127
+ function Y({
130
128
  status: r,
131
129
  active: a,
132
130
  onReset: l
@@ -182,5 +180,5 @@ function o({ label: r }) {
182
180
  ] });
183
181
  }
184
182
  export {
185
- ce as default
183
+ se as default
186
184
  };
@@ -2,21 +2,16 @@ import { DropClient } from './client';
2
2
  import { Digest, DropResponse } from './types';
3
3
  export interface AuthenticatedDropClientConfig {
4
4
  /**
5
- * Base for JSON API calls, routed through the consumer's access-control
6
- * rewrite so the session cookie authenticates them. On netlify.com that
7
- * rewrite is `/access-control/*` → the app's access-control function, which
8
- * makes `/access-control/bb-api/api/v1` the API root (the default here).
9
- */
10
- proxyBase?: string;
11
- /**
12
- * Direct API base for the per-file uploads. The proxy above runs in a Lambda
13
- * with a ~6MB request body limit, so file PUTs go straight to
14
- * `api.netlify.com` (whose `/api/*` CORS policy allows any origin).
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`).
15
10
  */
16
11
  apiBase?: string;
17
12
  /**
18
- * Mint a short-lived bearer token for those direct uploads. On netlify.com
19
- * this is `GET /access-control/generate-access-control-token`, whose
13
+ * Mint a short-lived bearer token authorizing the calls. On netlify.com this
14
+ * is `GET /access-control/generate-access-control-token`, whose
20
15
  * `accessControlToken` the API decrypts back into the user's access token.
21
16
  * Resolving `null` means "no session" and surfaces as `NotAuthenticatedError`.
22
17
  */
@@ -36,11 +31,12 @@ export interface AuthenticatedDropClientConfig {
36
31
  * There is no claim step, so the caller should hand off to the site's dashboard
37
32
  * page rather than the claim page.
38
33
  *
39
- * Requests split across two hosts by necessity: JSON calls go through the
40
- * consumer's access-control proxy (cookie session, no token handling in the
41
- * browser), while the per-file PUTs go direct to `api.netlify.com` with the
42
- * short-lived bearer from `getUploadToken()` because the proxy can't carry
43
- * large bodies.
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.
44
40
  *
45
41
  * Error-mapping invariant: only pre-mutation failures — a null upload token, or
46
42
  * a 401/403 creating the site — throw `NotAuthenticatedError`, which is the
@@ -50,31 +46,32 @@ export interface AuthenticatedDropClientConfig {
50
46
  * anonymously.
51
47
  */
52
48
  export declare class AuthenticatedDropClient implements DropClient {
53
- private proxyBase;
54
49
  private apiBase;
55
50
  private getUploadToken;
56
51
  private accountSlug?;
57
52
  private pollIntervalMs;
58
53
  private timeoutMs;
59
- constructor({ proxyBase, apiBase, getUploadToken, accountSlug, pollIntervalMs, timeoutMs, }: AuthenticatedDropClientConfig);
54
+ constructor({ apiBase, getUploadToken, accountSlug, pollIntervalMs, timeoutMs, }: AuthenticatedDropClientConfig);
60
55
  /**
61
- * The token here is the upload bearer, not a drop token — it authorizes the
62
- * direct-API file PUTs, while the proxied JSON calls ignore it and ride the
63
- * session cookie. Minted once per deploy, before anything is created, so a
64
- * missing session fails the whole attempt cleanly.
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.
65
59
  */
66
60
  getToken(): Promise<string>;
67
61
  /**
68
- * Two proxied calls: create the site in the visitor's account, then the
69
- * deploy on it. Mapped into the anonymous `DropResponse` shape so
70
- * `deployFiles` runs unchanged over either client.
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.
71
65
  */
72
- createDeploy(files: Digest, _token: string): Promise<DropResponse>;
66
+ createDeploy(files: Digest, token: string): Promise<DropResponse>;
73
67
  uploadFile(deployId: string, path: string, content: ArrayBuffer, token: string): Promise<void>;
74
68
  /**
75
- * Poll the deploy through the proxy until it settles — the session cookie
76
- * outlives the short-lived upload bearer, so a slow deploy can't expire its
77
- * own poll. Resolve on `ready`, throw on `error`/`rejected`, throw on timeout.
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.
78
75
  */
79
- waitUntilReady(deploy: DropResponse, _token: string): Promise<void>;
76
+ waitUntilReady(deploy: DropResponse, token: string): Promise<void>;
80
77
  }
@@ -1,95 +1,93 @@
1
- var p = Object.defineProperty;
2
- var h = (n, t, s) => t in n ? p(n, t, { enumerable: !0, configurable: !0, writable: !0, value: s }) : n[t] = s;
3
- var r = (n, t, s) => h(n, typeof t != "symbol" ? t + "" : t, s);
4
- import { apiError as d } from "./client.js";
5
- import { NotAuthenticatedError as l, SiteCreateError as y } from "./errors.js";
6
- const u = "/access-control/bb-api/api/v1", w = "https://api.netlify.com/api/v1", f = 1e3, m = 10 * 60 * 1e3, T = /* @__PURE__ */ new Set(["ready", "error", "rejected"]);
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
7
  class g {
8
8
  constructor({
9
- proxyBase: t = u,
10
- apiBase: s = w,
11
- getUploadToken: i,
12
- accountSlug: o,
13
- pollIntervalMs: a = f,
14
- timeoutMs: e = m
9
+ apiBase: t = y,
10
+ getUploadToken: e,
11
+ accountSlug: i,
12
+ pollIntervalMs: o = w,
13
+ timeoutMs: s = f
15
14
  }) {
16
- r(this, "proxyBase");
17
- r(this, "apiBase");
18
- r(this, "getUploadToken");
19
- r(this, "accountSlug");
20
- r(this, "pollIntervalMs");
21
- r(this, "timeoutMs");
22
- this.proxyBase = t, this.apiBase = s, this.getUploadToken = i, this.accountSlug = o, this.pollIntervalMs = a, this.timeoutMs = e;
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;
23
21
  }
24
22
  /**
25
- * The token here is the upload bearer, not a drop token — it authorizes the
26
- * direct-API file PUTs, while the proxied JSON calls ignore it and ride the
27
- * session cookie. Minted once per deploy, before anything is created, so a
28
- * missing session fails the whole attempt cleanly.
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.
29
26
  */
30
27
  async getToken() {
31
28
  const t = await this.getUploadToken();
32
- if (!t) throw new l();
29
+ if (!t) throw new p();
33
30
  return t;
34
31
  }
35
32
  /**
36
- * Two proxied calls: create the site in the visitor's account, then the
37
- * deploy on it. Mapped into the anonymous `DropResponse` shape so
38
- * `deployFiles` runs unchanged over either client.
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.
39
36
  */
40
- async createDeploy(t, s) {
41
- const i = this.accountSlug ? `/${this.accountSlug}/sites` : "/sites", o = await fetch(`${this.proxyBase}${i}`, {
37
+ async createDeploy(t, e) {
38
+ const i = this.accountSlug ? `/${this.accountSlug}/sites` : "/sites", o = await fetch(`${this.apiBase}${i}`, {
42
39
  method: "POST",
43
- credentials: "include",
44
- headers: { "Content-Type": "application/json" },
40
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${e}` },
45
41
  body: JSON.stringify({ created_via: "drop" })
46
42
  });
47
- if (o.status === 401 || o.status === 403) throw new l();
48
- if (!o.ok) throw new y(o.status);
49
- const a = await o.json(), e = await fetch(`${this.proxyBase}/sites/${a.id}/deploys`, {
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`, {
50
46
  method: "POST",
51
- credentials: "include",
52
- headers: { "Content-Type": "application/json" },
47
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${e}` },
53
48
  body: JSON.stringify({ files: t, deploy_source: "drop" })
54
49
  });
55
- if (!e.ok) throw d("deploy create", e.status);
56
- const c = await e.json();
50
+ if (!a.ok) throw l("deploy create", a.status);
51
+ const d = await a.json();
57
52
  return {
58
- id: a.id,
59
- deploy_id: c.id,
60
- subdomain: a.name,
61
- required: c.required ?? []
53
+ id: s.id,
54
+ deploy_id: d.id,
55
+ subdomain: s.name,
56
+ required: d.required ?? []
62
57
  };
63
58
  }
64
- async uploadFile(t, s, i, o) {
65
- const a = await fetch(`${this.apiBase}/deploys/${t}/files${s}`, {
59
+ async uploadFile(t, e, i, o) {
60
+ const s = await fetch(`${this.apiBase}/deploys/${t}/files${e}`, {
66
61
  method: "PUT",
67
62
  headers: { "Content-Type": "application/octet-stream", Authorization: `Bearer ${o}` },
68
63
  body: i
69
64
  });
70
- if (!a.ok) throw d(`upload ${s}`, a.status);
65
+ if (!s.ok) throw l(`upload ${e}`, s.status);
71
66
  }
72
67
  /**
73
- * Poll the deploy through the proxy until it settles — the session cookie
74
- * outlives the short-lived upload bearer, so a slow deploy can't expire its
75
- * own poll. Resolve on `ready`, throw on `error`/`rejected`, throw on timeout.
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.
76
74
  */
77
- async waitUntilReady(t, s) {
75
+ async waitUntilReady(t, e) {
78
76
  const i = this.timeoutMs / this.pollIntervalMs;
79
77
  for (let o = 0; o < i; o++) {
80
- const a = await fetch(`${this.proxyBase}/deploys/${t.deploy_id}`, {
81
- credentials: "include"
78
+ const s = await fetch(`${this.apiBase}/deploys/${t.deploy_id}`, {
79
+ headers: { Authorization: `Bearer ${e}` }
82
80
  });
83
- if (a.ok) {
84
- const e = await a.json();
85
- if (e.state && T.has(e.state)) {
86
- if (e.state === "ready") return;
81
+ if (s.ok) {
82
+ const a = await s.json();
83
+ if (a.state && m.has(a.state)) {
84
+ if (a.state === "ready") return;
87
85
  throw new Error(
88
- e.error_message ? `deploy failed: ${e.error_message}` : "deploy failed"
86
+ a.error_message ? `deploy failed: ${a.error_message}` : "deploy failed"
89
87
  );
90
88
  }
91
89
  }
92
- await new Promise((e) => setTimeout(e, this.pollIntervalMs));
90
+ await new Promise((a) => setTimeout(a, this.pollIntervalMs));
93
91
  }
94
92
  throw new Error("Deploy did not become ready in time");
95
93
  }
@@ -22,20 +22,17 @@ export interface BuildClient {
22
22
  }
23
23
  export interface AuthenticatedBuildClientConfig {
24
24
  /**
25
- * Base for JSON API calls, routed through the consumer's access-control
26
- * rewrite so the session cookie authenticates them. On netlify.com that
27
- * rewrite is `/access-control/*` → the app's access-control function, which
28
- * makes `/access-control/bb-api/api/v1` the API root (the default here).
29
- */
30
- proxyBase?: string;
31
- /**
32
- * Direct API base for the zip upload. The proxy above runs in a Lambda with a
33
- * ~6MB request body limit, so the multipart POST has to go straight to
34
- * `api.netlify.com` (whose `/api/*` CORS policy allows any origin).
25
+ * API base for every call. Requests go straight to `api.netlify.com`, whose
26
+ * `/api/*` CORS policy allows any origin, rather than through a consumer's
27
+ * access-control rewrite — that proxy allowlists paths per origin, and site
28
+ * creation and deploy reads are not on the list for netlify.com (they 400
29
+ * with `x-debug: request origin not allowed to call endpoint`). The zip
30
+ * upload could not use that proxy regardless: it runs in a Lambda with a
31
+ * ~6MB request body limit.
35
32
  */
36
33
  apiBase?: string;
37
34
  /**
38
- * Mint a short-lived bearer token for that direct upload. On netlify.com this
35
+ * Mint a short-lived bearer token authorizing the calls. On netlify.com this
39
36
  * is `GET /access-control/generate-access-control-token`, whose
40
37
  * `accessControlToken` the API decrypts back into the user's access token.
41
38
  * Resolving `null` means "no session" and surfaces as `NotAuthenticatedError`.
@@ -63,18 +60,16 @@ export interface AuthenticatedBuildClientConfig {
63
60
  * short-lived bearer token because the proxy can't carry a body that large.
64
61
  */
65
62
  export declare class AuthenticatedBuildClient implements BuildClient {
66
- private proxyBase;
67
63
  private apiBase;
68
64
  private getUploadToken;
69
65
  private accountSlug?;
70
66
  private title;
71
67
  private pollIntervalMs;
72
68
  private timeoutMs;
73
- constructor({ proxyBase, apiBase, getUploadToken, accountSlug, title, pollIntervalMs, timeoutMs, }: AuthenticatedBuildClientConfig);
69
+ constructor({ apiBase, getUploadToken, accountSlug, title, pollIntervalMs, timeoutMs, }: AuthenticatedBuildClientConfig);
74
70
  /**
75
- * Create the site the build deploys into. Goes through the proxy with
76
- * `credentials: 'include'` so the app session authorizes it; without an
77
- * account slug the API picks the user's default account.
71
+ * Create the site the build deploys into. Authorized by the bearer token;
72
+ * without an account slug the API picks the user's default account.
78
73
  */
79
74
  createSite(): Promise<BuildSite>;
80
75
  /**
@@ -85,14 +80,16 @@ export declare class AuthenticatedBuildClient implements BuildClient {
85
80
  */
86
81
  createBuildFromZip(siteId: string, zip: File): Promise<BuildResponse>;
87
82
  /**
88
- * Poll the build's deploy through the proxy until it settles, mirroring
83
+ * Poll the build's deploy until it settles, mirroring
89
84
  * `AnonymousDropClient.waitUntilReady` — resolve on `ready`, throw on
90
85
  * `error`/`rejected`, throw on timeout. A build that hasn't been given a
91
86
  * deploy id yet has nothing pollable, so it resolves immediately and the
92
87
  * caller falls back to the site page.
93
88
  *
94
- * Optional in the drop flow: a build takes minutes, so the default hand-off
95
- * redirects to the app's deploy page (live logs) instead of waiting here.
89
+ * Optional in the drop flow, and off by default: a build takes minutes, which
90
+ * can outlive the bearer token minted for it. Each poll re-mints so a long
91
+ * build keeps reading; a token that can't be minted any more ends the wait as
92
+ * a timeout rather than a false failure.
96
93
  */
97
94
  waitUntilBuildReady(build: BuildResponse): Promise<void>;
98
95
  }
@@ -1,41 +1,39 @@
1
- var d = Object.defineProperty;
2
- var p = (r, t, e) => t in r ? d(r, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : r[t] = e;
3
- var i = (r, t, e) => p(r, typeof t != "symbol" ? t + "" : t, e);
4
- import { NotAuthenticatedError as n, SiteCreateError as h, BuildCreateError as u, BuildFailedError as w, BuildTimeoutError as y } from "./errors.js";
5
- const f = "/access-control/bb-api/api/v1", B = "https://api.netlify.com/api/v1", m = 1e3, S = 10 * 60 * 1e3, T = /* @__PURE__ */ new Set(["ready", "error", "rejected"]);
6
- class _ {
1
+ var h = Object.defineProperty;
2
+ var d = (n, t, o) => t in n ? h(n, t, { enumerable: !0, configurable: !0, writable: !0, value: o }) : n[t] = o;
3
+ var r = (n, t, o) => d(n, typeof t != "symbol" ? t + "" : t, o);
4
+ import { NotAuthenticatedError as l, SiteCreateError as c, BuildCreateError as p, BuildFailedError as u, BuildTimeoutError as w } from "./errors.js";
5
+ const f = "https://api.netlify.com/api/v1", m = 1e3, y = 10 * 60 * 1e3, B = /* @__PURE__ */ new Set(["ready", "error", "rejected"]);
6
+ class g {
7
7
  constructor({
8
- proxyBase: t = f,
9
- apiBase: e = B,
10
- getUploadToken: a,
11
- accountSlug: o,
8
+ apiBase: t = f,
9
+ getUploadToken: o,
10
+ accountSlug: e,
12
11
  title: s = "Build from drop deployment",
13
- pollIntervalMs: l = m,
14
- timeoutMs: c = S
12
+ pollIntervalMs: i = m,
13
+ timeoutMs: a = y
15
14
  }) {
16
- i(this, "proxyBase");
17
- i(this, "apiBase");
18
- i(this, "getUploadToken");
19
- i(this, "accountSlug");
20
- i(this, "title");
21
- i(this, "pollIntervalMs");
22
- i(this, "timeoutMs");
23
- this.proxyBase = t, this.apiBase = e, this.getUploadToken = a, this.accountSlug = o, this.title = s, this.pollIntervalMs = l, this.timeoutMs = c;
15
+ r(this, "apiBase");
16
+ r(this, "getUploadToken");
17
+ r(this, "accountSlug");
18
+ r(this, "title");
19
+ r(this, "pollIntervalMs");
20
+ r(this, "timeoutMs");
21
+ this.apiBase = t, this.getUploadToken = o, this.accountSlug = e, this.title = s, this.pollIntervalMs = i, this.timeoutMs = a;
24
22
  }
25
23
  /**
26
- * Create the site the build deploys into. Goes through the proxy with
27
- * `credentials: 'include'` so the app session authorizes it; without an
28
- * account slug the API picks the user's default account.
24
+ * Create the site the build deploys into. Authorized by the bearer token;
25
+ * without an account slug the API picks the user's default account.
29
26
  */
30
27
  async createSite() {
31
- const t = this.accountSlug ? `/${this.accountSlug}/sites` : "/sites", e = await fetch(`${this.proxyBase}${t}`, {
28
+ const t = await this.getUploadToken();
29
+ if (!t) throw new l();
30
+ const o = this.accountSlug ? `/${this.accountSlug}/sites` : "/sites", e = await fetch(`${this.apiBase}${o}`, {
32
31
  method: "POST",
33
- credentials: "include",
34
- headers: { "Content-Type": "application/json" },
32
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${t}` },
35
33
  body: JSON.stringify({ created_via: "drop" })
36
34
  });
37
- if (e.status === 401 || e.status === 403) throw new n();
38
- if (!e.ok) throw new h(e.status);
35
+ if (e.status === 401 || e.status === 403) throw new l();
36
+ if (!e.ok) throw new c(e.status);
39
37
  return e.json();
40
38
  }
41
39
  /**
@@ -44,49 +42,51 @@ class _ {
44
42
  * `Content-Type` is left unset on purpose so the browser adds the multipart
45
43
  * boundary itself.
46
44
  */
47
- async createBuildFromZip(t, e) {
48
- const a = await this.getUploadToken();
49
- if (!a) throw new n();
50
- const o = new FormData();
51
- o.append("zip", e), o.append("title", this.title);
52
- const s = await fetch(`${this.apiBase}/sites/${t}/builds`, {
45
+ async createBuildFromZip(t, o) {
46
+ const e = await this.getUploadToken();
47
+ if (!e) throw new l();
48
+ const s = new FormData();
49
+ s.append("zip", o), s.append("title", this.title);
50
+ const i = await fetch(`${this.apiBase}/sites/${t}/builds`, {
53
51
  method: "POST",
54
- headers: { Authorization: `Bearer ${a}` },
55
- body: o
52
+ headers: { Authorization: `Bearer ${e}` },
53
+ body: s
56
54
  });
57
- if (s.status === 401 || s.status === 403) throw new n();
58
- if (!s.ok) throw new u(s.status);
59
- return s.json();
55
+ if (i.status === 401 || i.status === 403) throw new l();
56
+ if (!i.ok) throw new p(i.status);
57
+ return i.json();
60
58
  }
61
59
  /**
62
- * Poll the build's deploy through the proxy until it settles, mirroring
60
+ * Poll the build's deploy until it settles, mirroring
63
61
  * `AnonymousDropClient.waitUntilReady` — resolve on `ready`, throw on
64
62
  * `error`/`rejected`, throw on timeout. A build that hasn't been given a
65
63
  * deploy id yet has nothing pollable, so it resolves immediately and the
66
64
  * caller falls back to the site page.
67
65
  *
68
- * Optional in the drop flow: a build takes minutes, so the default hand-off
69
- * redirects to the app's deploy page (live logs) instead of waiting here.
66
+ * Optional in the drop flow, and off by default: a build takes minutes, which
67
+ * can outlive the bearer token minted for it. Each poll re-mints so a long
68
+ * build keeps reading; a token that can't be minted any more ends the wait as
69
+ * a timeout rather than a false failure.
70
70
  */
71
71
  async waitUntilBuildReady(t) {
72
72
  if (!t.deploy_id) return;
73
- const e = this.timeoutMs / this.pollIntervalMs;
74
- for (let a = 0; a < e; a++) {
75
- const o = await fetch(`${this.proxyBase}/deploys/${t.deploy_id}`, {
76
- credentials: "include"
73
+ const o = this.timeoutMs / this.pollIntervalMs;
74
+ for (let e = 0; e < o; e++) {
75
+ const s = await this.getUploadToken(), i = await fetch(`${this.apiBase}/deploys/${t.deploy_id}`, {
76
+ headers: s ? { Authorization: `Bearer ${s}` } : {}
77
77
  });
78
- if (o.ok) {
79
- const s = await o.json();
80
- if (s.state && T.has(s.state)) {
81
- if (s.state === "ready") return;
82
- throw new w(s.error_message);
78
+ if (i.ok) {
79
+ const a = await i.json();
80
+ if (a.state && B.has(a.state)) {
81
+ if (a.state === "ready") return;
82
+ throw new u(a.error_message);
83
83
  }
84
84
  }
85
- await new Promise((s) => setTimeout(s, this.pollIntervalMs));
85
+ await new Promise((a) => setTimeout(a, this.pollIntervalMs));
86
86
  }
87
- throw new y();
87
+ throw new w();
88
88
  }
89
89
  }
90
90
  export {
91
- _ as AuthenticatedBuildClient
91
+ g as AuthenticatedBuildClient
92
92
  };
@@ -68,7 +68,6 @@ interface UseDropDeployOptions {
68
68
  buildClient?: BuildClient;
69
69
  authenticatedDropClient?: DropClient;
70
70
  getUploadToken?: () => Promise<string | null>;
71
- proxyBase?: string;
72
71
  accountSlug?: string;
73
72
  buildDeployUrl: (build: DeployedBuild) => string;
74
73
  waitForBuild?: boolean;
@@ -82,7 +81,7 @@ interface UseDropDeployOptions {
82
81
  * Owns the deploy state machine and its side effects (upload, local persistence,
83
82
  * analytics, redirect), keeping DropZone a thin view over the returned `status`.
84
83
  */
85
- export declare function useDropDeploy({ apiBase, client, claimUrl, siteDashboardUrl, buildSignupUrl, buildClient, authenticatedDropClient, getUploadToken, proxyBase, accountSlug, buildDeployUrl, waitForBuild, onBuildRequired, onDeploy, onBuildDeploy, onError, track, }: UseDropDeployOptions): {
84
+ export declare function useDropDeploy({ apiBase, client, claimUrl, siteDashboardUrl, buildSignupUrl, buildClient, authenticatedDropClient, getUploadToken, accountSlug, buildDeployUrl, waitForBuild, onBuildRequired, onDeploy, onBuildDeploy, onError, track, }: UseDropDeployOptions): {
86
85
  status: DeployStatus;
87
86
  warning: string | null;
88
87
  busy: boolean;
@@ -1,12 +1,12 @@
1
- import { d as H, A as I } from "../../../chunks/hooks.module.DRcxknIz.js";
2
- import { renameSingleNonIndexHtmlToIndex as Y, getSingleNonIndexHtmlFileName as Z, hasMultipleHtmlFilesWithoutIndex as B } from "./drop-core/readFiles.js";
3
- import { detectBuild as D } from "./drop-core/detectBuild.js";
4
- import { AnonymousDropClient as T } from "./drop-core/client.js";
5
- import { AuthenticatedDropClient as U } from "./drop-core/authedDropClient.js";
6
- import { AuthenticatedBuildClient as tt } from "./drop-core/buildClient.js";
1
+ import { d as P, A as I } from "../../../chunks/hooks.module.DRcxknIz.js";
2
+ import { renameSingleNonIndexHtmlToIndex as X, getSingleNonIndexHtmlFileName as Y, hasMultipleHtmlFilesWithoutIndex as Z } from "./drop-core/readFiles.js";
3
+ import { detectBuild as B } from "./drop-core/detectBuild.js";
4
+ import { AnonymousDropClient as D } from "./drop-core/client.js";
5
+ import { AuthenticatedDropClient as T } from "./drop-core/authedDropClient.js";
6
+ import { AuthenticatedBuildClient as U } from "./drop-core/buildClient.js";
7
7
  import "./drop-core/zip.js";
8
- import { deployFiles as W, deployBuild as et } from "./drop-core/deploy.js";
9
- import { NotAuthenticatedError as $, humanizeDropError as nt } from "./drop-core/errors.js";
8
+ import { deployFiles as H, deployBuild as tt } from "./drop-core/deploy.js";
9
+ import { NotAuthenticatedError as W, humanizeDropError as et } from "./drop-core/errors.js";
10
10
  function S(o) {
11
11
  switch (o.phase) {
12
12
  case "zipping":
@@ -22,39 +22,38 @@ function S(o) {
22
22
  return { kind: "processing" };
23
23
  }
24
24
  }
25
- function it(o) {
26
- const f = Z(o);
27
- return f ? `No index.html found — "${f}" won’t be served at the site root.` : B(o) ? "No index.html found — your site may not have a landing page." : null;
25
+ function nt(o) {
26
+ const f = Y(o);
27
+ return f ? `No index.html found — "${f}" won’t be served at the site root.` : Z(o) ? "No index.html found — your site may not have a landing page." : null;
28
28
  }
29
- function mt({
29
+ function ft({
30
30
  apiBase: o,
31
31
  client: f,
32
- claimUrl: v,
33
- siteDashboardUrl: j,
34
- buildSignupUrl: q,
35
- buildClient: M,
36
- authenticatedDropClient: E,
32
+ claimUrl: $,
33
+ siteDashboardUrl: v,
34
+ buildSignupUrl: j,
35
+ buildClient: q,
36
+ authenticatedDropClient: M,
37
37
  getUploadToken: m,
38
- proxyBase: x,
39
- accountSlug: z,
40
- buildDeployUrl: G,
41
- waitForBuild: J,
42
- onBuildRequired: C,
43
- onDeploy: p,
38
+ accountSlug: x,
39
+ buildDeployUrl: E,
40
+ waitForBuild: G,
41
+ onBuildRequired: z,
42
+ onDeploy: g,
44
43
  onBuildDeploy: h,
45
44
  onError: b,
46
45
  track: c
47
46
  }) {
48
- const [w, n] = H({ kind: "idle" }), [K, _] = H(null), L = I(f ?? new T(o)).current, A = I(
49
- M ?? (m ? new tt({ proxyBase: x, getUploadToken: m, accountSlug: z }) : null)
50
- ).current, F = I(
51
- E ?? (m ? new U({ proxyBase: x, getUploadToken: m, accountSlug: z }) : null)
52
- ).current, O = w.kind !== "idle" && w.kind !== "error";
53
- async function Q(y, r) {
47
+ const [w, n] = P({ kind: "idle" }), [J, _] = P(null), K = I(f ?? new D(o)).current, C = I(
48
+ q ?? (m ? new U({ getUploadToken: m, accountSlug: x }) : null)
49
+ ).current, A = I(
50
+ M ?? (m ? new T({ getUploadToken: m, accountSlug: x }) : null)
51
+ ).current, L = w.kind !== "idle" && w.kind !== "error";
52
+ async function O(y, r) {
54
53
  try {
55
- const { site: d, build: l, buildSettings: g, url: k } = await et(y, r, {
54
+ const { site: d, build: l, buildSettings: p, url: k } = await tt(y, r, {
56
55
  onProgress: (t) => n(S(t)),
57
- waitForBuild: J
56
+ waitForBuild: G
58
57
  });
59
58
  return {
60
59
  url: k,
@@ -62,25 +61,25 @@ function mt({
62
61
  siteId: d.id,
63
62
  buildId: l.id,
64
63
  deployId: l.deploy_id,
65
- buildSettings: g
64
+ buildSettings: p
66
65
  };
67
66
  } catch (d) {
68
- if (d instanceof $) return null;
67
+ if (d instanceof W) return null;
69
68
  throw d;
70
69
  }
71
70
  }
72
- async function R(y, r, d) {
71
+ async function Q(y, r, d) {
73
72
  _(null), n({ kind: "reading" });
74
- const l = d.length ? { file_types: d } : {}, g = (t, i, a) => {
73
+ const l = d.length ? { file_types: d } : {}, p = (t, i, a) => {
75
74
  n({ kind: "error", message: t }), c("dropzone_deploy_failed", { method: r, reason: i, ...l, ...a }), b == null || b({ message: t, reason: i });
76
75
  }, k = (t, i) => {
77
76
  const a = t instanceof Error ? t.message : String(t);
78
- g(nt(t), a, i);
77
+ p(et(t), a, i);
79
78
  };
80
79
  try {
81
- const t = Y(await y());
80
+ const t = X(await y());
82
81
  if (!t.length) {
83
- g("No files found to deploy.", "No files found to deploy.", { file_count: 0 });
82
+ p("No files found to deploy.", "No files found to deploy.", { file_count: 0 });
84
83
  return;
85
84
  }
86
85
  r === "drag" && c("dropzone_files_dropped", {
@@ -88,19 +87,19 @@ function mt({
88
87
  file_count: t.length,
89
88
  ...l
90
89
  });
91
- const i = D(t);
90
+ const i = B(t);
92
91
  if (i.buildRequired) {
93
92
  if (c("dropzone_build_required", {
94
93
  method: r,
95
94
  reason: i.reason,
96
95
  file_count: t.length,
97
96
  ...l
98
- }), C) {
99
- n({ kind: "idle" }), C(i);
97
+ }), z) {
98
+ n({ kind: "idle" }), z(i);
100
99
  return;
101
100
  }
102
- if (A) {
103
- const e = await Q(A, t);
101
+ if (C) {
102
+ const e = await O(C, t);
104
103
  if (e) {
105
104
  n({ kind: "redirecting", target: "build" }), c("dropzone_build_deploy_succeeded", {
106
105
  method: r,
@@ -109,28 +108,28 @@ function mt({
109
108
  file_count: t.length,
110
109
  ...e.buildSettings ? { framework: e.buildSettings.framework } : {},
111
110
  ...l
112
- }), h == null || h(e), window.location.assign(G(e));
111
+ }), h == null || h(e), window.location.assign(E(e));
113
112
  return;
114
113
  }
115
114
  }
116
- n({ kind: "redirecting", target: "signup" }), window.location.assign(q);
115
+ n({ kind: "redirecting", target: "signup" }), window.location.assign(j);
117
116
  return;
118
117
  }
119
- _(it(t));
120
- let a = null, s = !1, P = !1;
121
- if (F)
118
+ _(nt(t));
119
+ let a = null, s = !1, F = !1;
120
+ if (A)
122
121
  try {
123
- a = await W(F, t, {
122
+ a = await H(A, t, {
124
123
  onProgress: (e) => n(S(e))
125
124
  }), s = !0;
126
125
  } catch (e) {
127
- if (!(e instanceof $)) throw e;
128
- P = !0;
126
+ if (!(e instanceof W)) throw e;
127
+ F = !0;
129
128
  }
130
- a || (a = await W(L, t, {
129
+ a || (a = await H(K, t, {
131
130
  onProgress: (e) => n(S(e))
132
131
  }));
133
- const { url: X, token: N, deploy: u } = a;
132
+ const { url: V, token: N, deploy: u } = a;
134
133
  if (!s)
135
134
  try {
136
135
  localStorage.setItem("drop-token", N), localStorage.setItem("dropSiteName", u.subdomain);
@@ -141,24 +140,24 @@ function mt({
141
140
  site_name: u.subdomain,
142
141
  file_count: t.length,
143
142
  authenticated: s,
144
- ...P ? { auth_fallback: !0 } : {},
143
+ ...F ? { auth_fallback: !0 } : {},
145
144
  ...l
146
- }), p == null || p({
147
- url: X,
145
+ }), g == null || g({
146
+ url: V,
148
147
  ...s ? {} : { token: N },
149
148
  siteName: u.subdomain,
150
149
  siteId: u.id,
151
150
  authenticated: s
152
- }), s ? window.location.assign(j(u.subdomain)) : window.location.assign(`${v(u.subdomain)}#drop_token=${N}`);
151
+ }), s ? window.location.assign(v(u.subdomain)) : window.location.assign(`${$(u.subdomain)}#drop_token=${N}`);
153
152
  } catch (t) {
154
153
  k(t);
155
154
  }
156
155
  }
157
- function V() {
156
+ function R() {
158
157
  _(null), n({ kind: "idle" });
159
158
  }
160
- return { status: w, warning: K, busy: O, deploy: R, reset: V };
159
+ return { status: w, warning: J, busy: L, deploy: Q, reset: R };
161
160
  }
162
161
  export {
163
- mt as useDropDeploy
162
+ ft as useDropDeploy
164
163
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@netlify/spark-ui",
3
3
  "description": "Assets, design tokens, components, and utilities",
4
- "version": "1.31.0-alpha.1",
4
+ "version": "1.31.0-alpha.2",
5
5
  "type": "module",
6
6
  "main": "dist/components/preact/index.js",
7
7
  "types": "dist/components/preact/index.d.ts",
@@ -93,13 +93,6 @@ interface Props {
93
93
  * static path. Distinct from `client`, which is the anonymous fallback.
94
94
  */
95
95
  authenticatedDropClient?: DropClient;
96
- /**
97
- * API root for the authenticated paths' JSON calls, which go through your
98
- * access-control rewrite so the session cookie authorizes them. Defaults to
99
- * `/access-control/bb-api/api/v1`. (Uploads always go direct to
100
- * `api.netlify.com` — the proxy can't carry bodies that large.)
101
- */
102
- proxyBase?: string;
103
96
  /** Create the visitor's new project in this account. Omit for their default account. */
104
97
  accountSlug?: string;
105
98
  /**
@@ -187,7 +180,6 @@ export default function DropZone({
187
180
  getUploadToken,
188
181
  buildClient,
189
182
  authenticatedDropClient,
190
- proxyBase,
191
183
  accountSlug,
192
184
  buildDeployUrl = defaultBuildDeployUrl,
193
185
  waitForBuild,
@@ -214,7 +206,6 @@ export default function DropZone({
214
206
  getUploadToken,
215
207
  buildClient,
216
208
  authenticatedDropClient,
217
- proxyBase,
218
209
  accountSlug,
219
210
  buildDeployUrl,
220
211
  waitForBuild,
@@ -5,21 +5,16 @@ import { NotAuthenticatedError, SiteCreateError } from './errors';
5
5
 
6
6
  export interface AuthenticatedDropClientConfig {
7
7
  /**
8
- * Base for JSON API calls, routed through the consumer's access-control
9
- * rewrite so the session cookie authenticates them. On netlify.com that
10
- * rewrite is `/access-control/*` → the app's access-control function, which
11
- * makes `/access-control/bb-api/api/v1` the API root (the default here).
12
- */
13
- proxyBase?: string;
14
- /**
15
- * Direct API base for the per-file uploads. The proxy above runs in a Lambda
16
- * with a ~6MB request body limit, so file PUTs go straight to
17
- * `api.netlify.com` (whose `/api/*` CORS policy allows any origin).
8
+ * API base for every call. Requests go straight to `api.netlify.com`, whose
9
+ * `/api/*` CORS policy allows any origin, rather than through a consumer's
10
+ * access-control rewrite — that proxy allowlists paths per origin, and site
11
+ * and deploy creation are not on the list for netlify.com (they 400 with
12
+ * `x-debug: request origin not allowed to call endpoint`).
18
13
  */
19
14
  apiBase?: string;
20
15
  /**
21
- * Mint a short-lived bearer token for those direct uploads. On netlify.com
22
- * this is `GET /access-control/generate-access-control-token`, whose
16
+ * Mint a short-lived bearer token authorizing the calls. On netlify.com this
17
+ * is `GET /access-control/generate-access-control-token`, whose
23
18
  * `accessControlToken` the API decrypts back into the user's access token.
24
19
  * Resolving `null` means "no session" and surfaces as `NotAuthenticatedError`.
25
20
  */
@@ -36,7 +31,6 @@ export interface AuthenticatedDropClientConfig {
36
31
  // (constants + site create) are deliberately duplicated rather than extracted,
37
32
  // to avoid churn on the in-review build-client branch. Worth unifying once both
38
33
  // have landed.
39
- const PROXY_BASE = '/access-control/bb-api/api/v1';
40
34
  const API_BASE = 'https://api.netlify.com/api/v1';
41
35
  const POLL_INTERVAL_MS = 1000;
42
36
  const TIMEOUT_MS = 10 * 60 * 1000;
@@ -65,11 +59,12 @@ interface SiteDeployResponse {
65
59
  * There is no claim step, so the caller should hand off to the site's dashboard
66
60
  * page rather than the claim page.
67
61
  *
68
- * Requests split across two hosts by necessity: JSON calls go through the
69
- * consumer's access-control proxy (cookie session, no token handling in the
70
- * browser), while the per-file PUTs go direct to `api.netlify.com` with the
71
- * short-lived bearer from `getUploadToken()` because the proxy can't carry
72
- * large bodies.
62
+ * Every request goes direct to `api.netlify.com` with the short-lived bearer
63
+ * from `getUploadToken()`. Routing the JSON calls through a consumer's
64
+ * access-control rewrite instead (cookie session, no token in the browser)
65
+ * would be the tighter design, but that proxy allowlists paths per origin and
66
+ * site/deploy creation isn't allowed from netlify.com — it 400s before
67
+ * reaching the API. Revisit if that allowlist gains these paths.
73
68
  *
74
69
  * Error-mapping invariant: only pre-mutation failures — a null upload token, or
75
70
  * a 401/403 creating the site — throw `NotAuthenticatedError`, which is the
@@ -79,7 +74,6 @@ interface SiteDeployResponse {
79
74
  * anonymously.
80
75
  */
81
76
  export class AuthenticatedDropClient implements DropClient {
82
- private proxyBase: string;
83
77
  private apiBase: string;
84
78
  private getUploadToken: () => Promise<string | null>;
85
79
  private accountSlug?: string;
@@ -87,14 +81,12 @@ export class AuthenticatedDropClient implements DropClient {
87
81
  private timeoutMs: number;
88
82
 
89
83
  constructor({
90
- proxyBase = PROXY_BASE,
91
84
  apiBase = API_BASE,
92
85
  getUploadToken,
93
86
  accountSlug,
94
87
  pollIntervalMs = POLL_INTERVAL_MS,
95
88
  timeoutMs = TIMEOUT_MS,
96
89
  }: AuthenticatedDropClientConfig) {
97
- this.proxyBase = proxyBase;
98
90
  this.apiBase = apiBase;
99
91
  this.getUploadToken = getUploadToken;
100
92
  this.accountSlug = accountSlug;
@@ -103,10 +95,9 @@ export class AuthenticatedDropClient implements DropClient {
103
95
  }
104
96
 
105
97
  /**
106
- * The token here is the upload bearer, not a drop token — it authorizes the
107
- * direct-API file PUTs, while the proxied JSON calls ignore it and ride the
108
- * session cookie. Minted once per deploy, before anything is created, so a
109
- * missing session fails the whole attempt cleanly.
98
+ * The token here is the upload bearer, not a drop token — it authorizes every
99
+ * call this client makes. Minted once per deploy, before anything is created,
100
+ * so a missing session fails the whole attempt cleanly.
110
101
  */
111
102
  async getToken(): Promise<string> {
112
103
  const token = await this.getUploadToken();
@@ -115,16 +106,15 @@ export class AuthenticatedDropClient implements DropClient {
115
106
  }
116
107
 
117
108
  /**
118
- * Two proxied calls: create the site in the visitor's account, then the
119
- * deploy on it. Mapped into the anonymous `DropResponse` shape so
120
- * `deployFiles` runs unchanged over either client.
109
+ * Two calls: create the site in the visitor's account, then the deploy on it.
110
+ * Mapped into the anonymous `DropResponse` shape so `deployFiles` runs
111
+ * unchanged over either client.
121
112
  */
122
- async createDeploy(files: Digest, _token: string): Promise<DropResponse> {
113
+ async createDeploy(files: Digest, token: string): Promise<DropResponse> {
123
114
  const path = this.accountSlug ? `/${this.accountSlug}/sites` : '/sites';
124
- const siteRes = await fetch(`${this.proxyBase}${path}`, {
115
+ const siteRes = await fetch(`${this.apiBase}${path}`, {
125
116
  method: 'POST',
126
- credentials: 'include',
127
- headers: { 'Content-Type': 'application/json' },
117
+ headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
128
118
  body: JSON.stringify({ created_via: 'drop' }),
129
119
  });
130
120
  // A logged-out visitor 401s here, before anything is created — the one
@@ -133,10 +123,9 @@ export class AuthenticatedDropClient implements DropClient {
133
123
  if (!siteRes.ok) throw new SiteCreateError(siteRes.status);
134
124
  const site: BuildSite = await siteRes.json();
135
125
 
136
- const deployRes = await fetch(`${this.proxyBase}/sites/${site.id}/deploys`, {
126
+ const deployRes = await fetch(`${this.apiBase}/sites/${site.id}/deploys`, {
137
127
  method: 'POST',
138
- credentials: 'include',
139
- headers: { 'Content-Type': 'application/json' },
128
+ headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
140
129
  body: JSON.stringify({ files, deploy_source: 'drop' }),
141
130
  });
142
131
  if (!deployRes.ok) throw apiError('deploy create', deployRes.status);
@@ -156,7 +145,6 @@ export class AuthenticatedDropClient implements DropClient {
156
145
  content: ArrayBuffer,
157
146
  token: string
158
147
  ): Promise<void> {
159
- // Direct to api.netlify.com with the upload bearer — see the class doc.
160
148
  // PUT (not POST) so paths with "&" etc. work.
161
149
  const res = await fetch(`${this.apiBase}/deploys/${deployId}/files${path}`, {
162
150
  method: 'PUT',
@@ -167,15 +155,18 @@ export class AuthenticatedDropClient implements DropClient {
167
155
  }
168
156
 
169
157
  /**
170
- * Poll the deploy through the proxy until it settles — the session cookie
171
- * outlives the short-lived upload bearer, so a slow deploy can't expire its
172
- * own poll. Resolve on `ready`, throw on `error`/`rejected`, throw on timeout.
158
+ * Poll until the deploy settles: resolve on `ready`, throw on
159
+ * `error`/`rejected`, throw on timeout. Reuses the bearer minted at the start
160
+ * of the deploy, so a deploy that outlives the token's TTL will start seeing
161
+ * 401s here — those are ignored like any other non-ok read and the loop runs
162
+ * to its timeout rather than failing fast. Acceptable while static deploys
163
+ * finish in seconds; re-mint per poll if that stops being true.
173
164
  */
174
- async waitUntilReady(deploy: DropResponse, _token: string): Promise<void> {
165
+ async waitUntilReady(deploy: DropResponse, token: string): Promise<void> {
175
166
  const deadline = this.timeoutMs / this.pollIntervalMs;
176
167
  for (let i = 0; i < deadline; i++) {
177
- const res = await fetch(`${this.proxyBase}/deploys/${deploy.deploy_id}`, {
178
- credentials: 'include',
168
+ const res = await fetch(`${this.apiBase}/deploys/${deploy.deploy_id}`, {
169
+ headers: { Authorization: `Bearer ${token}` },
179
170
  });
180
171
  if (res.ok) {
181
172
  const state: DeployState = await res.json();
@@ -33,20 +33,17 @@ export interface BuildClient {
33
33
 
34
34
  export interface AuthenticatedBuildClientConfig {
35
35
  /**
36
- * Base for JSON API calls, routed through the consumer's access-control
37
- * rewrite so the session cookie authenticates them. On netlify.com that
38
- * rewrite is `/access-control/*` → the app's access-control function, which
39
- * makes `/access-control/bb-api/api/v1` the API root (the default here).
40
- */
41
- proxyBase?: string;
42
- /**
43
- * Direct API base for the zip upload. The proxy above runs in a Lambda with a
44
- * ~6MB request body limit, so the multipart POST has to go straight to
45
- * `api.netlify.com` (whose `/api/*` CORS policy allows any origin).
36
+ * API base for every call. Requests go straight to `api.netlify.com`, whose
37
+ * `/api/*` CORS policy allows any origin, rather than through a consumer's
38
+ * access-control rewrite — that proxy allowlists paths per origin, and site
39
+ * creation and deploy reads are not on the list for netlify.com (they 400
40
+ * with `x-debug: request origin not allowed to call endpoint`). The zip
41
+ * upload could not use that proxy regardless: it runs in a Lambda with a
42
+ * ~6MB request body limit.
46
43
  */
47
44
  apiBase?: string;
48
45
  /**
49
- * Mint a short-lived bearer token for that direct upload. On netlify.com this
46
+ * Mint a short-lived bearer token authorizing the calls. On netlify.com this
50
47
  * is `GET /access-control/generate-access-control-token`, whose
51
48
  * `accessControlToken` the API decrypts back into the user's access token.
52
49
  * Resolving `null` means "no session" and surfaces as `NotAuthenticatedError`.
@@ -62,7 +59,6 @@ export interface AuthenticatedBuildClientConfig {
62
59
  timeoutMs?: number;
63
60
  }
64
61
 
65
- const PROXY_BASE = '/access-control/bb-api/api/v1';
66
62
  const API_BASE = 'https://api.netlify.com/api/v1';
67
63
  const POLL_INTERVAL_MS = 1000;
68
64
  const TIMEOUT_MS = 10 * 60 * 1000;
@@ -88,7 +84,6 @@ interface DeployState {
88
84
  * short-lived bearer token because the proxy can't carry a body that large.
89
85
  */
90
86
  export class AuthenticatedBuildClient implements BuildClient {
91
- private proxyBase: string;
92
87
  private apiBase: string;
93
88
  private getUploadToken: () => Promise<string | null>;
94
89
  private accountSlug?: string;
@@ -97,7 +92,6 @@ export class AuthenticatedBuildClient implements BuildClient {
97
92
  private timeoutMs: number;
98
93
 
99
94
  constructor({
100
- proxyBase = PROXY_BASE,
101
95
  apiBase = API_BASE,
102
96
  getUploadToken,
103
97
  accountSlug,
@@ -105,7 +99,6 @@ export class AuthenticatedBuildClient implements BuildClient {
105
99
  pollIntervalMs = POLL_INTERVAL_MS,
106
100
  timeoutMs = TIMEOUT_MS,
107
101
  }: AuthenticatedBuildClientConfig) {
108
- this.proxyBase = proxyBase;
109
102
  this.apiBase = apiBase;
110
103
  this.getUploadToken = getUploadToken;
111
104
  this.accountSlug = accountSlug;
@@ -115,16 +108,17 @@ export class AuthenticatedBuildClient implements BuildClient {
115
108
  }
116
109
 
117
110
  /**
118
- * Create the site the build deploys into. Goes through the proxy with
119
- * `credentials: 'include'` so the app session authorizes it; without an
120
- * account slug the API picks the user's default account.
111
+ * Create the site the build deploys into. Authorized by the bearer token;
112
+ * without an account slug the API picks the user's default account.
121
113
  */
122
114
  async createSite(): Promise<BuildSite> {
115
+ const token = await this.getUploadToken();
116
+ if (!token) throw new NotAuthenticatedError();
117
+
123
118
  const path = this.accountSlug ? `/${this.accountSlug}/sites` : '/sites';
124
- const res = await fetch(`${this.proxyBase}${path}`, {
119
+ const res = await fetch(`${this.apiBase}${path}`, {
125
120
  method: 'POST',
126
- credentials: 'include',
127
- headers: { 'Content-Type': 'application/json' },
121
+ headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
128
122
  body: JSON.stringify({ created_via: 'drop' }),
129
123
  });
130
124
  // A logged-out visitor gets a 401 here rather than at the token step, since
@@ -159,22 +153,25 @@ export class AuthenticatedBuildClient implements BuildClient {
159
153
  }
160
154
 
161
155
  /**
162
- * Poll the build's deploy through the proxy until it settles, mirroring
156
+ * Poll the build's deploy until it settles, mirroring
163
157
  * `AnonymousDropClient.waitUntilReady` — resolve on `ready`, throw on
164
158
  * `error`/`rejected`, throw on timeout. A build that hasn't been given a
165
159
  * deploy id yet has nothing pollable, so it resolves immediately and the
166
160
  * caller falls back to the site page.
167
161
  *
168
- * Optional in the drop flow: a build takes minutes, so the default hand-off
169
- * redirects to the app's deploy page (live logs) instead of waiting here.
162
+ * Optional in the drop flow, and off by default: a build takes minutes, which
163
+ * can outlive the bearer token minted for it. Each poll re-mints so a long
164
+ * build keeps reading; a token that can't be minted any more ends the wait as
165
+ * a timeout rather than a false failure.
170
166
  */
171
167
  async waitUntilBuildReady(build: BuildResponse): Promise<void> {
172
168
  if (!build.deploy_id) return;
173
169
 
174
170
  const deadline = this.timeoutMs / this.pollIntervalMs;
175
171
  for (let i = 0; i < deadline; i++) {
176
- const res = await fetch(`${this.proxyBase}/deploys/${build.deploy_id}`, {
177
- credentials: 'include',
172
+ const token = await this.getUploadToken();
173
+ const res = await fetch(`${this.apiBase}/deploys/${build.deploy_id}`, {
174
+ headers: token ? { Authorization: `Bearer ${token}` } : {},
178
175
  });
179
176
  if (res.ok) {
180
177
  const deploy: DeployState = await res.json();
@@ -89,7 +89,6 @@ interface UseDropDeployOptions {
89
89
  buildClient?: BuildClient;
90
90
  authenticatedDropClient?: DropClient;
91
91
  getUploadToken?: () => Promise<string | null>;
92
- proxyBase?: string;
93
92
  accountSlug?: string;
94
93
  buildDeployUrl: (build: DeployedBuild) => string;
95
94
  waitForBuild?: boolean;
@@ -142,7 +141,6 @@ export function useDropDeploy({
142
141
  buildClient,
143
142
  authenticatedDropClient,
144
143
  getUploadToken,
145
- proxyBase,
146
144
  accountSlug,
147
145
  buildDeployUrl,
148
146
  waitForBuild,
@@ -162,9 +160,7 @@ export function useDropDeploy({
162
160
  // fresh client (and a fresh token cache) to an in-flight deploy.
163
161
  const authedBuildClient = useRef(
164
162
  buildClient ??
165
- (getUploadToken
166
- ? new AuthenticatedBuildClient({ proxyBase, getUploadToken, accountSlug })
167
- : null)
163
+ (getUploadToken ? new AuthenticatedBuildClient({ getUploadToken, accountSlug }) : null)
168
164
  ).current;
169
165
 
170
166
  // Same opt-in for static drops: with authenticated wiring present, deploy
@@ -172,9 +168,7 @@ export function useDropDeploy({
172
168
  // every static drop stays anonymous.
173
169
  const authedDropClient = useRef(
174
170
  authenticatedDropClient ??
175
- (getUploadToken
176
- ? new AuthenticatedDropClient({ proxyBase, getUploadToken, accountSlug })
177
- : null)
171
+ (getUploadToken ? new AuthenticatedDropClient({ getUploadToken, accountSlug }) : null)
178
172
  ).current;
179
173
 
180
174
  const busy = status.kind !== 'idle' && status.kind !== 'error';