@webstudio-is/http-client 0.271.0 → 0.273.0

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/lib/index.js CHANGED
@@ -1,14 +1,56 @@
1
1
  // src/index.ts
2
2
  import { createTRPCUntypedClient, httpBatchLink } from "@trpc/client";
3
+ import { Upload } from "tus-js-client";
3
4
  import {
4
- importProjectBundleResultSchema,
5
- publishedProjectBundleSchema
6
- } from "@webstudio-is/protocol";
7
- import {
8
- getBundleVersion,
9
- isAssetFileDataString,
10
- bundleVersion
5
+ importProjectBundleResult,
6
+ publishedProjectBundle,
7
+ maxProjectBundleSize,
8
+ stagedUploadPath,
9
+ stagedUploadProjectIdHeader
11
10
  } from "@webstudio-is/protocol";
11
+ import { getBundleVersion, bundleVersion } from "@webstudio-is/protocol";
12
+ var maxResponsePreviewLength = 500;
13
+ var getResponsePreview = async (response) => {
14
+ try {
15
+ const text = await response.clone().text();
16
+ return text.replace(/\s+/g, " ").trim().slice(0, maxResponsePreviewLength);
17
+ } catch {
18
+ return "";
19
+ }
20
+ };
21
+ var createApiResponseErrorMessage = async (request, response) => {
22
+ const contentType = response.headers.get("content-type") ?? "unknown";
23
+ const status = `${response.status} ${response.statusText}`.trim();
24
+ const url = typeof request === "string" || request instanceof URL ? request.toString() : request.url;
25
+ const preview = await getResponsePreview(response);
26
+ const hint = response.status === 413 ? "The request may be too large for the API." : void 0;
27
+ return [
28
+ `API returned ${contentType} instead of JSON from ${url}.`,
29
+ `HTTP status: ${status}.`,
30
+ preview === "" ? void 0 : `Response preview: ${preview}`,
31
+ hint
32
+ ].filter(Boolean).join("\n");
33
+ };
34
+ var fetchJsonResponse = async (request, init) => {
35
+ const response = await fetch(request, init);
36
+ const contentType = response.headers.get("content-type");
37
+ if (contentType?.includes("json") !== true) {
38
+ throw new Error(await createApiResponseErrorMessage(request, response));
39
+ }
40
+ return response;
41
+ };
42
+ var createHeaders = (headers) => {
43
+ return new Headers(createHeadersObject(headers));
44
+ };
45
+ var createHeadersObject = (headers) => {
46
+ const result = {};
47
+ for (const [name, value] of Object.entries(headers)) {
48
+ if (value !== void 0) {
49
+ result[name] = value;
50
+ }
51
+ }
52
+ return result;
53
+ };
12
54
  var createTrpcClient = (origin, headers) => {
13
55
  const { sourceOrigin } = parseBuilderUrl(origin);
14
56
  const url = new URL("/trpc", sourceOrigin);
@@ -16,7 +58,8 @@ var createTrpcClient = (origin, headers) => {
16
58
  links: [
17
59
  httpBatchLink({
18
60
  url: url.href,
19
- headers
61
+ headers,
62
+ fetch: fetchJsonResponse
20
63
  })
21
64
  ]
22
65
  });
@@ -25,6 +68,50 @@ var createAuthTrpcClient = (params) => createTrpcClient(params.origin, {
25
68
  ...params.headers,
26
69
  "x-auth-token": params.authToken
27
70
  });
