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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/dist/components/index.d.ts +2 -2
  2. package/dist/components/preact/DropZone/DropZone.d.ts +28 -2
  3. package/dist/components/preact/DropZone/DropZone.js +58 -51
  4. package/dist/components/preact/DropZone/DropZone.test.d.ts +1 -0
  5. package/dist/components/preact/DropZone/index.d.ts +3 -3
  6. package/dist/components/preact/DropZone/index.js +25 -23
  7. package/dist/components/preact/DropZone/useDropDeploy.d.ts +11 -6
  8. package/dist/components/preact/DropZone/useDropDeploy.js +183 -129
  9. package/dist/components/preact/DropZone/useDropDeploy.test.d.ts +1 -0
  10. package/dist/components/preact/index.d.ts +2 -2
  11. package/dist/components/preact/index.js +25 -19
  12. package/dist/drop-core/analytics.d.ts +107 -0
  13. package/dist/drop-core/analytics.js +21 -0
  14. package/dist/drop-core/analytics.test.d.ts +1 -0
  15. package/dist/drop-core/authedApi.d.ts +55 -0
  16. package/dist/drop-core/authedApi.js +35 -0
  17. package/dist/drop-core/authedApi.test.d.ts +1 -0
  18. package/dist/drop-core/authedDropClient.d.ts +100 -0
  19. package/dist/drop-core/authedDropClient.js +109 -0
  20. package/dist/drop-core/authedDropClient.test.d.ts +1 -0
  21. package/dist/{components/preact/DropZone/drop-core → drop-core}/buildClient.d.ts +32 -26
  22. package/dist/drop-core/buildClient.js +84 -0
  23. package/dist/drop-core/buildClient.test.d.ts +1 -0
  24. package/dist/drop-core/buildStash.d.ts +37 -0
  25. package/dist/drop-core/buildStash.js +84 -0
  26. package/dist/drop-core/buildStash.test.d.ts +0 -0
  27. package/dist/{components/preact/DropZone/drop-core → drop-core}/deploy.d.ts +7 -0
  28. package/dist/drop-core/deploy.js +53 -0
  29. package/dist/drop-core/deploy.test.d.ts +1 -0
  30. package/dist/drop-core/detectBuild.test.d.ts +1 -0
  31. package/dist/drop-core/digest.test.d.ts +1 -0
  32. package/dist/{components/preact/DropZone/drop-core → drop-core}/errors.js +1 -1
  33. package/dist/drop-core/errors.test.d.ts +1 -0
  34. package/dist/drop-core/fileTypes.test.d.ts +1 -0
  35. package/dist/{components/preact/DropZone/drop-core → drop-core}/index.d.ts +3 -0
  36. package/dist/drop-core/index.js +51 -0
  37. package/dist/{components/preact/DropZone/drop-core → drop-core}/readFiles.js +2 -2
  38. package/dist/drop-core/readFiles.test.d.ts +1 -0
  39. package/dist/{components/preact/DropZone/drop-core → drop-core}/types.d.ts +10 -0
  40. package/dist/{components/preact/DropZone/drop-core → drop-core}/zip.js +1 -1
  41. package/dist/drop-core/zip.test.d.ts +1 -0
  42. package/package.json +4 -1
  43. package/packages/components/index.ts +19 -0
  44. package/packages/components/preact/DropZone/DropZone.test.ts +71 -0
  45. package/packages/components/preact/DropZone/DropZone.tsx +32 -2
  46. package/packages/components/preact/DropZone/index.tsx +14 -3
  47. package/packages/components/preact/DropZone/useDropDeploy.test.ts +376 -0
  48. package/packages/components/preact/DropZone/useDropDeploy.ts +305 -134
  49. package/packages/components/preact/index.ts +19 -2
  50. package/packages/drop-core/analytics.test.ts +17 -0
  51. package/packages/drop-core/analytics.ts +122 -0
  52. package/packages/drop-core/authedApi.test.ts +100 -0
  53. package/packages/drop-core/authedApi.ts +94 -0
  54. package/packages/drop-core/authedDropClient.test.ts +196 -0
  55. package/packages/drop-core/authedDropClient.ts +210 -0
  56. package/packages/drop-core/buildClient.test.ts +112 -0
  57. package/packages/{components/preact/DropZone/drop-core → drop-core}/buildClient.ts +55 -70
  58. package/packages/drop-core/buildStash.test.ts +85 -0
  59. package/packages/drop-core/buildStash.ts +185 -0
  60. package/packages/drop-core/deploy.test.ts +135 -0
  61. package/packages/{components/preact/DropZone/drop-core → drop-core}/deploy.ts +18 -4
  62. package/packages/drop-core/detectBuild.test.ts +139 -0
  63. package/packages/drop-core/digest.test.ts +24 -0
  64. package/packages/drop-core/errors.test.ts +69 -0
  65. package/packages/{components/preact/DropZone/drop-core → drop-core}/errors.ts +1 -1
  66. package/packages/drop-core/fileTypes.test.ts +37 -0
  67. package/packages/{components/preact/DropZone/drop-core → drop-core}/index.ts +3 -0
  68. package/packages/drop-core/readFiles.test.ts +125 -0
  69. package/packages/{components/preact/DropZone/drop-core → drop-core}/types.ts +12 -6
  70. package/packages/drop-core/zip.test.ts +92 -0
  71. package/dist/components/preact/DropZone/drop-core/authedDropClient.d.ts +0 -77
  72. package/dist/components/preact/DropZone/drop-core/authedDropClient.js +0 -97
  73. package/dist/components/preact/DropZone/drop-core/buildClient.js +0 -92
  74. package/dist/components/preact/DropZone/drop-core/deploy.js +0 -48
  75. package/dist/components/preact/DropZone/drop-core/index.js +0 -36
  76. package/packages/components/preact/DropZone/drop-core/authedDropClient.ts +0 -185
  77. /package/dist/{components/preact/DropZone/drop-core → drop-core}/client.d.ts +0 -0
  78. /package/dist/{components/preact/DropZone/drop-core → drop-core}/client.js +0 -0
  79. /package/dist/{components/preact/DropZone/drop-core → drop-core}/detectBuild.d.ts +0 -0
  80. /package/dist/{components/preact/DropZone/drop-core → drop-core}/detectBuild.js +0 -0
  81. /package/dist/{components/preact/DropZone/drop-core → drop-core}/digest.d.ts +0 -0
  82. /package/dist/{components/preact/DropZone/drop-core → drop-core}/digest.js +0 -0
  83. /package/dist/{components/preact/DropZone/drop-core → drop-core}/errors.d.ts +0 -0
  84. /package/dist/{components/preact/DropZone/drop-core → drop-core}/fileTypes.d.ts +0 -0
  85. /package/dist/{components/preact/DropZone/drop-core → drop-core}/fileTypes.js +0 -0
  86. /package/dist/{components/preact/DropZone/drop-core → drop-core}/readFiles.d.ts +0 -0
  87. /package/dist/{components/preact/DropZone/drop-core → drop-core}/types.js +0 -0
  88. /package/dist/{components/preact/DropZone/drop-core → drop-core}/zip.d.ts +0 -0
  89. /package/packages/{components/preact/DropZone/drop-core → drop-core}/client.ts +0 -0
  90. /package/packages/{components/preact/DropZone/drop-core → drop-core}/detectBuild.ts +0 -0
  91. /package/packages/{components/preact/DropZone/drop-core → drop-core}/digest.ts +0 -0
  92. /package/packages/{components/preact/DropZone/drop-core → drop-core}/fileTypes.ts +0 -0
  93. /package/packages/{components/preact/DropZone/drop-core → drop-core}/readFiles.ts +0 -0
  94. /package/packages/{components/preact/DropZone/drop-core → drop-core}/zip.ts +0 -0
