@smoothglue/sync-whiteboard 1.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets.js +33 -35
- package/package.json +11 -8
package/dist/assets.js
CHANGED
|
@@ -15,8 +15,26 @@ if (!ASSET_STORAGE_URL) {
|
|
|
15
15
|
process.exit(1);
|
|
16
16
|
}
|
|
17
17
|
logger_1.default.info({ assetStorageUrl: ASSET_STORAGE_URL }, `[ASSETS] Using Asset Storage URL`);
|
|
18
|
+
/**
|
|
19
|
+
* Buffers a Readable stream into an ArrayBuffer.
|
|
20
|
+
* @param stream The Node.js Readable stream.
|
|
21
|
+
* @returns A promise that resolves with an ArrayBuffer.
|
|
22
|
+
*/
|
|
23
|
+
async function streamToArrayBuffer(stream) {
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
const chunks = [];
|
|
26
|
+
stream.on("data", (chunk) => chunks.push(chunk));
|
|
27
|
+
stream.on("end", () => {
|
|
28
|
+
const buffer = Buffer.concat(chunks);
|
|
29
|
+
// Convert the final Node.js Buffer to a standard ArrayBuffer
|
|
30
|
+
resolve(buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength));
|
|
31
|
+
});
|
|
32
|
+
stream.on("error", reject);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
18
35
|
/**
|
|
19
36
|
* Stores an asset by proxying a PUT request to the configured asset storage backend.
|
|
37
|
+
* Wraps the file in a multipart/form-data request.
|
|
20
38
|
* @param id - The unique identifier for the asset (generated by the client).
|
|
21
39
|
* @param fileStream - The readable stream containing the asset data (from the client request).
|
|
22
40
|
* @param contentType - The MIME type of the asset.
|
|
@@ -32,39 +50,35 @@ async function storeAsset(id, fileStream, contentType = "application/octet-strea
|
|
|
32
50
|
logger_1.default.error({ assetId: id, receivedType: typeof fileStream }, "[ASSETS] Error: storeAsset received a non-readable stream type.");
|
|
33
51
|
throw new Error("Invalid stream type provided to storeAsset.");
|
|
34
52
|
}
|
|
35
|
-
let webStream = null;
|
|
36
53
|
try {
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
//
|
|
41
|
-
const
|
|
54
|
+
// Buffer the stream into memory to create a Blob.
|
|
55
|
+
const arrayBuffer = await streamToArrayBuffer(fileStream);
|
|
56
|
+
const blob = new Blob([arrayBuffer], { type: contentType });
|
|
57
|
+
// Create a FormData payload. The backend expects the file under the 'file' field name.
|
|
58
|
+
const formData = new FormData();
|
|
59
|
+
formData.append("file", blob, originalFilename);
|
|
60
|
+
// Prepare headers. Do NOT manually set the 'Content-Type' header.
|
|
61
|
+
// The 'fetch' API will automatically set the correct 'multipart/form-data' boundary.
|
|
42
62
|
const headers = {
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"Content-Disposition": `attachment; filename="${sanitizedFilename}"`, // Use the sanitized filename in the Content-Disposition header to avoid errors
|
|
63
|
+
"X-Original-Filename": encodeURIComponent(originalFilename),
|
|
64
|
+
"Content-Disposition": `attachment; filename="${originalFilename.replace(/[^\x00-\x7F]/g, "")}"`,
|
|
46
65
|
};
|
|
47
|
-
// forward auth headers if included from client
|
|
48
|
-
// Prioritize Authorization header, but fall back to Cookie
|
|
49
66
|
if (credentials?.authorization) {
|
|
50
67
|
headers["Authorization"] = credentials.authorization;
|
|
51
68
|
}
|
|
52
69
|
if (credentials?.cookie) {
|
|
53
70
|
headers["Cookie"] = credentials.cookie;
|
|
54
71
|
}
|
|
55
|
-
// Make the
|
|
72
|
+
// Make the request using the FormData as the body.
|
|
56
73
|
const response = await fetch(url, {
|
|
57
74
|
method: "PUT",
|
|
58
75
|
headers: headers,
|
|
59
|
-
body:
|
|
60
|
-
// @ts-ignore - duplex: 'half' is required for streaming request bodies with Node fetch
|
|
61
|
-
duplex: "half",
|
|
76
|
+
body: formData, // Use the FormData object as the body
|
|
62
77
|
});
|
|
63
78
|
logger_1.default.debug({ assetId: id, status: response.status }, `[ASSETS] Backend PUT response status`);
|
|
64
|
-
// Handle backend errors
|
|
65
79
|
if (!response.ok) {
|
|
66
80
|
const errorBody = await response.text();
|
|
67
|
-
const err = new Error(`Backend failed to store asset ${id}. Status: ${response.status}. Body: ${errorBody}. Headers ${headers}`);
|
|
81
|
+
const err = new Error(`Backend failed to store asset ${id}. Status: ${response.status}. Body: ${errorBody}. Headers ${JSON.stringify(headers)}`);
|
|
68
82
|
logger_1.default.error({
|
|
69
83
|
err,
|
|
70
84
|
assetId: id,
|
|
@@ -72,33 +86,17 @@ async function storeAsset(id, fileStream, contentType = "application/octet-strea
|
|
|
72
86
|
responseStatusText: response.statusText,
|
|
73
87
|
responseBody: errorBody,
|
|
74
88
|
}, `[ASSETS] Error response from backend storing asset`);
|
|
75
|
-
// Ensure streams are closed on error
|
|
76
|
-
if (webStream) {
|
|
77
|
-
await webStream.cancel().catch((cancelErr) => logger_1.default.error({
|
|
78
|
-
err: cancelErr,
|
|
79
|
-
assetId: id,
|
|
80
|
-
stage: "cancel_upload_after_failed_fetch",
|
|
81
|
-
}, `[ASSETS] Error cancelling upload webStream`));
|
|
82
|
-
}
|
|
83
89
|
throw err;
|
|
84
90
|
}
|
|
85
91
|
logger_1.default.debug({ assetId: id, targetUrl: url }, `[ASSETS] Successfully proxied storage for asset`);
|
|
86
|
-
return id;
|
|
92
|
+
return id;
|
|
87
93
|
}
|
|
88
94
|
catch (error) {
|
|
89
95
|
logger_1.default.error({ err: error, assetId: id, targetUrl: url, operation: "storeAsset" }, `[ASSETS] Network or fetch error storing asset`);
|
|
90
|
-
// Clean up streams on error
|
|
91
96
|
if (fileStream instanceof stream_1.Readable && !fileStream.destroyed) {
|
|
92
97
|
fileStream.destroy(error instanceof Error ? error : new Error(String(error)));
|
|
93
98
|
}
|
|
94
|
-
|
|
95
|
-
await webStream.cancel().catch((cancelErr) => logger_1.default.error({
|
|
96
|
-
err: cancelErr,
|
|
97
|
-
assetId: id,
|
|
98
|
-
stage: "cancel_upload_during_error_handling",
|
|
99
|
-
}, `[ASSETS] Error cancelling upload webStream`));
|
|
100
|
-
}
|
|
101
|
-
throw error; // Re-throw error for the server handler
|
|
99
|
+
throw error;
|
|
102
100
|
}
|
|
103
101
|
}
|
|
104
102
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smoothglue/sync-whiteboard",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"main": "dist/server.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "ts-node-dev --respawn --transpile-only src/server.ts",
|
|
@@ -25,19 +25,22 @@
|
|
|
25
25
|
"url": "https://code.build.smoothglue.io/braingu/smoothglue/frontend/sync-whiteboard.git"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@types/node": "
|
|
29
|
-
"@types/ws": "
|
|
30
|
-
"ts-node": "
|
|
31
|
-
"ts-node-dev": "
|
|
32
|
-
"typescript": "
|
|
28
|
+
"@types/node": "22.18.0",
|
|
29
|
+
"@types/ws": "8.18.1",
|
|
30
|
+
"ts-node": "10.9.2",
|
|
31
|
+
"ts-node-dev": "2.0.0",
|
|
32
|
+
"typescript": "5.9.2"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@fastify/cors": "^11.0.1",
|
|
36
36
|
"@fastify/websocket": "^11.0.2",
|
|
37
37
|
"@tldraw/sync-core": "^3.12.0",
|
|
38
38
|
"@tldraw/tlschema": "^3.12.0",
|
|
39
|
-
"fastify": "^5.
|
|
40
|
-
"pino": "^
|
|
39
|
+
"fastify": "^5.7.4",
|
|
40
|
+
"pino": "^10.3.0",
|
|
41
41
|
"ws": "^8.18.1"
|
|
42
|
+
},
|
|
43
|
+
"overrides": {
|
|
44
|
+
"diff": "^4.0.4"
|
|
42
45
|
}
|
|
43
46
|
}
|