Skip to content

Commit 4f77dfb

Browse files
committed
fix asset upload to s3
1 parent 9a60f8c commit 4f77dfb

File tree

4 files changed

+22
-263
lines changed

4 files changed

+22
-263
lines changed

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
"test": "jest"
1313
},
1414
"dependencies": {
15-
"axios": "^1.8.4",
1615
"crypto-browserify": "^3.12.1",
1716
"form-data": "^4.0.0",
1817
"formdata-node": "^6.0.3",

src/api/resources/assets/client/Client.ts

Lines changed: 0 additions & 210 deletions
Original file line numberDiff line numberDiff line change
@@ -31,28 +31,6 @@ export declare namespace Assets {
3131
}
3232
}
3333

34-
interface TestAssetUploadRequest {
35-
/**
36-
* File to upload via various formats
37-
*/
38-
file: ArrayBuffer | ReadableStream | string;
39-
40-
/**
41-
* The file MIME type
42-
*/
43-
mimeType?: string;
44-
45-
/**
46-
* Name of the file
47-
*/
48-
fileName: string;
49-
50-
/**
51-
* Name of the parent folder to upload to
52-
*/
53-
parentFolder?: string;
54-
}
55-
5634
/**
5735
* Assets are files that are uploaded to your Webflow account.
5836
*/
@@ -176,194 +154,6 @@ export class Assets {
176154
}
177155
}
178156

