@netlify/spark-ui 1.31.0-alpha.4 → 1.31.0-alpha.6
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/README.md +3 -4
- package/dist/components/preact/DropZone/DropZone.d.ts +23 -98
- package/dist/components/preact/DropZone/DropZone.js +5 -5
- package/dist/components/preact/DropZone/index.js +29 -29
- package/dist/components/preact/DropZone/useDropDeploy.d.ts +0 -1
- package/dist/components/preact/DropZone/useDropDeploy.js +25 -27
- package/dist/components/preact/index.js +10 -10
- package/dist/drop-core/analytics.d.ts +4 -15
- package/dist/drop-core/authedApi.d.ts +9 -32
- package/dist/drop-core/authedApi.js +15 -20
- package/dist/drop-core/authedDropClient.d.ts +15 -64
- package/dist/drop-core/authedDropClient.js +27 -49
- package/dist/drop-core/buildClient.d.ts +19 -53
- package/dist/drop-core/buildClient.js +32 -44
- package/dist/drop-core/buildStash.d.ts +6 -20
- package/dist/drop-core/client.d.ts +4 -2
- package/dist/drop-core/client.js +19 -17
- package/dist/drop-core/constants.d.ts +9 -0
- package/dist/drop-core/constants.js +10 -0
- package/dist/drop-core/deploy.d.ts +7 -17
- package/dist/drop-core/deploy.js +56 -43
- package/dist/drop-core/detectBuild.d.ts +8 -26
- package/dist/drop-core/errors.d.ts +27 -17
- package/dist/drop-core/errors.js +70 -33
- package/dist/drop-core/fileTypes.d.ts +4 -14
- package/dist/drop-core/index.d.ts +22 -11
- package/dist/drop-core/index.js +45 -49
- package/dist/drop-core/readFiles.d.ts +5 -11
- package/dist/drop-core/types.d.ts +2 -4
- package/dist/drop-core/zip.d.ts +7 -30
- package/package.json +1 -1
- package/packages/components/preact/DropZone/DropZone.tsx +27 -122
- package/packages/components/preact/DropZone/useDropDeploy.ts +31 -73
- package/packages/drop-core/README.md +91 -0
- package/packages/drop-core/analytics.ts +4 -15
- package/packages/drop-core/authedApi.ts +9 -35
- package/packages/drop-core/authedDropClient.ts +23 -86
- package/packages/drop-core/buildClient.test.ts +19 -4
- package/packages/drop-core/buildClient.ts +23 -66
- package/packages/drop-core/buildStash.ts +9 -25
- package/packages/drop-core/client.ts +19 -11
- package/packages/drop-core/constants.ts +25 -0
- package/packages/drop-core/deploy.test.ts +42 -0
- package/packages/drop-core/deploy.ts +42 -22
- package/packages/drop-core/detectBuild.ts +15 -50
- package/packages/drop-core/errors.test.ts +34 -3
- package/packages/drop-core/errors.ts +63 -27
- package/packages/drop-core/fileTypes.ts +5 -19
- package/packages/drop-core/index.ts +41 -11
- package/packages/drop-core/readFiles.test.ts +0 -1
- package/packages/drop-core/readFiles.ts +5 -12
- package/packages/drop-core/types.ts +2 -4
- package/packages/drop-core/zip.ts +7 -30
|
@@ -1,27 +1,13 @@
|
|
|
1
1
|
import { DropClient } from './client';
|
|
2
2
|
import { Digest, DropResponse } from './types';
|
|
3
3
|
export interface AuthenticatedDropClientConfig {
|
|
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
|
-
*/
|
|
4
|
+
/** Root for session-cookie JSON calls, via the consumer's access-control rewrite. */
|
|
10
5
|
proxyBase?: string;
|
|
11
|
-
/**
|
|
12
|
-
* Direct API base for the file uploads. The proxy above runs in a Lambda with
|
|
13
|
-
* a ~6MB request body limit, so file PUTs go straight to `api.netlify.com`
|
|
14
|
-
* (whose `/api/*` CORS policy allows any origin).
|
|
15
|
-
*/
|
|
6
|
+
/** Direct base for file PUTs — the proxy's ~6MB Lambda body limit can't carry uploads. */
|
|
16
7
|
apiBase?: string;
|
|
17
8
|
/**
|
|
18
|
-
* Mint
|
|
19
|
-
*
|
|
20
|
-
* `accessControlToken` the API decrypts back into the user's access token.
|
|
21
|
-
* Resolving `null` means "no session" and surfaces as `NotAuthenticatedError`.
|
|
22
|
-
*
|
|
23
|
-
* Called repeatedly, not once: the token issued to a non-app origin lives
|
|
24
|
-
* only 30 seconds, which a multi-file upload routinely outlasts.
|
|
9
|
+
* Mint the upload bearer; `null` means "no session". Called repeatedly —
|
|
10
|
+
* non-app origins get 30-second tokens, which an upload queue outlasts.
|
|
25
11
|
*/
|
|
26
12
|
getUploadToken: () => Promise<string | null>;
|
|
27
13
|
/** Create the site in this account. Omit to use the user's default account. */
|
|
@@ -32,25 +18,10 @@ export interface AuthenticatedDropClientConfig {
|
|
|
32
18
|
timeoutMs?: number;
|
|
33
19
|
}
|
|
34
20
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* There is no claim step, so the caller should hand off to the site's dashboard
|
|
40
|
-
* page rather than the claim page.
|
|
41
|
-
*
|
|
42
|
-
* Requests split across two hosts by necessity. JSON calls (create the site,
|
|
43
|
-
* create the deploy, poll it) go through the consumer's access-control proxy on
|
|
44
|
-
* the session cookie — no token involved, and nothing that can expire mid-deploy.
|
|
45
|
-
* File uploads can't: the proxy caps bodies at ~6MB, so they go direct to
|
|
46
|
-
* `api.netlify.com` with a bearer, re-minted as it ages (see `TOKEN_REUSE_MS`).
|
|
47
|
-
*
|
|
48
|
-
* Error-mapping invariant: only pre-mutation failures — a null upload token at
|
|
49
|
-
* the gate, or a 401/403 creating the site — throw `NotAuthenticatedError`,
|
|
50
|
-
* which is the signal the drop flow may safely retry anonymously. Once the site
|
|
51
|
-
* exists, any auth failure throws a plain error instead: falling back at that
|
|
52
|
-
* point would strand an empty site in the account *and* deploy a duplicate
|
|
53
|
-
* anonymously.
|
|
21
|
+
* Deploys a static drop straight into a logged-in visitor's account — no claim
|
|
22
|
+
* step. JSON rides the cookie proxy; file PUTs go direct on a re-minted bearer.
|
|
23
|
+
* Invariant: NotAuthenticatedError only pre-mutation — after the site exists, a
|
|
24
|
+
* fallback would strand an empty site and deploy a duplicate.
|
|
54
25
|
*/
|
|
55
26
|
export declare class AuthenticatedDropClient implements DropClient {
|
|
56
27
|
private proxyBase;
|
|
@@ -62,39 +33,19 @@ export declare class AuthenticatedDropClient implements DropClient {
|
|
|
62
33
|
private cachedToken?;
|
|
63
34
|
constructor({ proxyBase, apiBase, getUploadToken, accountSlug, pollIntervalMs, timeoutMs, }: AuthenticatedDropClientConfig);
|
|
64
35
|
/**
|
|
65
|
-
* The auth gate
|
|
66
|
-
* logged-out visitor fail cleanly and fall back to the anonymous flow.
|
|
67
|
-
* returned token is threaded through `deployFiles`, but only the uploads
|
|
68
|
-
* actually need one, and they re-mint rather than trusting this to still be
|
|
69
|
-
* valid by the time they run.
|
|
36
|
+
* The auth gate: minting here, before anything is created, is what lets a
|
|
37
|
+
* logged-out visitor fail cleanly and fall back to the anonymous flow.
|
|
70
38
|
*/
|
|
71
39
|
getToken(): Promise<string>;
|
|
72
40
|
/**
|
|
73
|
-
* A token
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* Deliberately NOT `NotAuthenticatedError` when minting fails: by the time
|
|
77
|
-
* uploads run the site already exists, and that error would send the drop
|
|
78
|
-
* back through the anonymous path — leaving an empty project behind and
|
|
79
|
-
* deploying a duplicate. See the class doc.
|
|
41
|
+
* A token younger than its 30s expiry. Mint failures here are plain Errors,
|
|
42
|
+
* never NotAuthenticatedError — the site exists by now (class doc).
|
|
80
43
|
*/
|
|
81
44
|
private freshToken;
|
|
82
|
-
/**
|
|
83
|
-
* Two proxied calls: create the site in the visitor's account, then the
|
|
84
|
-
* deploy on it. Mapped into the anonymous `DropResponse` shape so
|
|
85
|
-
* `deployFiles` runs unchanged over either client.
|
|
86
|
-
*/
|
|
45
|
+
/** Site + deploy via the proxy, mapped into `DropResponse` so `deployFiles` runs unchanged. */
|
|
87
46
|
createDeploy(files: Digest, _token: string): Promise<DropResponse>;
|
|
88
|
-
/**
|
|
89
|
-
* Direct to `api.netlify.com` on a freshly-aged bearer — the threaded `token`
|
|
90
|
-
* is ignored because an upload queue easily outlives the 30 seconds it was
|
|
91
|
-
* minted with. PUT (not POST) so paths with "&" etc. work.
|
|
92
|
-
*/
|
|
47
|
+
/** Direct on a fresh bearer (the threaded one has expired). PUT so paths with "&" work. */
|
|
93
48
|
uploadFile(deployId: string, path: string, content: ArrayBuffer, _token: string): Promise<void>;
|
|
94
|
-
/**
|
|
95
|
-
* Poll the deploy through the proxy until it settles: resolve on `ready`,
|
|
96
|
-
* throw on `error`/`rejected`, throw on timeout. On the session cookie rather
|
|
97
|
-
* than a bearer, so a deploy taking minutes can't outlive its own credentials.
|
|
98
|
-
*/
|
|
49
|
+
/** Polls on the session cookie, so a slow deploy can't outlive its own credentials. */
|
|
99
50
|
waitUntilReady(deploy: DropResponse, _token: string): Promise<void>;
|
|
100
51
|
}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
var
|
|
2
|
-
var
|
|
3
|
-
var a = (i, e, t) =>
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
class
|
|
1
|
+
var c = Object.defineProperty;
|
|
2
|
+
var d = (i, e, t) => e in i ? c(i, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : i[e] = t;
|
|
3
|
+
var a = (i, e, t) => d(i, typeof e != "symbol" ? e + "" : e, t);
|
|
4
|
+
import { createSiteInAccount as p, pollDeployUntilSettled as h } from "./authedApi.js";
|
|
5
|
+
import { DEFAULT_PROXY_BASE as u, DEFAULT_API_BASE as y, DEFAULT_POLL_INTERVAL_MS as T, DEFAULT_TIMEOUT_MS as m, TOKEN_REUSE_MS as w } from "./constants.js";
|
|
6
|
+
import { apiError as l } from "./client.js";
|
|
7
|
+
import { NotAuthenticatedError as k, DeployFailedError as f, DeployTimeoutError as _ } from "./errors.js";
|
|
8
|
+
class B {
|
|
9
9
|
constructor({
|
|
10
|
-
proxyBase: e =
|
|
11
|
-
apiBase: t =
|
|
10
|
+
proxyBase: e = u,
|
|
11
|
+
apiBase: t = y,
|
|
12
12
|
getUploadToken: o,
|
|
13
13
|
accountSlug: s,
|
|
14
|
-
pollIntervalMs: n =
|
|
15
|
-
timeoutMs: r =
|
|
14
|
+
pollIntervalMs: n = T,
|
|
15
|
+
timeoutMs: r = m
|
|
16
16
|
}) {
|
|
17
17
|
a(this, "proxyBase");
|
|
18
18
|
a(this, "apiBase");
|
|
@@ -24,47 +24,35 @@ class M {
|
|
|
24
24
|
this.proxyBase = e, this.apiBase = t, this.getUploadToken = o, this.accountSlug = s, this.pollIntervalMs = n, this.timeoutMs = r;
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
|
-
* The auth gate
|
|
28
|
-
* logged-out visitor fail cleanly and fall back to the anonymous flow.
|
|
29
|
-
* returned token is threaded through `deployFiles`, but only the uploads
|
|
30
|
-
* actually need one, and they re-mint rather than trusting this to still be
|
|
31
|
-
* valid by the time they run.
|
|
27
|
+
* The auth gate: minting here, before anything is created, is what lets a
|
|
28
|
+
* logged-out visitor fail cleanly and fall back to the anonymous flow.
|
|
32
29
|
*/
|
|
33
30
|
async getToken() {
|
|
34
31
|
const e = await this.getUploadToken();
|
|
35
|
-
if (!e) throw new
|
|
32
|
+
if (!e) throw new k();
|
|
36
33
|
return this.cachedToken = { value: e, mintedAt: Date.now() }, e;
|
|
37
34
|
}
|
|
38
35
|
/**
|
|
39
|
-
* A token
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* Deliberately NOT `NotAuthenticatedError` when minting fails: by the time
|
|
43
|
-
* uploads run the site already exists, and that error would send the drop
|
|
44
|
-
* back through the anonymous path — leaving an empty project behind and
|
|
45
|
-
* deploying a duplicate. See the class doc.
|
|
36
|
+
* A token younger than its 30s expiry. Mint failures here are plain Errors,
|
|
37
|
+
* never NotAuthenticatedError — the site exists by now (class doc).
|
|
46
38
|
*/
|
|
47
39
|
async freshToken() {
|
|
48
40
|
const e = Date.now();
|
|
49
|
-
if (this.cachedToken && e - this.cachedToken.mintedAt <
|
|
41
|
+
if (this.cachedToken && e - this.cachedToken.mintedAt < w)
|
|
50
42
|
return this.cachedToken.value;
|
|
51
43
|
const t = await this.getUploadToken();
|
|
52
44
|
if (!t) throw new Error("could not renew the upload token");
|
|
53
45
|
return this.cachedToken = { value: t, mintedAt: e }, t;
|
|
54
46
|
}
|
|
55
|
-
/**
|
|
56
|
-
* Two proxied calls: create the site in the visitor's account, then the
|
|
57
|
-
* deploy on it. Mapped into the anonymous `DropResponse` shape so
|
|
58
|
-
* `deployFiles` runs unchanged over either client.
|
|
59
|
-
*/
|
|
47
|
+
/** Site + deploy via the proxy, mapped into `DropResponse` so `deployFiles` runs unchanged. */
|
|
60
48
|
async createDeploy(e, t) {
|
|
61
|
-
const o = await
|
|
49
|
+
const o = await p(this.proxyBase, this.accountSlug), s = await fetch(`${this.proxyBase}/sites/${o.id}/deploys`, {
|
|
62
50
|
method: "POST",
|
|
63
51
|
credentials: "include",
|
|
64
52
|
headers: { "Content-Type": "application/json" },
|
|
65
53
|
body: JSON.stringify({ files: e, deploy_source: "drop" })
|
|
66
54
|
});
|
|
67
|
-
if (!s.ok) throw
|
|
55
|
+
if (!s.ok) throw l("deploy create", s.status);
|
|
68
56
|
const n = await s.json();
|
|
69
57
|
return {
|
|
70
58
|
id: o.id,
|
|
@@ -73,37 +61,27 @@ class M {
|
|
|
73
61
|
required: n.required ?? []
|
|
74
62
|
};
|
|
75
63
|
}
|
|
76
|
-
/**
|
|
77
|
-
* Direct to `api.netlify.com` on a freshly-aged bearer — the threaded `token`
|
|
78
|
-
* is ignored because an upload queue easily outlives the 30 seconds it was
|
|
79
|
-
* minted with. PUT (not POST) so paths with "&" etc. work.
|
|
80
|
-
*/
|
|
64
|
+
/** Direct on a fresh bearer (the threaded one has expired). PUT so paths with "&" work. */
|
|
81
65
|
async uploadFile(e, t, o, s) {
|
|
82
66
|
const n = await this.freshToken(), r = await fetch(`${this.apiBase}/deploys/${e}/files${t}`, {
|
|
83
67
|
method: "PUT",
|
|
84
68
|
headers: { "Content-Type": "application/octet-stream", Authorization: `Bearer ${n}` },
|
|
85
69
|
body: o
|
|
86
70
|
});
|
|
87
|
-
if (!r.ok) throw
|
|
71
|
+
if (!r.ok) throw l(`upload ${t}`, r.status);
|
|
88
72
|
}
|
|
89
|
-
/**
|
|
90
|
-
* Poll the deploy through the proxy until it settles: resolve on `ready`,
|
|
91
|
-
* throw on `error`/`rejected`, throw on timeout. On the session cookie rather
|
|
92
|
-
* than a bearer, so a deploy taking minutes can't outlive its own credentials.
|
|
93
|
-
*/
|
|
73
|
+
/** Polls on the session cookie, so a slow deploy can't outlive its own credentials. */
|
|
94
74
|
async waitUntilReady(e, t) {
|
|
95
|
-
const o = await
|
|
75
|
+
const o = await h({
|
|
96
76
|
proxyBase: this.proxyBase,
|
|
97
77
|
deployId: e.deploy_id,
|
|
98
78
|
pollIntervalMs: this.pollIntervalMs,
|
|
99
79
|
timeoutMs: this.timeoutMs
|
|
100
80
|
});
|
|
101
81
|
if (o.outcome !== "ready")
|
|
102
|
-
throw o.outcome === "failed" ? new
|
|
103
|
-
o.errorMessage ? `deploy failed: ${o.errorMessage}` : "deploy failed"
|
|
104
|
-
) : new Error("Deploy did not become ready in time");
|
|
82
|
+
throw o.outcome === "failed" ? new f(o.errorMessage) : new _();
|
|
105
83
|
}
|
|
106
84
|
}
|
|
107
85
|
export {
|
|
108
|
-
|
|
86
|
+
B as AuthenticatedDropClient
|
|
109
87
|
};
|
|
@@ -2,7 +2,6 @@ import { BuildSite } from './types';
|
|
|
2
2
|
export type { BuildSite } from './types';
|
|
3
3
|
/** The subset of a Netlify Build returned by `POST /sites/{id}/builds`. */
|
|
4
4
|
export interface BuildResponse {
|
|
5
|
-
/** build id */
|
|
6
5
|
id: string;
|
|
7
6
|
/** the deploy the build produces — present as soon as the build is enqueued */
|
|
8
7
|
deploy_id?: string;
|
|
@@ -13,28 +12,14 @@ export interface BuildClient {
|
|
|
13
12
|
waitUntilBuildReady(build: BuildResponse): Promise<void>;
|
|
14
13
|
}
|
|
15
14
|
export interface AuthenticatedBuildClientConfig {
|
|
16
|
-
/**
|
|
17
|
-
* Base for JSON API calls, routed through the consumer's access-control
|
|
18
|
-
* rewrite so the session cookie authenticates them. On netlify.com that
|
|
19
|
-
* rewrite is `/access-control/*` → the app's access-control function, which
|
|
20
|
-
* makes `/access-control/bb-api/api/v1` the API root (the default here).
|
|
21
|
-
*/
|
|
15
|
+
/** Root for session-cookie JSON calls, via the consumer's access-control rewrite. */
|
|
22
16
|
proxyBase?: string;
|
|
23
|
-
/**
|
|
24
|
-
* Direct API base for the zip upload. The proxy above runs in a Lambda with a
|
|
25
|
-
* ~6MB request body limit, so the multipart POST has to go straight to
|
|
26
|
-
* `api.netlify.com` (whose `/api/*` CORS policy allows any origin).
|
|
27
|
-
*/
|
|
17
|
+
/** Direct base for the zip upload — the proxy's ~6MB Lambda body limit can't carry it. */
|
|
28
18
|
apiBase?: string;
|
|
29
19
|
/**
|
|
30
|
-
* Mint
|
|
31
|
-
*
|
|
32
|
-
* `
|
|
33
|
-
* Resolving `null` means "no session" and surfaces as `NotAuthenticatedError`.
|
|
34
|
-
*
|
|
35
|
-
* Note the token issued to a non-app origin lives only 30 seconds. It is
|
|
36
|
-
* minted immediately before the upload, but a zip that takes longer than that
|
|
37
|
-
* to transfer will still be rejected mid-flight — see `createBuildFromZip`.
|
|
20
|
+
* Mint the upload bearer; `null` means "no session". Non-app origins get
|
|
21
|
+
* 30-second tokens — a zip slower than that fails mid-flight (see
|
|
22
|
+
* `createBuildFromZip`).
|
|
38
23
|
*/
|
|
39
24
|
getUploadToken: () => Promise<string | null>;
|
|
40
25
|
/** Create the site in this account. Omit to use the user's default account. */
|
|
@@ -47,16 +32,9 @@ export interface AuthenticatedBuildClientConfig {
|
|
|
47
32
|
timeoutMs?: number;
|
|
48
33
|
}
|
|
49
34
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* hands buildbot a source archive to build, mirroring what app.netlify.com does
|
|
54
|
-
* for a drag-and-drop build.
|
|
55
|
-
*
|
|
56
|
-
* Requests split across two hosts by necessity: JSON calls go through the
|
|
57
|
-
* consumer's access-control proxy (cookie session, no token handling in the
|
|
58
|
-
* browser), while the zip upload goes direct to `api.netlify.com` with a
|
|
59
|
-
* short-lived bearer token because the proxy can't carry a body that large.
|
|
35
|
+
* Builds a drop in a logged-in visitor's account: create the site, hand
|
|
36
|
+
* buildbot the source zip. JSON rides the cookie proxy; the zip goes direct
|
|
37
|
+
* with a bearer (the proxy can't carry it).
|
|
60
38
|
*/
|
|
61
39
|
export declare class AuthenticatedBuildClient implements BuildClient {
|
|
62
40
|
private proxyBase;
|
|
@@ -67,35 +45,23 @@ export declare class AuthenticatedBuildClient implements BuildClient {
|
|
|
67
45
|
private pollIntervalMs;
|
|
68
46
|
private timeoutMs;
|
|
69
47
|
constructor({ proxyBase, apiBase, getUploadToken, accountSlug, title, pollIntervalMs, timeoutMs, }: AuthenticatedBuildClientConfig);
|
|
70
|
-
/**
|
|
71
|
-
* Create the site the build deploys into. A logged-out visitor fails here,
|
|
72
|
-
* before anything is created — see `createSiteInAccount` for the error
|
|
73
|
-
* mapping that makes the signup fallback safe.
|
|
74
|
-
*/
|
|
48
|
+
/** A logged-out visitor fails here, before anything is created (see `createSiteInAccount`). */
|
|
75
49
|
createSite(): Promise<BuildSite>;
|
|
76
50
|
/**
|
|
77
|
-
*
|
|
78
|
-
* (
|
|
79
|
-
*
|
|
80
|
-
* boundary itself.
|
|
51
|
+
* One multipart request on a fresh 30s bearer — a slower zip fails mid-flight
|
|
52
|
+
* (the app origin gets 300s; raising ours is a platform-side change).
|
|
53
|
+
* Content-Type stays unset so the browser adds the multipart boundary.
|
|
81
54
|
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
55
|
+
* Never NotAuthenticatedError, including for a 401: the site exists by the
|
|
56
|
+
* time this runs, so signalling "no session" would strand it and re-stash
|
|
57
|
+
* the drop for the next visit to strand another. `createSite` is the auth
|
|
58
|
+
* gate for this path.
|
|
86
59
|
*/
|
|
87
60
|
createBuildFromZip(siteId: string, zip: File): Promise<BuildResponse>;
|
|
88
61
|
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* deploy id yet has nothing pollable, so it resolves immediately and the
|
|
93
|
-
* caller falls back to the site page.
|
|
94
|
-
*
|
|
95
|
-
* Optional in the drop flow, and off by default: a build takes minutes, so
|
|
96
|
-
* the useful hand-off is usually the project page rather than a spinner.
|
|
97
|
-
* Polling rides the session cookie, so it cannot outlive its credentials the
|
|
98
|
-
* way a 30s bearer would.
|
|
62
|
+
* Optional (a build takes minutes; the useful hand-off is the project page).
|
|
63
|
+
* Polls on the cookie, so it can't outlive its credentials; no deploy id yet
|
|
64
|
+
* means nothing pollable, so it resolves immediately.
|
|
99
65
|
*/
|
|
100
66
|
waitUntilBuildReady(build: BuildResponse): Promise<void>;
|
|
101
67
|
}
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
var d = Object.defineProperty;
|
|
2
|
-
var
|
|
3
|
-
var o = (
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
var u = (i, t, e) => t in i ? d(i, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : i[t] = e;
|
|
3
|
+
var o = (i, t, e) => u(i, typeof t != "symbol" ? t + "" : t, e);
|
|
4
|
+
import { createSiteInAccount as c, pollDeployUntilSettled as h } from "./authedApi.js";
|
|
5
|
+
import { DEFAULT_PROXY_BASE as m, DEFAULT_API_BASE as B, DEFAULT_POLL_INTERVAL_MS as y, DEFAULT_TIMEOUT_MS as w } from "./constants.js";
|
|
6
|
+
import { BuildCreateError as l, BuildFailedError as M, BuildTimeoutError as T } from "./errors.js";
|
|
6
7
|
class _ {
|
|
7
8
|
constructor({
|
|
8
|
-
proxyBase: t =
|
|
9
|
-
apiBase: e =
|
|
9
|
+
proxyBase: t = m,
|
|
10
|
+
apiBase: e = B,
|
|
10
11
|
getUploadToken: a,
|
|
11
|
-
accountSlug:
|
|
12
|
-
title:
|
|
13
|
-
pollIntervalMs: n =
|
|
14
|
-
timeoutMs:
|
|
12
|
+
accountSlug: r,
|
|
13
|
+
title: s = "Build from drop deployment",
|
|
14
|
+
pollIntervalMs: n = y,
|
|
15
|
+
timeoutMs: p = w
|
|
15
16
|
}) {
|
|
16
17
|
o(this, "proxyBase");
|
|
17
18
|
o(this, "apiBase");
|
|
@@ -20,63 +21,50 @@ class _ {
|
|
|
20
21
|
o(this, "title");
|
|
21
22
|
o(this, "pollIntervalMs");
|
|
22
23
|
o(this, "timeoutMs");
|
|
23
|
-
this.proxyBase = t, this.apiBase = e, this.getUploadToken = a, this.accountSlug =
|
|
24
|
+
this.proxyBase = t, this.apiBase = e, this.getUploadToken = a, this.accountSlug = r, this.title = s, this.pollIntervalMs = n, this.timeoutMs = p;
|
|
24
25
|
}
|
|
25
|
-
/**
|
|
26
|
-
* Create the site the build deploys into. A logged-out visitor fails here,
|
|
27
|
-
* before anything is created — see `createSiteInAccount` for the error
|
|
28
|
-
* mapping that makes the signup fallback safe.
|
|
29
|
-
*/
|
|
26
|
+
/** A logged-out visitor fails here, before anything is created (see `createSiteInAccount`). */
|
|
30
27
|
async createSite() {
|
|
31
|
-
return
|
|
28
|
+
return c(this.proxyBase, this.accountSlug);
|
|
32
29
|
}
|
|
33
30
|
/**
|
|
34
|
-
*
|
|
35
|
-
* (
|
|
36
|
-
*
|
|
37
|
-
* boundary itself.
|
|
31
|
+
* One multipart request on a fresh 30s bearer — a slower zip fails mid-flight
|
|
32
|
+
* (the app origin gets 300s; raising ours is a platform-side change).
|
|
33
|
+
* Content-Type stays unset so the browser adds the multipart boundary.
|
|
38
34
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
35
|
+
* Never NotAuthenticatedError, including for a 401: the site exists by the
|
|
36
|
+
* time this runs, so signalling "no session" would strand it and re-stash
|
|
37
|
+
* the drop for the next visit to strand another. `createSite` is the auth
|
|
38
|
+
* gate for this path.
|
|
43
39
|
*/
|
|
44
40
|
async createBuildFromZip(t, e) {
|
|
45
41
|
const a = await this.getUploadToken();
|
|
46
42
|
if (!a) throw new l();
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
const
|
|
43
|
+
const r = new FormData();
|
|
44
|
+
r.append("zip", e), r.append("title", this.title);
|
|
45
|
+
const s = await fetch(`${this.apiBase}/sites/${t}/builds`, {
|
|
50
46
|
method: "POST",
|
|
51
47
|
headers: { Authorization: `Bearer ${a}` },
|
|
52
|
-
body:
|
|
48
|
+
body: r
|
|
53
49
|
});
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
return i.json();
|
|
50
|
+
if (!s.ok) throw new l(s.status);
|
|
51
|
+
return s.json();
|
|
57
52
|
}
|
|
58
53
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* deploy id yet has nothing pollable, so it resolves immediately and the
|
|
63
|
-
* caller falls back to the site page.
|
|
64
|
-
*
|
|
65
|
-
* Optional in the drop flow, and off by default: a build takes minutes, so
|
|
66
|
-
* the useful hand-off is usually the project page rather than a spinner.
|
|
67
|
-
* Polling rides the session cookie, so it cannot outlive its credentials the
|
|
68
|
-
* way a 30s bearer would.
|
|
54
|
+
* Optional (a build takes minutes; the useful hand-off is the project page).
|
|
55
|
+
* Polls on the cookie, so it can't outlive its credentials; no deploy id yet
|
|
56
|
+
* means nothing pollable, so it resolves immediately.
|
|
69
57
|
*/
|
|
70
58
|
async waitUntilBuildReady(t) {
|
|
71
59
|
if (!t.deploy_id) return;
|
|
72
|
-
const e = await
|
|
60
|
+
const e = await h({
|
|
73
61
|
proxyBase: this.proxyBase,
|
|
74
62
|
deployId: t.deploy_id,
|
|
75
63
|
pollIntervalMs: this.pollIntervalMs,
|
|
76
64
|
timeoutMs: this.timeoutMs
|
|
77
65
|
});
|
|
78
66
|
if (e.outcome !== "ready")
|
|
79
|
-
throw e.outcome === "failed" ? new
|
|
67
|
+
throw e.outcome === "failed" ? new M(e.errorMessage) : new T();
|
|
80
68
|
}
|
|
81
69
|
}
|
|
82
70
|
export {
|
|
@@ -1,20 +1,10 @@
|
|
|
1
1
|
import { InferredBuildSettings } from './detectBuild';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* budget. When they return to this origin authenticated, the zone deploys the
|
|
9
|
-
* stash automatically instead of asking them to drop again.
|
|
10
|
-
*
|
|
11
|
-
* Storage is per-origin: a stash written on www.netlify.com can only ever be
|
|
12
|
-
* resumed on www.netlify.com. That is the design, not a limitation — it is
|
|
13
|
-
* exactly why the hand-off page must bring the visitor back here.
|
|
14
|
-
*
|
|
15
|
-
* Every operation is failure-swallowing: the stash is an enhancement, and a
|
|
16
|
-
* browser without IndexedDB (or with a full quota) degrades to the plain
|
|
17
|
-
* signup redirect the zone always supported.
|
|
3
|
+
* Carries a build drop across the signup round-trip: stash the zip in
|
|
4
|
+
* IndexedDB before the hand-off, deploy it when the visitor returns
|
|
5
|
+
* authenticated. Storage is per-origin by design — the hand-off must bring
|
|
6
|
+
* them back here. Every operation swallows failures: no storage means the
|
|
7
|
+
* plain signup redirect the zone always supported.
|
|
18
8
|
*/
|
|
19
9
|
export interface DropBuildStash {
|
|
20
10
|
/** The `source.zip` exactly as it would have been uploaded. */
|
|
@@ -23,11 +13,7 @@ export interface DropBuildStash {
|
|
|
23
13
|
buildSettings: InferredBuildSettings | null;
|
|
24
14
|
createdAt: number;
|
|
25
15
|
}
|
|
26
|
-
/**
|
|
27
|
-
* Stash a drop's source archive for an automatic deploy after signup. Returns
|
|
28
|
-
* whether it was actually stored — callers use that only to phrase the
|
|
29
|
-
* hand-off, never to block it.
|
|
30
|
-
*/
|
|
16
|
+
/** Stash the archive for auto-deploy after signup. The result phrases the hand-off, never blocks it. */
|
|
31
17
|
export declare function saveDropStash(zipFile: File, buildSettings?: InferredBuildSettings | null): Promise<boolean>;
|
|
32
18
|
/** The stashed drop, or null when there is none, it expired, or storage is unavailable. */
|
|
33
19
|
export declare function loadDropStash(): Promise<DropBuildStash | null>;
|
|
@@ -11,11 +11,13 @@ export interface DropClient {
|
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
13
|
* Unauthenticated drop client. Authorized server-side by the Referer header,
|
|
14
|
-
* so it only works from an allowlisted origin
|
|
14
|
+
* so it only works from an allowlisted origin.
|
|
15
15
|
*/
|
|
16
16
|
export declare class AnonymousDropClient implements DropClient {
|
|
17
17
|
private apiBase;
|
|
18
|
-
|
|
18
|
+
private pollIntervalMs;
|
|
19
|
+
private timeoutMs;
|
|
20
|
+
constructor(apiBase?: string, pollIntervalMs?: number, timeoutMs?: number);
|
|
19
21
|
getToken(): Promise<string>;
|
|
20
22
|
createDeploy(files: Digest, token: string): Promise<DropResponse>;
|
|
21
23
|
uploadFile(deployId: string, path: string, content: ArrayBuffer, token: string): Promise<void>;
|
package/dist/drop-core/client.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { DEFAULT_API_BASE as p, DEFAULT_POLL_INTERVAL_MS as l, DEFAULT_TIMEOUT_MS as h } from "./constants.js";
|
|
2
|
+
import { DeployTimeoutError as d } from "./errors.js";
|
|
3
|
+
function i(r, t) {
|
|
4
|
+
return Object.assign(new Error(`${r} failed: ${t}`), { status: t });
|
|
3
5
|
}
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
this.apiBase = t;
|
|
6
|
+
class y {
|
|
7
|
+
constructor(t = p, o = l, e = h) {
|
|
8
|
+
this.apiBase = t, this.pollIntervalMs = o, this.timeoutMs = e;
|
|
8
9
|
}
|
|
9
10
|
async getToken() {
|
|
10
11
|
const t = await fetch(`${this.apiBase}/drop/token`, { method: "POST" });
|
|
11
|
-
if (!t.ok) throw
|
|
12
|
+
if (!t.ok) throw i("drop/token", t.status);
|
|
12
13
|
return (await t.json()).token;
|
|
13
14
|
}
|
|
14
15
|
async createDeploy(t, o) {
|
|
@@ -17,29 +18,30 @@ class p {
|
|
|
17
18
|
headers: { "Content-Type": "application/json" },
|
|
18
19
|
body: JSON.stringify({ files: t, token: o })
|
|
19
20
|
});
|
|
20
|
-
if (!e.ok) throw
|
|
21
|
+
if (!e.ok) throw i("drop", e.status);
|
|
21
22
|
return e.json();
|
|
22
23
|
}
|
|
23
24
|
async uploadFile(t, o, e, a) {
|
|
24
|
-
const
|
|
25
|
+
const s = await fetch(`${this.apiBase}/deploys/${t}/files${o}`, {
|
|
25
26
|
method: "PUT",
|
|
26
27
|
headers: { "Content-Type": "application/octet-stream", Authorization: `Bearer ${a}` },
|
|
27
28
|
body: e
|
|
28
29
|
});
|
|
29
|
-
if (!
|
|
30
|
+
if (!s.ok) throw i(`upload ${o}`, s.status);
|
|
30
31
|
}
|
|
31
32
|
async waitUntilReady(t, o) {
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
const e = this.timeoutMs / this.pollIntervalMs;
|
|
34
|
+
for (let a = 0; a < e; a++) {
|
|
35
|
+
const s = await fetch(`${this.apiBase}/deploys/${t.deploy_id}`, {
|
|
34
36
|
headers: { Authorization: `Bearer ${o}` }
|
|
35
37
|
});
|
|
36
|
-
if (
|
|
37
|
-
await new Promise((
|
|
38
|
+
if (s.ok && (await s.json()).state === "ready") return;
|
|
39
|
+
await new Promise((n) => setTimeout(n, this.pollIntervalMs));
|
|
38
40
|
}
|
|
39
|
-
throw new
|
|
41
|
+
throw new d();
|
|
40
42
|
}
|
|
41
43
|
}
|
|
42
44
|
export {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
y as AnonymousDropClient,
|
|
46
|
+
i as apiError
|
|
45
47
|
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Tunables shared across drop-core. Module-specific constants stay with their module. */
|
|
2
|
+
export declare const DEFAULT_PROXY_BASE = "/access-control/bb-api/api/v1";
|
|
3
|
+
/** Direct api.netlify.com root for the uploads the proxy's ~6MB Lambda body limit can't carry. */
|
|
4
|
+
export declare const DEFAULT_API_BASE = "https://api.netlify.com/api/v1";
|
|
5
|
+
export declare const DEFAULT_POLL_INTERVAL_MS = 1000;
|
|
6
|
+
export declare const DEFAULT_TIMEOUT_MS: number;
|
|
7
|
+
export declare const MAX_CONCURRENT_UPLOADS = 5;
|
|
8
|
+
export declare const TOKEN_REUSE_MS: number;
|
|
9
|
+
export declare const UPLOAD_RETRY_DELAYS_MS: readonly number[];
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const _ = "/access-control/bb-api/api/v1", E = "https://api.netlify.com/api/v1", t = 1e3, A = 6e5, c = 5, o = 2e4, s = [1e3, 2e3, 3e3];
|
|
2
|
+
export {
|
|
3
|
+
E as DEFAULT_API_BASE,
|
|
4
|
+
t as DEFAULT_POLL_INTERVAL_MS,
|
|
5
|
+
_ as DEFAULT_PROXY_BASE,
|
|
6
|
+
A as DEFAULT_TIMEOUT_MS,
|
|
7
|
+
c as MAX_CONCURRENT_UPLOADS,
|
|
8
|
+
o as TOKEN_REUSE_MS,
|
|
9
|
+
s as UPLOAD_RETRY_DELAYS_MS
|
|
10
|
+
};
|