@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.
- package/dist/components/preact/DropZone/DropZone.d.ts +1 -8
- package/dist/components/preact/DropZone/DropZone.js +40 -42
- package/dist/components/preact/DropZone/drop-core/authedDropClient.d.ts +28 -31
- package/dist/components/preact/DropZone/drop-core/authedDropClient.js +55 -57
- package/dist/components/preact/DropZone/drop-core/buildClient.d.ts +16 -19
- package/dist/components/preact/DropZone/drop-core/buildClient.js +54 -54
- package/dist/components/preact/DropZone/useDropDeploy.d.ts +1 -2
- package/dist/components/preact/DropZone/useDropDeploy.js +59 -60
- package/package.json +1 -1
- package/packages/components/preact/DropZone/DropZone.tsx +0 -9
- package/packages/components/preact/DropZone/drop-core/authedDropClient.ts +33 -42
- package/packages/components/preact/DropZone/drop-core/buildClient.ts +23 -26
- package/packages/components/preact/DropZone/useDropDeploy.ts +2 -8
|
@@ -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,
|
|
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
|
|
3
|
-
import { readFileList as
|
|
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
|
|
7
|
+
import { useDropDeploy as Z } from "./useDropDeploy.js";
|
|
8
8
|
import { Fragment as w } from "preact";
|
|
9
|
-
import '../../../assets/DropZone.css';const
|
|
10
|
-
function
|
|
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 =
|
|
15
|
-
siteDashboardUrl: u =
|
|
16
|
-
buildSignupUrl: A =
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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:
|
|
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
|
-
},
|
|
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
|
-
|
|
57
|
+
E
|
|
60
58
|
].filter(Boolean).join(" ");
|
|
61
59
|
return /* @__PURE__ */ e(
|
|
62
60
|
"div",
|
|
63
61
|
{
|
|
64
|
-
class:
|
|
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(),
|
|
69
|
+
i || (t.preventDefault(), I());
|
|
72
70
|
},
|
|
73
71
|
onDrop: (t) => {
|
|
74
|
-
if (t.preventDefault(),
|
|
72
|
+
if (t.preventDefault(), M(), i) return;
|
|
75
73
|
const n = t.dataTransfer;
|
|
76
|
-
n && m(() =>
|
|
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(() =>
|
|
98
|
+
m(() => K(z), "browse", D(z));
|
|
101
99
|
}
|
|
102
100
|
n.value = "";
|
|
103
101
|
}
|
|
104
102
|
}
|
|
105
103
|
),
|
|
106
|
-
|
|
104
|
+
N && /* @__PURE__ */ e(
|
|
107
105
|
"div",
|
|
108
106
|
{
|
|
109
107
|
class: "n-dropzone-overlay n-fadeIn",
|
|
110
108
|
children: [
|
|
111
|
-
/* @__PURE__ */ e(
|
|
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
|
|
122
|
-
const [r, a] =
|
|
123
|
-
return
|
|
124
|
-
a(!0), clearTimeout(l.current), l.current = setTimeout(() => a(!1),
|
|
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
|
|
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
|
-
|
|
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
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
|
19
|
-
*
|
|
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
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
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({
|
|
54
|
+
constructor({ apiBase, getUploadToken, accountSlug, pollIntervalMs, timeoutMs, }: AuthenticatedDropClientConfig);
|
|
60
55
|
/**
|
|
61
|
-
* The token here is the upload bearer, not a drop token — it authorizes
|
|
62
|
-
*
|
|
63
|
-
*
|
|
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
|
|
69
|
-
*
|
|
70
|
-
*
|
|
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,
|
|
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
|
|
76
|
-
*
|
|
77
|
-
*
|
|
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,
|
|
76
|
+
waitUntilReady(deploy: DropResponse, token: string): Promise<void>;
|
|
80
77
|
}
|
|
@@ -1,95 +1,93 @@
|
|
|
1
|
-
var
|
|
2
|
-
var h = (
|
|
3
|
-
var
|
|
4
|
-
import { apiError as
|
|
5
|
-
import { NotAuthenticatedError as
|
|
6
|
-
const
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
|
26
|
-
*
|
|
27
|
-
*
|
|
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
|
|
29
|
+
if (!t) throw new p();
|
|
33
30
|
return t;
|
|
34
31
|
}
|
|
35
32
|
/**
|
|
36
|
-
* Two
|
|
37
|
-
*
|
|
38
|
-
*
|
|
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,
|
|
41
|
-
const i = this.accountSlug ? `/${this.accountSlug}/sites` : "/sites", o = await fetch(`${this.
|
|
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
|
-
|
|
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
|
|
48
|
-
if (!o.ok) throw new
|
|
49
|
-
const
|
|
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
|
-
|
|
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 (!
|
|
56
|
-
const
|
|
50
|
+
if (!a.ok) throw l("deploy create", a.status);
|
|
51
|
+
const d = await a.json();
|
|
57
52
|
return {
|
|
58
|
-
id:
|
|
59
|
-
deploy_id:
|
|
60
|
-
subdomain:
|
|
61
|
-
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,
|
|
65
|
-
const
|
|
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 (!
|
|
65
|
+
if (!s.ok) throw l(`upload ${e}`, s.status);
|
|
71
66
|
}
|
|
72
67
|
/**
|
|
73
|
-
* Poll the deploy
|
|
74
|
-
*
|
|
75
|
-
*
|
|
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,
|
|
75
|
+
async waitUntilReady(t, e) {
|
|
78
76
|
const i = this.timeoutMs / this.pollIntervalMs;
|
|
79
77
|
for (let o = 0; o < i; o++) {
|
|
80
|
-
const
|
|
81
|
-
|
|
78
|
+
const s = await fetch(`${this.apiBase}/deploys/${t.deploy_id}`, {
|
|
79
|
+
headers: { Authorization: `Bearer ${e}` }
|
|
82
80
|
});
|
|
83
|
-
if (
|
|
84
|
-
const
|
|
85
|
-
if (
|
|
86
|
-
if (
|
|
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
|
-
|
|
86
|
+
a.error_message ? `deploy failed: ${a.error_message}` : "deploy failed"
|
|
89
87
|
);
|
|
90
88
|
}
|
|
91
89
|
}
|
|
92
|
-
await new Promise((
|
|
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
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
|
|
30
|
-
|
|
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
|
|
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({
|
|
69
|
+
constructor({ apiBase, getUploadToken, accountSlug, title, pollIntervalMs, timeoutMs, }: AuthenticatedBuildClientConfig);
|
|
74
70
|
/**
|
|
75
|
-
* Create the site the build deploys into.
|
|
76
|
-
*
|
|
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
|
|
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,
|
|
95
|
-
*
|
|
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
|
|
2
|
-
var
|
|
3
|
-
var
|
|
4
|
-
import { NotAuthenticatedError as
|
|
5
|
-
const f = "
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
accountSlug: o,
|
|
8
|
+
apiBase: t = f,
|
|
9
|
+
getUploadToken: o,
|
|
10
|
+
accountSlug: e,
|
|
12
11
|
title: s = "Build from drop deployment",
|
|
13
|
-
pollIntervalMs:
|
|
14
|
-
timeoutMs:
|
|
12
|
+
pollIntervalMs: i = m,
|
|
13
|
+
timeoutMs: a = y
|
|
15
14
|
}) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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.
|
|
27
|
-
*
|
|
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 =
|
|
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
|
-
|
|
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
|
|
38
|
-
if (!e.ok) throw new
|
|
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,
|
|
48
|
-
const
|
|
49
|
-
if (!
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
const
|
|
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 ${
|
|
55
|
-
body:
|
|
52
|
+
headers: { Authorization: `Bearer ${e}` },
|
|
53
|
+
body: s
|
|
56
54
|
});
|
|
57
|
-
if (
|
|
58
|
-
if (!
|
|
59
|
-
return
|
|
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
|
|
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,
|
|
69
|
-
*
|
|
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
|
|
74
|
-
for (let
|
|
75
|
-
const
|
|
76
|
-
|
|
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 (
|
|
79
|
-
const
|
|
80
|
-
if (
|
|
81
|
-
if (
|
|
82
|
-
throw new
|
|
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((
|
|
85
|
+
await new Promise((a) => setTimeout(a, this.pollIntervalMs));
|
|
86
86
|
}
|
|
87
|
-
throw new
|
|
87
|
+
throw new w();
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
export {
|
|
91
|
-
|
|
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,
|
|
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
|
|
2
|
-
import { renameSingleNonIndexHtmlToIndex as
|
|
3
|
-
import { detectBuild as
|
|
4
|
-
import { AnonymousDropClient as
|
|
5
|
-
import { AuthenticatedDropClient as
|
|
6
|
-
import { AuthenticatedBuildClient as
|
|
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
|
|
9
|
-
import { NotAuthenticatedError as
|
|
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
|
|
26
|
-
const f =
|
|
27
|
-
return f ? `No index.html found — "${f}" won’t be served at the site root.` :
|
|
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
|
|
29
|
+
function ft({
|
|
30
30
|
apiBase: o,
|
|
31
31
|
client: f,
|
|
32
|
-
claimUrl:
|
|
33
|
-
siteDashboardUrl:
|
|
34
|
-
buildSignupUrl:
|
|
35
|
-
buildClient:
|
|
36
|
-
authenticatedDropClient:
|
|
32
|
+
claimUrl: $,
|
|
33
|
+
siteDashboardUrl: v,
|
|
34
|
+
buildSignupUrl: j,
|
|
35
|
+
buildClient: q,
|
|
36
|
+
authenticatedDropClient: M,
|
|
37
37
|
getUploadToken: m,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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] =
|
|
49
|
-
|
|
50
|
-
).current,
|
|
51
|
-
|
|
52
|
-
).current,
|
|
53
|
-
async function
|
|
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:
|
|
54
|
+
const { site: d, build: l, buildSettings: p, url: k } = await tt(y, r, {
|
|
56
55
|
onProgress: (t) => n(S(t)),
|
|
57
|
-
waitForBuild:
|
|
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:
|
|
64
|
+
buildSettings: p
|
|
66
65
|
};
|
|
67
66
|
} catch (d) {
|
|
68
|
-
if (d instanceof
|
|
67
|
+
if (d instanceof W) return null;
|
|
69
68
|
throw d;
|
|
70
69
|
}
|
|
71
70
|
}
|
|
72
|
-
async function
|
|
71
|
+
async function Q(y, r, d) {
|
|
73
72
|
_(null), n({ kind: "reading" });
|
|
74
|
-
const l = d.length ? { file_types: d } : {},
|
|
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
|
-
|
|
77
|
+
p(et(t), a, i);
|
|
79
78
|
};
|
|
80
79
|
try {
|
|
81
|
-
const t =
|
|
80
|
+
const t = X(await y());
|
|
82
81
|
if (!t.length) {
|
|
83
|
-
|
|
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 =
|
|
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
|
-
}),
|
|
99
|
-
n({ kind: "idle" }),
|
|
97
|
+
}), z) {
|
|
98
|
+
n({ kind: "idle" }), z(i);
|
|
100
99
|
return;
|
|
101
100
|
}
|
|
102
|
-
if (
|
|
103
|
-
const e = await
|
|
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(
|
|
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(
|
|
115
|
+
n({ kind: "redirecting", target: "signup" }), window.location.assign(j);
|
|
117
116
|
return;
|
|
118
117
|
}
|
|
119
|
-
_(
|
|
120
|
-
let a = null, s = !1,
|
|
121
|
-
if (
|
|
118
|
+
_(nt(t));
|
|
119
|
+
let a = null, s = !1, F = !1;
|
|
120
|
+
if (A)
|
|
122
121
|
try {
|
|
123
|
-
a = await
|
|
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
|
|
128
|
-
|
|
126
|
+
if (!(e instanceof W)) throw e;
|
|
127
|
+
F = !0;
|
|
129
128
|
}
|
|
130
|
-
a || (a = await
|
|
129
|
+
a || (a = await H(K, t, {
|
|
131
130
|
onProgress: (e) => n(S(e))
|
|
132
131
|
}));
|
|
133
|
-
const { url:
|
|
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
|
-
...
|
|
143
|
+
...F ? { auth_fallback: !0 } : {},
|
|
145
144
|
...l
|
|
146
|
-
}),
|
|
147
|
-
url:
|
|
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(
|
|
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
|
|
156
|
+
function R() {
|
|
158
157
|
_(null), n({ kind: "idle" });
|
|
159
158
|
}
|
|
160
|
-
return { status: w, warning:
|
|
159
|
+
return { status: w, warning: J, busy: L, deploy: Q, reset: R };
|
|
161
160
|
}
|
|
162
161
|
export {
|
|
163
|
-
|
|
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.
|
|
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
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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
|
|
22
|
-
*
|
|
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
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
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
|
|
107
|
-
*
|
|
108
|
-
*
|
|
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
|
|
119
|
-
*
|
|
120
|
-
*
|
|
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,
|
|
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.
|
|
115
|
+
const siteRes = await fetch(`${this.apiBase}${path}`, {
|
|
125
116
|
method: 'POST',
|
|
126
|
-
|
|
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.
|
|
126
|
+
const deployRes = await fetch(`${this.apiBase}/sites/${site.id}/deploys`, {
|
|
137
127
|
method: 'POST',
|
|
138
|
-
|
|
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
|
|
171
|
-
*
|
|
172
|
-
*
|
|
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,
|
|
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.
|
|
178
|
-
|
|
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
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
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.
|
|
119
|
-
*
|
|
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.
|
|
119
|
+
const res = await fetch(`${this.apiBase}${path}`, {
|
|
125
120
|
method: 'POST',
|
|
126
|
-
|
|
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
|
|
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,
|
|
169
|
-
*
|
|
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
|
|
177
|
-
|
|
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';
|