71
+ var stagedUploadChunkSize = 3 * 1024 * 1024;
72
+ var formatMebibytes = (bytes) => `${Math.round(bytes / 1024 / 1024)} MiB`;
73
+ var getAssetUploadUrl = ({
74
+ asset,
75
+ origin,
76
+ projectId
77
+ }) => {
78
+ const { sourceOrigin } = parseBuilderUrl(origin);
79
+ const url = new URL(
80
+ `/rest/assets/${encodeURIComponent(asset.name)}`,
81
+ sourceOrigin
82
+ );
83
+ url.searchParams.set("projectId", projectId);
84
+ url.searchParams.set("type", asset.type);
85
+ if (asset.type === "image") {
86
+ url.searchParams.set("width", String(asset.meta.width));
87
+ url.searchParams.set("height", String(asset.meta.height));
88
+ url.searchParams.set("format", asset.format);
89
+ }
90
+ return url;
91
+ };
92
+ var uploadAsset = async (params) => {
93
+ const { authToken, headers, origin, projectId, upload } = params;
94
+ const response = await fetchJsonResponse(
95
+ getAssetUploadUrl({
96
+ asset: upload.asset,
97
+ origin,
98
+ projectId
99
+ }),
100
+ {
101
+ method: "POST",
102
+ body: upload.data,
103
+ headers: createHeaders({
104
+ ...headers,
105
+ "x-auth-token": authToken,
106
+ "content-type": "application/octet-stream"
107
+ })
108
+ }
109
+ );
110
+ const result = await response.json();
111
+ if (typeof result === "object" && result !== null && "errors" in result && typeof result.errors === "string") {
112
+ throw new Error(result.errors);
113
+ }
114
+ };
28
115
  var loadProjectBundleByBuildId = async (params) => {
29
116
  const headers = "serviceToken" in params ? { Authorization: params.serviceToken } : { "x-auth-token": params.authToken };
30
117
  const data = await createTrpcClient(params.origin, {
@@ -33,7 +120,7 @@ var loadProjectBundleByBuildId = async (params) => {
33
120
  }).query("build.loadProjectBundleByBuildId", {
34
121
  buildId: params.buildId
35
122
  });
36
- return publishedProjectBundleSchema.parse(data);
123
+ return publishedProjectBundle.parse(data);
37
124
  };
38
125
  var loadProjectBundleByProjectId = async (params) => {
39
126
  const data = await createAuthTrpcClient(params).query(
@@ -42,7 +129,7 @@ var loadProjectBundleByProjectId = async (params) => {
42
129
  projectId: params.projectId
43
130
  }
44
131
  );
45
- return publishedProjectBundleSchema.parse(data);
132
+ return publishedProjectBundle.parse(data);
46
133
  };
47
134
  var checkProjectBuildPermission = async (params) => {
48
135
  await createAuthTrpcClient(params).query(
@@ -52,17 +139,59 @@ var checkProjectBuildPermission = async (params) => {
52
139
  }
53
140
  );
54
141
  };
142
+ var getUploadIdFromUrl = (uploadUrl) => {
143
+ if (uploadUrl === null) {
144
+ throw new Error("Project bundle upload did not return an upload URL.");
145
+ }
146
+ const { pathname } = new URL(uploadUrl);
147
+ const uploadId = pathname.split("/").filter(Boolean).at(-1);
148
+ if (uploadId === void 0) {
149
+ throw new Error("Project bundle upload did not return an upload id.");
150
+ }
151
+ return decodeURIComponent(uploadId);
152
+ };
153
+ var uploadProjectBundleData = async (params) => {
154
+ const { sourceOrigin } = parseBuilderUrl(params.origin);
155
+ const endpoint = new URL(stagedUploadPath, sourceOrigin);
156
+ const data = JSON.stringify(params.data);
157
+ if (new TextEncoder().encode(data).byteLength > maxProjectBundleSize) {
158
+ throw new Error(
159
+ `Project bundle is too large to import. Maximum size is ${formatMebibytes(maxProjectBundleSize)}.`
160
+ );
161
+ }
162
+ const file = typeof Buffer === "undefined" ? new Blob([data], { type: "application/json" }) : Buffer.from(data);
163
+ return await new Promise((resolve, reject) => {
164
+ const upload = new Upload(file, {
165
+ endpoint: endpoint.href,
166
+ chunkSize: stagedUploadChunkSize,
167
+ retryDelays: [0, 1e3],
168
+ removeFingerprintOnSuccess: true,
169
+ storeFingerprintForResuming: false,
170
+ headers: createHeadersObject({
171
+ ...params.headers,
172
+ "x-auth-token": params.authToken,
173
+ [stagedUploadProjectIdHeader]: params.projectId
174
+ }),
175
+ metadata: {
176
+ projectId: params.projectId
177
+ },
178
+ onError: reject,
179
+ onSuccess: () => resolve(getUploadIdFromUrl(upload.url))
180
+ });
181
+ upload.start();
182
+ });
183
+ };
55
184
  var importProjectBundle = async (params) => {
185
+ const uploadId = await uploadProjectBundleData(params);
56
186
  const result = await createAuthTrpcClient(params).mutation(
57
187
  "build.importProjectBundle",
58
188
  {
59
189
  projectId: params.projectId,
60
- data: params.data,
61
- assetFiles: params.assetFiles,
190
+ uploadId,
62
191
  ignoreVersionCheck: params.ignoreVersionCheck
63
192
  }
64
193
  );
65
- return importProjectBundleResultSchema.parse(result);
194
+ return importProjectBundleResult.parse(result);
66
195
  };
67
196
  var buildProjectDomainPrefix = "p-";
68
197
  var parseBuilderUrl = (urlStr) => {
@@ -99,8 +228,8 @@ export {
99
228
  checkProjectBuildPermission,
100
229
  getBundleVersion,
101
230
  importProjectBundle,
102
- isAssetFileDataString,
103
231
  loadProjectBundleByBuildId,
104
232
  loadProjectBundleByProjectId,
105
- parseBuilderUrl
233
+ parseBuilderUrl,
234
+ uploadAsset
106
235
  };
@@ -1,35 +1,36 @@
1
- import { type AssetFileData, type ImportProjectBundleResult, type PublishedProjectBundle } from "@webstudio-is/protocol";
2
- export { getBundleVersion, isAssetFileDataString, bundleVersion, } from "@webstudio-is/protocol";
3
- export type { AssetFileData, PublishedProjectBundle, ProjectBundle, } from "@webstudio-is/protocol";
1
+ import { type ImportProjectBundleResult, type PublishedProjectBundle } from "@webstudio-is/protocol";
2
+ export { getBundleVersion, bundleVersion } from "@webstudio-is/protocol";
3
+ export type { PublishedProjectBundle, ProjectBundle, } from "@webstudio-is/protocol";
4
+ type RequestHeaders = Record<string, string | undefined>;
5
+ type AuthProjectParams = {
6
+ projectId: string;
7
+ origin: string;
8
+ authToken: string;
9
+ headers?: RequestHeaders;
10
+ };
11
+ type Asset = PublishedProjectBundle["assets"][number];
12
+ type BinaryAssetData = Blob | ArrayBuffer | ArrayBufferView<ArrayBuffer>;
13
+ type AssetUpload = {
14
+ asset: Asset;
15
+ data: BinaryAssetData;
16
+ };
17
+ export declare const uploadAsset: (params: AuthProjectParams & {
18
+ upload: AssetUpload;
19
+ }) => Promise<void>;
4
20
  export declare const loadProjectBundleByBuildId: (params: {
5
21
  buildId: string;
6
22
  origin: string;
7
- headers?: Record<string, string | undefined>;
23
+ headers?: RequestHeaders;
8
24
  } & ({
9
25
  serviceToken: string;
10
26
  } | {
11
27
  authToken: string;
12
28
  })) => Promise<PublishedProjectBundle>;
13
- export declare const loadProjectBundleByProjectId: (params: {
14
- projectId: string;
15
- origin: string;
16
- authToken: string;
17
- headers?: Record<string, string | undefined>;
18
- }) => Promise<PublishedProjectBundle>;
19
- export declare const checkProjectBuildPermission: (params: {
20
- projectId: string;
21
- origin: string;
22
- authToken: string;
23
- headers?: Record<string, string | undefined>;
24
- }) => Promise<void>;
25
- export declare const importProjectBundle: (params: {
26
- projectId: string;
27
- origin: string;
28
- authToken: string;
29
+ export declare const loadProjectBundleByProjectId: (params: AuthProjectParams) => Promise<PublishedProjectBundle>;
30
+ export declare const checkProjectBuildPermission: (params: AuthProjectParams) => Promise<void>;
31
+ export declare const importProjectBundle: (params: AuthProjectParams & {
29
32
  data: PublishedProjectBundle;
30
- assetFiles?: AssetFileData[];
31
33
  ignoreVersionCheck?: boolean;
32
- headers?: Record<string, string | undefined>;
33
34
  }) => Promise<ImportProjectBundleResult>;
34
35
  export declare const parseBuilderUrl: (urlStr: string) => {
35
36
  projectId: undefined;
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "@webstudio-is/http-client",
3
- "version": "0.271.0",
3
+ "version": "0.273.0",
4
4
  "description": "Webstudio HTTP Client",
5
5
  "author": "Webstudio <github@webstudio.is>",
6
6
  "homepage": "https://webstudio.is",
7
7
  "type": "module",
8
8
  "dependencies": {
9
9
  "@trpc/client": "^10.45.2",
10
- "@webstudio-is/protocol": "0.271.0"
10
+ "tus-js-client": "^4.3.1",
11
+ "@webstudio-is/protocol": "0.273.0"
11
12
  },
12
13
  "devDependencies": {
13
14
  "vitest": "^3.1.2",