@@ -0,0 +1,100 @@
1
+ import { DropClient } from './client';
2
+ import { Digest, DropResponse } from './types';
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
+ */
10
+ 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
+ */
16
+ apiBase?: string;
17
+ /**
18
+ * Mint a bearer token for those direct uploads. On netlify.com this is
19
+ * `GET /access-control/generate-access-control-token`, whose
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.
25
+ */
26
+ getUploadToken: () => Promise<string | null>;
27
+ /** Create the site in this account. Omit to use the user's default account. */
28
+ accountSlug?: string;
29
+ /** How often to re-read the deploy while waiting. Default 1s. */
30
+ pollIntervalMs?: number;
31
+ /** How long to wait before giving up on the deploy. Default 10 minutes. */
32
+ timeoutMs?: number;
33
+ }
34
+ /**
35
+ * Authenticated drop client for logged-in visitors. Unlike `AnonymousDropClient`
36
+ * — which creates an account-less site the visitor must later claim — this
37
+ * deploys straight into the visitor's account via the sites/deploys API,
38
+ * mirroring what app.netlify.com does for a logged-in drag-and-drop deploy.
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.
54
+ */
55
+ export declare class AuthenticatedDropClient implements DropClient {
56
+ private proxyBase;
57
+ private apiBase;
58
+ private getUploadToken;
59
+ private accountSlug?;
60
+ private pollIntervalMs;
61
+ private timeoutMs;
62
+ private cachedToken?;
63
+ constructor({ proxyBase, apiBase, getUploadToken, accountSlug, pollIntervalMs, timeoutMs, }: AuthenticatedDropClientConfig);
64
+ /**
65
+ * The auth gate. Minting here — before anything is created — is what lets a
66
+ * logged-out visitor fail cleanly and fall back to the anonymous flow. The
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.
70
+ */
71
+ getToken(): Promise<string>;
72
+ /**
73
+ * A token young enough to authorize an upload, minting a new one once the
74
+ * cached one nears its 30s expiry.
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.
80
+ */
81
+ 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
+ */
87
+ 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
+ */
93
+ 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
+ */
99
+ waitUntilReady(deploy: DropResponse, _token: string): Promise<void>;
100
+ }
@@ -0,0 +1,109 @@
1
+ var l = Object.defineProperty;
2
+ var c = (i, e, t) => e in i ? l(i, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : i[e] = t;
3
+ var a = (i, e, t) => c(i, typeof e != "symbol" ? e + "" : e, t);
4
+ import { DEFAULT_PROXY_BASE as p, DEFAULT_API_BASE as h, DEFAULT_POLL_INTERVAL_MS as u, DEFAULT_TIMEOUT_MS as y, createSiteInAccount as T, pollDeployUntilSettled as w } from "./authedApi.js";
5
+ import { apiError as d } from "./client.js";
6
+ import { NotAuthenticatedError as m } from "./errors.js";
7
+ const k = 20 * 1e3;
8
+ class M {
9
+ constructor({
10
+ proxyBase: e = p,
11
+ apiBase: t = h,
12
+ getUploadToken: o,
13
+ accountSlug: s,
14
+ pollIntervalMs: n = u,
15
+ timeoutMs: r = y
16
+ }) {
17
+ a(this, "proxyBase");
18
+ a(this, "apiBase");
19
+ a(this, "getUploadToken");
20
+ a(this, "accountSlug");
21
+ a(this, "pollIntervalMs");
22
+ a(this, "timeoutMs");
23
+ a(this, "cachedToken");
24
+ this.proxyBase = e, this.apiBase = t, this.getUploadToken = o, this.accountSlug = s, this.pollIntervalMs = n, this.timeoutMs = r;
25
+ }
26
+ /**
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. The
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.
32
+ */
33
+ async getToken() {
34
+ const e = await this.getUploadToken();
35
+ if (!e) throw new m();
36
+ return this.cachedToken = { value: e, mintedAt: Date.now() }, e;
37
+ }
38
+ /**
39
+ * A token young enough to authorize an upload, minting a new one once the
40
+ * cached one nears its 30s expiry.
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.
46
+ */
47
+ async freshToken() {
48
+ const e = Date.now();
49
+ if (this.cachedToken && e - this.cachedToken.mintedAt < k)
50
+ return this.cachedToken.value;
51
+ const t = await this.getUploadToken();
52
+ if (!t) throw new Error("could not renew the upload token");
53
+ return this.cachedToken = { value: t, mintedAt: e }, t;
54
+ }
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
+ */
60
+ async createDeploy(e, t) {
61
+ const o = await T(this.proxyBase, this.accountSlug), s = await fetch(`${this.proxyBase}/sites/${o.id}/deploys`, {
62
+ method: "POST",
63
+ credentials: "include",
64
+ headers: { "Content-Type": "application/json" },
65
+ body: JSON.stringify({ files: e, deploy_source: "drop" })
66
+ });
67
+ if (!s.ok) throw d("deploy create", s.status);
68
+ const n = await s.json();
69
+ return {
70
+ id: o.id,
71
+ deploy_id: n.id,
72
+ subdomain: o.name,
73
+ required: n.required ?? []
74
+ };
75
+ }
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
+ */
81
+ async uploadFile(e, t, o, s) {
82
+ const n = await this.freshToken(), r = await fetch(`${this.apiBase}/deploys/${e}/files${t}`, {
83
+ method: "PUT",
84
+ headers: { "Content-Type": "application/octet-stream", Authorization: `Bearer ${n}` },
85
+ body: o
86
+ });
87
+ if (!r.ok) throw d(`upload ${t}`, r.status);
88
+ }
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
+ */
94
+ async waitUntilReady(e, t) {
95
+ const o = await w({
96
+ proxyBase: this.proxyBase,
97
+ deployId: e.deploy_id,
98
+ pollIntervalMs: this.pollIntervalMs,
99
+ timeoutMs: this.timeoutMs
100
+ });
101
+ if (o.outcome !== "ready")
102
+ throw o.outcome === "failed" ? new Error(
103
+ o.errorMessage ? `deploy failed: ${o.errorMessage}` : "deploy failed"
104
+ ) : new Error("Deploy did not become ready in time");
105
+ }
106
+ }
107
+ export {
108
+ M as AuthenticatedDropClient
109
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -1,13 +1,5 @@
1
- /** The subset of a Netlify Site the drop flow needs to redirect and report. */
2
- export interface BuildSite {
3
- /** site id (UUID) */
4
- id: string;
5
- /** the *.netlify.app subdomain, also the app's URL segment */
6
- name: string;
7
- /** https://<name>.netlify.app */
8
- ssl_url?: string;
9
- url?: string;
10
- }
1
+ import { BuildSite } from './types';
2
+ export type { BuildSite } from './types';
11
3
  /** The subset of a Netlify Build returned by `POST /sites/{id}/builds`. */
12
4
  export interface BuildResponse {
13
5
  /** build id */
@@ -22,20 +14,27 @@ export interface BuildClient {
22
14
  }
23
15
  export interface AuthenticatedBuildClientConfig {
24
16
  /**
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.
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
+ */
22
+ 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).
32
27
  */
33
28
  apiBase?: string;
34
29
  /**
35
- * Mint a short-lived bearer token authorizing the calls. On netlify.com this
36
- * is `GET /access-control/generate-access-control-token`, whose
30
+ * Mint a bearer token for that direct upload. On netlify.com this is
31
+ * `GET /access-control/generate-access-control-token`, whose
37
32
  * `accessControlToken` the API decrypts back into the user's access token.
38
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`.
39
38
  */
40
39
  getUploadToken: () => Promise<string | null>;
41
40
  /** Create the site in this account. Omit to use the user's default account. */
@@ -60,16 +59,18 @@ export interface AuthenticatedBuildClientConfig {
60
59
  * short-lived bearer token because the proxy can't carry a body that large.
61
60
  */
62
61
  export declare class AuthenticatedBuildClient implements BuildClient {
62
+ private proxyBase;
63
63
  private apiBase;
64
64
  private getUploadToken;
65
65
  private accountSlug?;
66
66
  private title;
67
67
  private pollIntervalMs;
68
68
  private timeoutMs;
69
- constructor({ apiBase, getUploadToken, accountSlug, title, pollIntervalMs, timeoutMs, }: AuthenticatedBuildClientConfig);
69
+ constructor({ proxyBase, apiBase, getUploadToken, accountSlug, title, pollIntervalMs, timeoutMs, }: AuthenticatedBuildClientConfig);
70
70
  /**
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.
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.
73
74
  */
74
75
  createSite(): Promise<BuildSite>;
75
76
  /**
@@ -77,6 +78,11 @@ export declare class AuthenticatedBuildClient implements BuildClient {
77
78
  * (see the note on `apiBase`), authorized by a freshly minted bearer token.
78
79
  * `Content-Type` is left unset on purpose so the browser adds the multipart
79
80
  * boundary itself.
81
+ *
82
+ * Known ceiling: this is one request on a 30s token, and re-minting can't
83
+ * help once it is in flight. A zip that takes longer than that to upload will
84
+ * fail — the app origin gets 300s for exactly this reason, and lifting the
85
+ * limit for other origins is a platform-side change.
80
86
  */
81
87
  createBuildFromZip(siteId: string, zip: File): Promise<BuildResponse>;
82
88
  /**
@@ -86,10 +92,10 @@ export declare class AuthenticatedBuildClient implements BuildClient {
86
92
  * deploy id yet has nothing pollable, so it resolves immediately and the
87
93
  * caller falls back to the site page.
88
94
  *
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.
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.
93
99
  */
94
100
  waitUntilBuildReady(build: BuildResponse): Promise<void>;
95
101
  }
@@ -0,0 +1,84 @@
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 o = (r, t, e) => p(r, typeof t != "symbol" ? t + "" : t, e);
4
+ import { DEFAULT_PROXY_BASE as c, DEFAULT_API_BASE as h, DEFAULT_POLL_INTERVAL_MS as B, DEFAULT_TIMEOUT_MS as m, createSiteInAccount as y, pollDeployUntilSettled as w } from "./authedApi.js";
5
+ import { NotAuthenticatedError as l, BuildCreateError as A, BuildFailedError as E, BuildTimeoutError as M } from "./errors.js";
6
+ class _ {
7
+ constructor({
8
+ proxyBase: t = c,
9
+ apiBase: e = h,
10
+ getUploadToken: a,
11
+ accountSlug: s,
12
+ title: i = "Build from drop deployment",
13
+ pollIntervalMs: n = B,
14
+ timeoutMs: u = m
15
+ }) {
16
+ o(this, "proxyBase");
17
+ o(this, "apiBase");
18
+ o(this, "getUploadToken");
19
+ o(this, "accountSlug");
20
+ o(this, "title");
21
+ o(this, "pollIntervalMs");
22
+ o(this, "timeoutMs");
23
+ this.proxyBase = t, this.apiBase = e, this.getUploadToken = a, this.accountSlug = s, this.title = i, this.pollIntervalMs = n, this.timeoutMs = u;
24
+ }
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
+ */
30
+ async createSite() {
31
+ return y(this.proxyBase, this.accountSlug);
32
+ }
33
+ /**
34
+ * Upload the source archive and enqueue a build. Direct to `api.netlify.com`
35
+ * (see the note on `apiBase`), authorized by a freshly minted bearer token.
36
+ * `Content-Type` is left unset on purpose so the browser adds the multipart
37
+ * boundary itself.
38
+ *
39
+ * Known ceiling: this is one request on a 30s token, and re-minting can't
40
+ * help once it is in flight. A zip that takes longer than that to upload will
41
+ * fail — the app origin gets 300s for exactly this reason, and lifting the
42
+ * limit for other origins is a platform-side change.
43
+ */
44
+ async createBuildFromZip(t, e) {
45
+ const a = await this.getUploadToken();
46
+ if (!a) throw new l();
47
+ const s = new FormData();
48
+ s.append("zip", e), s.append("title", this.title);
49
+ const i = await fetch(`${this.apiBase}/sites/${t}/builds`, {
50
+ method: "POST",
51
+ headers: { Authorization: `Bearer ${a}` },
52
+ body: s
53
+ });
54
+ if (i.status === 401 || i.status === 403) throw new l();
55
+ if (!i.ok) throw new A(i.status);
56
+ return i.json();
57
+ }
58
+ /**
59
+ * Poll the build's deploy until it settles, mirroring
60
+ * `AnonymousDropClient.waitUntilReady` — resolve on `ready`, throw on
61
+ * `error`/`rejected`, throw on timeout. A build that hasn't been given a
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.
69
+ */
70
+ async waitUntilBuildReady(t) {
71
+ if (!t.deploy_id) return;
72
+ const e = await w({
73
+ proxyBase: this.proxyBase,
74
+ deployId: t.deploy_id,
75
+ pollIntervalMs: this.pollIntervalMs,
76
+ timeoutMs: this.timeoutMs
77
+ });
78
+ if (e.outcome !== "ready")
79
+ throw e.outcome === "failed" ? new E(e.errorMessage) : new M();
80
+ }
81
+ }
82
+ export {
83
+ _ as AuthenticatedBuildClient
84
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,37 @@
1
+ import { InferredBuildSettings } from './detectBuild';
2
+ /**
3
+ * Carry a build-required drop across the signup round-trip.
4
+ *
5
+ * A logged-out visitor's drop can't be built (builds have no anonymous
6
+ * equivalent), so before handing them to signup the zone stashes the source
7
+ * archive here — IndexedDB, because the zip is far beyond localStorage's
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.
18
+ */
19
+ export interface DropBuildStash {
20
+ /** The `source.zip` exactly as it would have been uploaded. */
21
+ zipFile: File;
22
+ /** Settings synthesised into the archive's netlify.toml, if any. */
23
+ buildSettings: InferredBuildSettings | null;
24
+ createdAt: number;
25
+ }
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
+ */
31
+ export declare function saveDropStash(zipFile: File, buildSettings?: InferredBuildSettings | null): Promise<boolean>;
32
+ /** The stashed drop, or null when there is none, it expired, or storage is unavailable. */
33
+ export declare function loadDropStash(): Promise<DropBuildStash | null>;
34
+ /** Remove the stash and its marker. Idempotent; never throws. */
35
+ export declare function clearDropStash(): Promise<void>;
36
+ /** Cheap synchronous check so page loads don't open IndexedDB just to look for a stash. */
37
+ export declare function hasDropStashMarker(): boolean;
@@ -0,0 +1,84 @@
1
+ const f = "netlify-drop";
2
+ const s = "build-stash", c = "latest", i = "dropBuildStash";
3
+ const S = (e) => Promise.race([
4
+ e,
5
+ new Promise((r, t) => {
6
+ setTimeout(() => t(new Error("IndexedDB operation timed out")), 3e3);
7
+ })
8
+ ]), d = () => new Promise((e, r) => {
9
+ const t = indexedDB.open(f, 1);
10
+ t.onupgradeneeded = () => {
11
+ t.result.createObjectStore(s);
12
+ }, t.onsuccess = () => e(t.result), t.onerror = () => r(t.error);
13
+ }), u = (e, r) => S(
14
+ d().then(
15
+ (t) => new Promise((o, a) => {
16
+ const n = r(t.transaction(s, e).objectStore(s));
17
+ n.onsuccess = () => {
18
+ t.close(), o(n.result);
19
+ }, n.onerror = () => {
20
+ t.close(), a(n.error);
21
+ };
22
+ })
23
+ )
24
+ ), p = async (e) => {
25
+ const { quota: r, usage: t } = await navigator.storage.estimate();
26
+ return r !== void 0 && t !== void 0 && r - t > e * 2;
27
+ }, T = () => {
28
+ localStorage.setItem(i, JSON.stringify({ expiresAt: Date.now() + 864e5 }));
29
+ }, l = () => {
30
+ localStorage.removeItem(i);
31
+ };
32
+ async function A(e, r = null) {
33
+ var t;
34
+ try {
35
+ if (typeof indexedDB > "u" || !((t = navigator.storage) != null && t.estimate) || e.size > 209715200 || !await p(e.size))
36
+ return !1;
37
+ const o = { zipFile: e, buildSettings: r, createdAt: Date.now() };
38
+ return await u("readwrite", (a) => a.put(o, c)), T(), !0;
39
+ } catch {
40
+ return !1;
41
+ }
42
+ }
43
+ async function D() {
44
+ try {
45
+ if (typeof indexedDB > "u") return null;
46
+ const e = await u(
47
+ "readonly",
48
+ (t) => t.get(c)
49
+ ), r = e && _(e.zipFile);
50
+ return !e || !r ? null : Date.now() - e.createdAt > 864e5 ? (await y(), null) : { ...e, zipFile: r };
51
+ } catch {
52
+ return null;
53
+ }
54
+ }
55
+ function _(e) {
56
+ return e instanceof File && e.name ? e : e instanceof Blob || e && typeof e.arrayBuffer == "function" ? new File([e], "source.zip", { type: "application/zip" }) : null;
57
+ }
58
+ async function y() {
59
+ try {
60
+ l();
61
+ } catch {
62
+ }
63
+ try {
64
+ if (typeof indexedDB > "u") return;
65
+ await u("readwrite", (e) => e.delete(c));
66
+ } catch {
67
+ }
68
+ }
69
+ function O() {
70
+ try {
71
+ const e = localStorage.getItem(i);
72
+ if (!e) return !1;
73
+ const { expiresAt: r } = JSON.parse(e);
74
+ return typeof r != "number" || Date.now() > r ? (l(), !1) : !0;
75
+ } catch {
76
+ return !1;
77
+ }
78
+ }
79
+ export {
80
+ y as clearDropStash,
81
+ O as hasDropStashMarker,
82
+ D as loadDropStash,
83
+ A as saveDropStash
84
+ };
File without changes
@@ -2,6 +2,7 @@ import { BuildClient, BuildResponse, BuildSite } from './buildClient';
2
2
  import { DropClient } from './client';
3
3
  import { InferredBuildSettings } from './detectBuild';
4
4
  import { DeployProgress, DeployResult, ProcessedFile } from './types';
5
+ import { ZipResult } from './zip';
5
6
  export interface DeployOptions {
6
7
  onProgress?: (e: DeployProgress) => void;
7
8
  }
@@ -35,3 +36,9 @@ export interface BuildDeployOptions extends DeployOptions {
35
36
  * doesn't leave an empty site behind in the user's account.
36
37
  */
37
38
  export declare function deployBuild(client: BuildClient, files: ProcessedFile[], opts?: BuildDeployOptions): Promise<BuildDeployResult>;
39
+ /**
40
+ * Deploy an archive that was already zipped — the resume path for a drop
41
+ * stashed across the signup round-trip (see `buildStash`), where `source.zip`
42
+ * and its synthesised settings were produced before the visitor left.
43
+ */
44
+ export declare function deployZippedBuild(client: BuildClient, { file: zip, buildSettings }: ZipResult, opts?: BuildDeployOptions): Promise<BuildDeployResult>;
@@ -0,0 +1,53 @@
1
+ import { getDigest as w } from "./digest.js";
2
+ import { zipFiles as y } from "./zip.js";
3
+ const r = 5;
4
+ async function f(i, n, p, a, t, e) {
5
+ const l = p.filter((h) => a.has(h.sha));
6
+ let u = 0, c = 0;
7
+ const d = async () => {
8
+ for (; u < l.length; ) {
9
+ const h = l[u++];
10
+ await i.uploadFile(n, h.path, h.content, t), e == null || e(++c);
11
+ }
12
+ };
13
+ await Promise.all(Array.from({ length: Math.min(r, l.length) }, d));
14
+ }
15
+ async function B(i, n, p = {}) {
16
+ if (!n.length) throw new Error("No files to deploy");
17
+ const { onProgress: a } = p;
18
+ a == null || a({ phase: "creating" });
19
+ const t = await i.getToken(), e = await i.createDeploy(w(n), t), l = e.required.length;
20
+ return a == null || a({ phase: "uploading", uploaded: 0, total: l }), await f(
21
+ i,
22
+ e.deploy_id,
23
+ n,
24
+ new Set(e.required),
25
+ t,
26
+ (u) => a == null ? void 0 : a({ phase: "uploading", uploaded: u, total: l })
27
+ ), a == null || a({ phase: "processing" }), await i.waitUntilReady(e, t), a == null || a({ phase: "ready" }), { deploy: e, token: t, url: `https://${e.subdomain}.netlify.app` };
28
+ }
29
+ async function F(i, n, p = {}) {
30
+ var t;
31
+ if (!n.length) throw new Error("No files to deploy");
32
+ (t = p.onProgress) == null || t.call(p, { phase: "zipping" });
33
+ const a = await y(n);
34
+ return o(i, a, p);
35
+ }
36
+ async function o(i, { file: n, buildSettings: p }, a = {}) {
37
+ const { onProgress: t, waitForBuild: e = !1 } = a;
38
+ t == null || t({ phase: "creating" });
39
+ const l = await i.createSite();
40
+ t == null || t({ phase: "uploading" });
41
+ const u = await i.createBuildFromZip(l.id, n);
42
+ return t == null || t({ phase: "building" }), e && await i.waitUntilBuildReady(u), t == null || t({ phase: "ready" }), {
43
+ site: l,
44
+ build: u,
45
+ buildSettings: p,
46
+ url: l.ssl_url || l.url || `https://${l.name}.netlify.app`
47
+ };
48
+ }
49
+ export {
50
+ F as deployBuild,
51
+ B as deployFiles,
52
+ o as deployZippedBuild
53
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -40,7 +40,7 @@ function h(e) {
40
40
  if (e instanceof o)
41
41
  return "To deploy, drop a folder or a .zip file — or a single HTML page.";
42
42
  if (e instanceof s)
43
- return "Please log in to Netlify to deploy a project that needs a build, then try again.";
43
+ return "Please log in to Netlify to deploy this project, then try again.";
44
44
  if (e instanceof u)
45
45
  return "We couldn't create a project for this build. Please try again.";
46
46
  if (e instanceof l)
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -4,8 +4,11 @@ export * from './readFiles';
4
4
  export * from './fileTypes';
5
5
  export * from './detectBuild';
6
6
  export * from './client';
7
+ export * from './authedApi';
7
8
  export * from './authedDropClient';
8
9
  export * from './buildClient';
9
10
  export * from './zip';
11
+ export * from './buildStash';
10
12
  export * from './deploy';
11
13
  export * from './errors';
14
+ export * from './analytics';