179-
public async createAndUpload(
180-
siteId: string,
181-
request: TestAssetUploadRequest,
182-
requestOptions?: Assets.RequestOptions
183-
): Promise<Webflow.AssetUpload> {
184-
if (siteId) {
185-
throw new errors.WebflowError({
186-
statusCode: 400,
187-
body: "a test",
188-
});
189-
}
190-
/** 1. Generate the hash */
191-
const getBufferFromUrl = async (url: string): Promise<ArrayBuffer> => {
192-
const response = await fetch(url);
193-
const buffer = await response.arrayBuffer();
194-
console.log("BUF", buffer);
195-
return buffer;
196-
// return crypto.createHash("md5").update(Buffer.from(buffer)).digest("hex");
197-
};
198-
const file = request.file;
199-
let tempBuffer: Buffer | null = null;
200-
if (typeof file === 'string') {
201-
const arrBuffer = await getBufferFromUrl(file);
202-
tempBuffer = Buffer.from(arrBuffer);
203-
} else if (file instanceof ArrayBuffer) {
204-
tempBuffer = Buffer.from(file);
205-
}
206-
if (tempBuffer === null) {
207-
throw new Error('Invalid file');
208-
}
209-
const hash = crypto.createHash("md5").update(Buffer.from(tempBuffer)).digest("hex");
210-
const fileName = request.fileName;
211-
212-
const wfUploadRequest = {
213-
fileName,
214-
fileHash: hash,
215-
};
216-
217-
/** 2. Create the Asset Metadata in Webflow */
218-
const createWfAssetMetadata = async () => {
219-
const _response = await core.fetcher({
220-
url: urlJoin(
221-
(await core.Supplier.get(this._options.environment)) ?? environments.WebflowEnvironment.Default,
222-
`sites/${encodeURIComponent(siteId)}/assets`
223-
),
224-
method: "POST",
225-
headers: {
226-
Authorization: await this._getAuthorizationHeader(),
227-
"X-Fern-Language": "JavaScript",
228-
"X-Fern-SDK-Name": "webflow-api",
229-
"X-Fern-SDK-Version": "3.1.1",
230-
"User-Agent": "webflow-api/3.1.1",
231-
"X-Fern-Runtime": core.RUNTIME.type,
232-
"X-Fern-Runtime-Version": core.RUNTIME.version,
233-
...requestOptions?.headers,
234-
},
235-
contentType: "application/json",
236-
requestType: "json",
237-
body: serializers.AssetsCreateRequest.jsonOrThrow(wfUploadRequest, {
238-
unrecognizedObjectKeys: "passthrough",
239-
allowUnrecognizedUnionMembers: true,
240-
allowUnrecognizedEnumValues: true,
241-
}),
242-
timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000,
243-
maxRetries: requestOptions?.maxRetries,
244-
abortSignal: requestOptions?.abortSignal,
245-
});
246-
if (_response.ok) {
247-
return serializers.AssetUpload.parseOrThrow(_response.body, {
248-
unrecognizedObjectKeys: "passthrough",
249-
allowUnrecognizedUnionMembers: true,
250-
allowUnrecognizedEnumValues: true,
251-
skipValidation: true,
252-
breadcrumbsPrefix: ["response"],
253-
});
254-
}
255-
256-
if (_response.error.reason === "status-code") {
257-
switch (_response.error.statusCode) {
258-
case 400:
259-
throw new Webflow.BadRequestError(_response.error.body);
260-
case 401:
261-
throw new Webflow.UnauthorizedError(
262-
serializers.Error_.parseOrThrow(_response.error.body, {
263-
unrecognizedObjectKeys: "passthrough",
264-
allowUnrecognizedUnionMembers: true,
265-
allowUnrecognizedEnumValues: true,
266-
skipValidation: true,
267-
breadcrumbsPrefix: ["response"],
268-
})
269-
);
270-
case 404:
271-
throw new Webflow.NotFoundError(
272-
serializers.Error_.parseOrThrow(_response.error.body, {
273-
unrecognizedObjectKeys: "passthrough",
274-
allowUnrecognizedUnionMembers: true,
275-
allowUnrecognizedEnumValues: true,
276-
skipValidation: true,
277-
breadcrumbsPrefix: ["response"],
278-
})
279-
);
280-
case 429:
281-
throw new Webflow.TooManyRequestsError(
282-
serializers.Error_.parseOrThrow(_response.error.body, {
283-
unrecognizedObjectKeys: "passthrough",
284-
allowUnrecognizedUnionMembers: true,
285-
allowUnrecognizedEnumValues: true,
286-
skipValidation: true,
287-
breadcrumbsPrefix: ["response"],
288-
})
289-
);
290-
case 500:
291-
throw new Webflow.InternalServerError(
292-
serializers.Error_.parseOrThrow(_response.error.body, {
293-
unrecognizedObjectKeys: "passthrough",
294-
allowUnrecognizedUnionMembers: true,
295-
allowUnrecognizedEnumValues: true,
296-
skipValidation: true,
297-
breadcrumbsPrefix: ["response"],
298-
})
299-
);
300-
default:
301-
throw new errors.WebflowError({
302-
statusCode: _response.error.statusCode,
303-
body: _response.error.body,
304-
});
305-
}
306-
}
307-
308-
switch (_response.error.reason) {
309-
case "non-json":
310-
throw new errors.WebflowError({
311-
statusCode: _response.error.statusCode,
312-
body: _response.error.rawBody,
313-
});
314-
case "timeout":
315-
throw new errors.WebflowTimeoutError("Timeout exceeded when calling POST /sites/{site_id}/assets.");
316-
case "unknown":
317-
throw new errors.WebflowError({
318-
message: _response.error.errorMessage,
319-
});
320-
}
321-
};
322-
323-
console.log("Step 2 done");
324-
325-
/** 3. Upload to AWS */
326-
const wfUploadedAsset = await createWfAssetMetadata();
327-
328-
console.log("Step 2.5 done");
329-
const wfUploadDetails = wfUploadedAsset.uploadDetails!;
330-
const uploadUrl = wfUploadedAsset.uploadUrl as string;
331-
// Temp workaround since headers from response are being camelCased and we need them to be exact when sending to S3
332-
const headerMappings = {
333-
'xAmzAlgorithm': 'X-Amz-Algorithm',
334-
'xAmzDate': 'X-Amz-Date',
335-
'xAmzCredential': 'X-Amz-Credential',
336-
'xAmzSignature': 'X-Amz-Signature',
337-
'successActionStatus': 'success_action_status',
338-
'contentType': 'Content-Type',
339-
'cacheControl': 'Cache-Control',
340-
};
341-
const transformedUploadHeaders = Object.keys(wfUploadDetails).reduce((acc: Record<string, any>, key) => {
342-
const mappedKey = headerMappings[key as keyof typeof headerMappings] || key;
343-
acc[mappedKey] = wfUploadDetails[key as keyof typeof headerMappings ];
344-
return acc;
345-
}, {});
346-
const formDataToUpload = new FormData();
347-
Object.keys(transformedUploadHeaders).forEach((key) => {
348-
formDataToUpload.append(key, transformedUploadHeaders[key]);
349-
});
350-
console.log("STEP 2.6 done", tempBuffer);
351-
const newBlob = Readable.from(tempBuffer);
352-
console.log("NEW BLOB", newBlob);
353-
formDataToUpload.append("file", newBlob, {
354-
filename: fileName,
355-
contentType: wfUploadedAsset.contentType,
356-
});
357-
console.log("STEP 2.75 done");
358-
const uploadResponse = await fetch(uploadUrl, {
359-
method: 'POST',
360-
body: formDataToUpload,
361-
headers: {...formDataToUpload.getHeaders()},
362-
});
363-
console.log("UPLOAD RESPONSE", JSON.stringify(uploadResponse));
364-
return wfUploadedAsset;
365-
}
366-
367157
/**
368158
* The first step in uploading an asset to a site.
369159
*

src/wrapper/AssetsUtilitiesClient.ts

Lines changed: 22 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,9 @@ import * as Webflow from "../api";
33
import { Assets } from "../api/resources/assets/client/Client";
44
import * as core from "../core";
55
import * as environments from "../environments";
6-
import * as errors from "../errors";
7-
import * as serializers from "../serialization";
86
import crypto from "crypto";
97
import fetch from "node-fetch";
10-
import FormData from 'form-data';
11-
import { Readable } from 'stream';
8+
import FormDataConstructor from 'form-data';
129

1310
export declare namespace AssetsUtilities {
1411
interface Options {
@@ -32,7 +29,7 @@ interface TestAssetUploadRequest {
3229
/**
3330
* File to upload via various formats
3431
*/
35-
file: ArrayBuffer | ReadableStream | string;
32+
file: ArrayBuffer | string;
3633

3734
/**
3835
* The file MIME type
@@ -56,6 +53,12 @@ export class Client extends Assets {
5653
super(_options);
5754
}
5855

56+
private async _getBufferFromUrl(url: string): Promise<ArrayBuffer> {
57+
const response = await fetch(url);
58+
const buffer = await response.arrayBuffer();
59+
return buffer;
60+
}
61+
5962
/**
6063
* Create the Asset metadata in Webflow, and immediately upload it to the S3 bucket on behalf of the user to simplify the 2-step process
6164
*
@@ -69,24 +72,11 @@ export class Client extends Assets {
6972
request: TestAssetUploadRequest,
7073
requestOptions?: Assets.RequestOptions
7174
): Promise<Webflow.AssetUpload> {
72-
// if (siteId) {
73-
// throw new errors.WebflowError({
74-
// statusCode: 400,
75-
// body: "a test",
76-
// });
77-
// }
7875
/** 1. Generate the hash */
79-
const getBufferFromUrl = async (url: string): Promise<ArrayBuffer> => {
80-
const response = await fetch(url);
81-
const buffer = await response.arrayBuffer();
82-
console.log("BUF", buffer);
83-
return buffer;
84-
// return crypto.createHash("md5").update(Buffer.from(buffer)).digest("hex");
85-
};
8676
const file = request.file;
8777
let tempBuffer: Buffer | null = null;
8878
if (typeof file === 'string') {
89-
const arrBuffer = await getBufferFromUrl(file);
79+
const arrBuffer = await this._getBufferFromUrl(file);
9080
tempBuffer = Buffer.from(arrBuffer);
9181
} else if (file instanceof ArrayBuffer) {
9282
tempBuffer = Buffer.from(file);
@@ -107,12 +97,9 @@ export class Client extends Assets {
10797
return await this.create(siteId, wfUploadRequest, requestOptions);
10898
};
10999

110-
console.log("Step 2 done");
111-
112-
/** 3. Upload to AWS */
100+
/** 3. Create FormData with S3 bucket signature */
113101
const wfUploadedAsset = await createWfAssetMetadata();
114102

115-
console.log("Step 2.5 done", wfUploadedAsset);
116103
const wfUploadDetails = wfUploadedAsset.uploadDetails!;
117104
const uploadUrl = wfUploadedAsset.uploadUrl as string;
118105
// Temp workaround since headers from response are being camelCased and we need them to be exact when sending to S3
@@ -130,25 +117,27 @@ export class Client extends Assets {
130117
acc[mappedKey] = wfUploadDetails[key as keyof typeof headerMappings ];
131118
return acc;
132119
}, {});
133-
const formDataToUpload = new FormData();
120+
const formDataToUpload = new FormDataConstructor();
134121
Object.keys(transformedUploadHeaders).forEach((key) => {
135122
formDataToUpload.append(key, transformedUploadHeaders[key]);
136123
});
137-
console.log("STEP 2.6 done");
138-
const newBlob = Readable.from(tempBuffer);
139-
console.log("NEW BLOB");
140-
formDataToUpload.append("file", newBlob, {
124+
125+
if (!Buffer.isBuffer(tempBuffer)) {
126+
throw new Error("Invalid Buffer: Expected a Buffer instance from file");
127+
}
128+
129+
formDataToUpload.append("file", tempBuffer, {
141130
filename: fileName,
142-
contentType: wfUploadedAsset.contentType,
131+
contentType: wfUploadedAsset.contentType || "application/octet-stream",
143132
});
144-
console.log("STEP 2.75 done");
145-
// TOOO: SOMETHING WEIRD HERE.. NOT UPLOADING TO S3 PROPERLY
146-
const uploadResponse = await fetch(uploadUrl, {
133+
134+
/** 4. Upload to S3 */
135+
await fetch(uploadUrl, {
147136
method: 'POST',
148137
body: formDataToUpload,
149138
headers: {...formDataToUpload.getHeaders()},
150139
});
151-
console.log("UPLOAD RESPONSE", JSON.stringify(uploadResponse));
140+
152141
return wfUploadedAsset;
153142
}
154143
}

yarn.lock

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -954,15 +954,6 @@ asynckit@^0.4.0:
954954
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
955955
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
956956

957-
axios@^1.8.4:
958-
version "1.8.4"
959-
resolved "https://registry.yarnpkg.com/axios/-/axios-1.8.4.tgz#78990bb4bc63d2cae072952d374835950a82f447"
960-
integrity sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==
961-
dependencies:
962-
follow-redirects "^1.15.6"
963-
form-data "^4.0.0"
964-
proxy-from-env "^1.1.0"
965-
966957
babel-jest@^29.7.0:
967958
version "29.7.0"
968959
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5"
@@ -1683,11 +1674,6 @@ find-up@^4.0.0, find-up@^4.1.0:
16831674
locate-path "^5.0.0"
16841675
path-exists "^4.0.0"
16851676

1686-
follow-redirects@^1.15.6:
1687-
version "1.15.9"
1688-
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1"
1689-
integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==
1690-
16911677
form-data@^4.0.0:
16921678
version "4.0.2"
16931679
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.2.tgz#35cabbdd30c3ce73deb2c42d3c8d3ed9ca51794c"
@@ -2810,11 +2796,6 @@ prompts@^2.0.1:
28102796
kleur "^3.0.3"
28112797
sisteransi "^1.0.5"
28122798

2813-
proxy-from-env@^1.1.0:
2814-
version "1.1.0"
2815-
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
2816-
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
2817-
28182799
psl@^1.1.33:
28192800
version "1.15.0"
28202801
resolved "https://registry.yarnpkg.com/psl/-/psl-1.15.0.tgz#bdace31896f1d97cec6a79e8224898ce93d974c6"

0 commit comments

Comments
 (0)