@smoothglue/sync-whiteboard 1.0.2 → 1.0.3
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 +15 -7
- package/dist/rooms.js +105 -99
- package/dist/server.js +18 -8
- package/package.json +3 -3
package/dist/assets.js
CHANGED
|
@@ -15,7 +15,6 @@ 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
|
-
// --- End Configuration ---
|
|
19
18
|
/**
|
|
20
19
|
* Stores an asset by proxying a PUT request to the configured asset storage backend.
|
|
21
20
|
* @param id - The unique identifier for the asset (generated by the client).
|
|
@@ -25,7 +24,7 @@ logger_1.default.info({ assetStorageUrl: ASSET_STORAGE_URL }, `[ASSETS] Using As
|
|
|
25
24
|
* @returns The asset ID upon successful storage.
|
|
26
25
|
* @throws Throws an error if the request to the backend fails.
|
|
27
26
|
*/
|
|
28
|
-
async function storeAsset(id, fileStream, contentType = "application/octet-stream", originalFilename,
|
|
27
|
+
async function storeAsset(id, fileStream, contentType = "application/octet-stream", originalFilename, credentials) {
|
|
29
28
|
const url = `${ASSET_STORAGE_URL}/${id}`;
|
|
30
29
|
logger_1.default.debug({ assetId: id, filename: originalFilename, targetUrl: url }, `[ASSETS] Storing asset`);
|
|
31
30
|
// Ensure we have a readable stream
|
|
@@ -46,8 +45,12 @@ async function storeAsset(id, fileStream, contentType = "application/octet-strea
|
|
|
46
45
|
"Content-Disposition": `attachment; filename="${sanitizedFilename}"`, // Use the sanitized filename in the Content-Disposition header to avoid errors
|
|
47
46
|
};
|
|
48
47
|
// forward auth headers if included from client
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
// Prioritize Authorization header, but fall back to Cookie
|
|
49
|
+
if (credentials?.authorization) {
|
|
50
|
+
headers["Authorization"] = credentials.authorization;
|
|
51
|
+
}
|
|
52
|
+
if (credentials?.cookie) {
|
|
53
|
+
headers["Cookie"] = credentials.cookie;
|
|
51
54
|
}
|
|
52
55
|
// Make the PUT request to the asset storage backend
|
|
53
56
|
const response = await fetch(url, {
|
|
@@ -104,12 +107,17 @@ async function storeAsset(id, fileStream, contentType = "application/octet-strea
|
|
|
104
107
|
* @returns An object containing the asset's data as a Readable stream and its Content-Type.
|
|
105
108
|
* @throws Throws an error if the request to the backend fails or the asset is not found.
|
|
106
109
|
*/
|
|
107
|
-
async function loadAsset(id,
|
|
110
|
+
async function loadAsset(id, credentials) {
|
|
108
111
|
const url = `${ASSET_STORAGE_URL}/${id}`;
|
|
109
112
|
logger_1.default.debug({ assetId: id, targetUrl: url }, `[ASSETS] Loading asset`);
|
|
110
113
|
const headers = {};
|
|
111
|
-
|
|
112
|
-
|
|
114
|
+
// Pass auth headers from client to backend call
|
|
115
|
+
// Prioritize Authorization header, but fall back to Cookie
|
|
116
|
+
if (credentials?.authorization) {
|
|
117
|
+
headers["Authorization"] = credentials.authorization;
|
|
118
|
+
}
|
|
119
|
+
else if (credentials?.cookie) {
|
|
120
|
+
headers["Cookie"] = credentials.cookie;
|
|
113
121
|
}
|
|
114
122
|
try {
|
|
115
123
|
// Make the GET request to the actual asset storage backend
|
package/dist/rooms.js
CHANGED
|
@@ -4,6 +4,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.getOrCreateRoom = getOrCreateRoom;
|
|
7
|
+
/**
|
|
8
|
+
* @file Manages tldraw room instances, including creation, persistence, and lifecycle.
|
|
9
|
+
* This module handles loading snapshots from a backend, saving them periodically,
|
|
10
|
+
* and cleaning up inactive rooms from memory.
|
|
11
|
+
*/
|
|
7
12
|
const sync_core_1 = require("@tldraw/sync-core");
|
|
8
13
|
const schema_1 = require("./schema");
|
|
9
14
|
const logger_1 = __importDefault(require("./logger"));
|
|
@@ -27,16 +32,17 @@ let createRoomMutex = Promise.resolve(undefined);
|
|
|
27
32
|
* @returns The snapshot data, or undefined if the backend returns 404.
|
|
28
33
|
* @throws Throws an error for non-404 HTTP errors or network issues.
|
|
29
34
|
*/
|
|
30
|
-
async function readSnapshotFromBackend(roomId,
|
|
35
|
+
async function readSnapshotFromBackend(roomId, credentials) {
|
|
31
36
|
const url = `${SNAPSHOT_STORAGE_URL}/${roomId}`;
|
|
32
37
|
logger_1.default.debug({ roomId, url }, `[ROOMS] Loading snapshot for room ${roomId} from ${url}`);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
const headers = new Headers();
|
|
39
|
+
headers.append("Accept", "application/json");
|
|
40
|
+
// Prioritize Authorization header, but fall back to Cookie
|
|
41
|
+
if (credentials?.authorization) {
|
|
42
|
+
headers.append("Authorization", credentials.authorization);
|
|
43
|
+
}
|
|
44
|
+
else if (credentials?.cookie) {
|
|
45
|
+
headers.append("Cookie", credentials.cookie);
|
|
40
46
|
}
|
|
41
47
|
try {
|
|
42
48
|
const response = await fetch(url, {
|
|
@@ -45,6 +51,13 @@ async function readSnapshotFromBackend(roomId, authHeader) {
|
|
|
45
51
|
});
|
|
46
52
|
if (response.ok) {
|
|
47
53
|
const snapshot = await response.json();
|
|
54
|
+
// Validate the snapshot structure before returning
|
|
55
|
+
if (snapshot &&
|
|
56
|
+
typeof snapshot === "object" &&
|
|
57
|
+
!("documents" in snapshot)) {
|
|
58
|
+
logger_1.default.warn({ roomId, snapshotReceived: snapshot }, `[ROOMS] Invalid snapshot received from backend for room ${roomId}. Missing 'documents' property.`);
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
48
61
|
logger_1.default.debug({ roomId, snapshotSize: JSON.stringify(snapshot).length }, `[ROOMS] Snapshot loaded successfully for room ${roomId}`);
|
|
49
62
|
return snapshot;
|
|
50
63
|
}
|
|
@@ -55,15 +68,13 @@ async function readSnapshotFromBackend(roomId, authHeader) {
|
|
|
55
68
|
else {
|
|
56
69
|
// Handle unexpected errors from the backend
|
|
57
70
|
const errorBody = await response.text();
|
|
58
|
-
const err = new Error(
|
|
59
|
-
`Backend failed to load snapshot for ${roomId}. Status: ${response.status}. Body: ${errorBody}. Headers: ${headers}`);
|
|
71
|
+
const err = new Error(`Backend failed to load snapshot for ${roomId}. Status: ${response.status}. Body: ${errorBody}.`);
|
|
60
72
|
logger_1.default.error({
|
|
61
73
|
err,
|
|
62
74
|
roomId,
|
|
63
75
|
url,
|
|
64
76
|
responseStatus: response.status,
|
|
65
77
|
responseBody: errorBody,
|
|
66
|
-
responseHeader: headers,
|
|
67
78
|
}, `[ROOMS] Error loading snapshot for room ${roomId}`);
|
|
68
79
|
throw err;
|
|
69
80
|
}
|
|
@@ -78,16 +89,17 @@ async function readSnapshotFromBackend(roomId, authHeader) {
|
|
|
78
89
|
* @param roomId - The ID of the room.
|
|
79
90
|
* @param room - The TLSocketRoom instance containing the state to save.
|
|
80
91
|
*/
|
|
81
|
-
async function saveSnapshotToBackend(roomId, room,
|
|
92
|
+
async function saveSnapshotToBackend(roomId, room, credentials) {
|
|
82
93
|
const url = `${SNAPSHOT_STORAGE_URL}/${roomId}`;
|
|
83
94
|
const snapshot = room.getCurrentSnapshot();
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
95
|
+
const headers = new Headers();
|
|
96
|
+
headers.append("Content-Type", "application/json");
|
|
97
|
+
// Prioritize Authorization header, but fall back to Cookie
|
|
98
|
+
if (credentials?.authorization) {
|
|
99
|
+
headers.append("Authorization", credentials.authorization);
|
|
100
|
+
}
|
|
101
|
+
else if (credentials?.cookie) {
|
|
102
|
+
headers.append("Cookie", credentials.cookie);
|
|
91
103
|
}
|
|
92
104
|
logger_1.default.debug({ roomId, url, snapshotSize: JSON.stringify(snapshot).length }, `[ROOMS] Saving snapshot for room ${roomId} to ${url}`);
|
|
93
105
|
try {
|
|
@@ -103,10 +115,7 @@ async function saveSnapshotToBackend(roomId, room, authHeader) {
|
|
|
103
115
|
url,
|
|
104
116
|
responseStatus: response.status,
|
|
105
117
|
responseBody: errorBody,
|
|
106
|
-
|
|
107
|
-
}, // No err: new Error() here, just context
|
|
108
|
-
`[ROOMS] Error saving snapshot for room ${roomId}: ${response.status} ${response.statusText}`);
|
|
109
|
-
// Log error but don't throw, to avoid breaking the save interval
|
|
118
|
+
}, `[ROOMS] Error saving snapshot for room ${roomId}: ${response.status} ${response.statusText}`);
|
|
110
119
|
}
|
|
111
120
|
else {
|
|
112
121
|
logger_1.default.debug({ roomId }, `[ROOMS] Snapshot saved successfully for room ${roomId}`);
|
|
@@ -114,7 +123,6 @@ async function saveSnapshotToBackend(roomId, room, authHeader) {
|
|
|
114
123
|
}
|
|
115
124
|
catch (error) {
|
|
116
125
|
logger_1.default.error({ err: error, roomId, url }, `[ROOMS] Network or fetch error saving snapshot for room ${roomId}`);
|
|
117
|
-
// Log error but don't throw
|
|
118
126
|
}
|
|
119
127
|
}
|
|
120
128
|
/**
|
|
@@ -125,25 +133,21 @@ async function saveSnapshotToBackend(roomId, room, authHeader) {
|
|
|
125
133
|
* @returns A promise resolving to the TLSocketRoom instance.
|
|
126
134
|
* @throws Throws an error if backend interaction fails during creation.
|
|
127
135
|
*/
|
|
128
|
-
async function getOrCreateRoom(roomId,
|
|
129
|
-
// Chain onto the mutex promise to ensure sequential access to the `rooms` map
|
|
136
|
+
async function getOrCreateRoom(roomId, credentials) {
|
|
130
137
|
createRoomMutex = createRoomMutex.then(async () => {
|
|
131
|
-
// Check if an active room instance already exists
|
|
132
138
|
if (rooms.has(roomId)) {
|
|
133
139
|
const existingRoomState = rooms.get(roomId);
|
|
134
140
|
if (!existingRoomState.room.isClosed()) {
|
|
135
141
|
logger_1.default.debug({ roomId }, "[ROOMS] Active room instance found in memory.");
|
|
136
|
-
return;
|
|
142
|
+
return;
|
|
137
143
|
}
|
|
138
144
|
else {
|
|
139
145
|
logger_1.default.info({ roomId }, `[ROOMS] Found closed room ${roomId}, removing before creating new one.`);
|
|
140
|
-
rooms.delete(roomId);
|
|
146
|
+
rooms.delete(roomId);
|
|
141
147
|
}
|
|
142
148
|
}
|
|
143
149
|
logger_1.default.info({ roomId }, `[ROOMS] Creating or recreating room: ${roomId}`);
|
|
144
|
-
|
|
145
|
-
const initialSnapshot = await readSnapshotFromBackend(roomId);
|
|
146
|
-
// Define child logger for the tldraw room instance
|
|
150
|
+
const initialSnapshot = await readSnapshotFromBackend(roomId, credentials);
|
|
147
151
|
const tldrawInstanceLogger = logger_1.default.child({
|
|
148
152
|
tldrawRoomId: roomId,
|
|
149
153
|
component: "tldraw-sync-core",
|
|
@@ -173,86 +177,88 @@ async function getOrCreateRoom(roomId, authHeader) {
|
|
|
173
177
|
}
|
|
174
178
|
},
|
|
175
179
|
};
|
|
176
|
-
// Create the new room state object
|
|
177
180
|
const newRoomState = {
|
|
178
181
|
id: roomId,
|
|
179
182
|
needsPersist: false,
|
|
180
183
|
persistPromise: null,
|
|
181
|
-
|
|
182
|
-
room:
|
|
183
|
-
schema: schema_1.whiteboardSchema, // Our defined tldraw schema
|
|
184
|
-
initialSnapshot, // Initial state from backend (or undefined)
|
|
185
|
-
log: tldrawLogAdapter, // Logger for internal tldraw messages
|
|
186
|
-
/** Callback when a user session is removed (e.g., disconnects/times out) */
|
|
187
|
-
onSessionRemoved(roomInstance, args) {
|
|
188
|
-
logger_1.default.debug({ roomId, remainingSessions: args.numSessionsRemaining }, `[ROOMS] Session removed from room ${roomId}. Remaining: ${args.numSessionsRemaining}`);
|
|
189
|
-
// If last user leaves, trigger a final save and close the room
|
|
190
|
-
if (args.numSessionsRemaining === 0) {
|
|
191
|
-
logger_1.default.info({ roomId }, `[ROOMS] Last user left room ${roomId}. Triggering final save.`);
|
|
192
|
-
// Ensure any pending periodic save completes first
|
|
193
|
-
const savePromise = newRoomState.persistPromise ?? Promise.resolve();
|
|
194
|
-
savePromise.finally(() => {
|
|
195
|
-
logger_1.default.info({ roomId }, `[ROOMS] Performing final save for room ${roomId}...`);
|
|
196
|
-
saveSnapshotToBackend(roomId, roomInstance).finally(() => {
|
|
197
|
-
logger_1.default.info({ roomId }, `[ROOMS] Closing room ${roomId} after final save.`);
|
|
198
|
-
roomInstance.close(); // Mark the tldraw room as closed
|
|
199
|
-
});
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
},
|
|
203
|
-
/** Callback when data within the room changes */
|
|
204
|
-
onDataChange() {
|
|
205
|
-
// Flag that the room needs to be saved on the next interval
|
|
206
|
-
newRoomState.needsPersist = true;
|
|
207
|
-
},
|
|
208
|
-
}),
|
|
184
|
+
credentials,
|
|
185
|
+
room: null,
|
|
209
186
|
};
|
|
210
|
-
|
|
187
|
+
const roomInstance = new sync_core_1.TLSocketRoom({
|
|
188
|
+
schema: schema_1.whiteboardSchema,
|
|
189
|
+
initialSnapshot,
|
|
190
|
+
log: tldrawLogAdapter,
|
|
191
|
+
onSessionRemoved(roomInstance, args) {
|
|
192
|
+
logger_1.default.debug({ roomId, remainingSessions: args.numSessionsRemaining }, `[ROOMS] Session removed. Remaining: ${args.numSessionsRemaining}`);
|
|
193
|
+
if (args.numSessionsRemaining === 0) {
|
|
194
|
+
logger_1.default.info({ roomId }, `[ROOMS] Last user left. Triggering final save.`);
|
|
195
|
+
const savePromise = newRoomState.persistPromise ?? Promise.resolve();
|
|
196
|
+
savePromise.finally(() => {
|
|
197
|
+
logger_1.default.info({ roomId }, `[ROOMS] Performing final save...`);
|
|
198
|
+
saveSnapshotToBackend(roomId, roomInstance, newRoomState.credentials).finally(() => {
|
|
199
|
+
logger_1.default.info({ roomId }, `[ROOMS] Closing room after final save.`);
|
|
200
|
+
roomInstance.close();
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
onDataChange() {
|
|
206
|
+
newRoomState.needsPersist = true;
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
newRoomState.room = roomInstance;
|
|
211
210
|
rooms.set(roomId, newRoomState);
|
|
212
|
-
logger_1.default.info({ roomId }, `[ROOMS] Room
|
|
211
|
+
logger_1.default.info({ roomId }, `[ROOMS] Room created successfully.`);
|
|
212
|
+
if (rooms.size > 0) {
|
|
213
|
+
startPersistenceInterval();
|
|
214
|
+
}
|
|
213
215
|
});
|
|
214
|
-
// Wait for the mutex-protected operation (lookup/creation) to complete
|
|
215
216
|
await createRoomMutex;
|
|
216
|
-
// Retrieve the room state (should always exist after the mutex)
|
|
217
217
|
const roomState = rooms.get(roomId);
|
|
218
218
|
if (!roomState || roomState.room.isClosed()) {
|
|
219
|
-
|
|
220
|
-
logger_1.default.error({ roomId }, `[ROOMS] Failed to get or create a valid room instance for ${roomId} after mutex.`);
|
|
219
|
+
logger_1.default.error({ roomId }, `[ROOMS] Failed to get or create a valid room instance after mutex.`);
|
|
221
220
|
throw new Error(`Failed to retrieve valid room instance for ${roomId}`);
|
|
222
221
|
}
|
|
223
|
-
// Return the tldraw room object
|
|
224
222
|
return roomState.room;
|
|
225
223
|
}
|
|
226
|
-
// --- Periodic Persistence ---
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
224
|
+
// --- Smart Periodic Persistence ---
|
|
225
|
+
let persistenceInterval = null;
|
|
226
|
+
function stopPersistenceInterval() {
|
|
227
|
+
if (persistenceInterval) {
|
|
228
|
+
logger_1.default.info("[ROOMS] No active rooms. Stopping periodic persistence.");
|
|
229
|
+
clearInterval(persistenceInterval);
|
|
230
|
+
persistenceInterval = null;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function startPersistenceInterval() {
|
|
234
|
+
if (persistenceInterval)
|
|
235
|
+
return;
|
|
236
|
+
logger_1.default.info("[ROOMS] First active room created. Starting periodic persistence.");
|
|
237
|
+
persistenceInterval = setInterval(() => {
|
|
238
|
+
logger_1.default.debug("[ROOMS] Periodic persistence check initiated.");
|
|
239
|
+
let updatedRoomCount = 0;
|
|
240
|
+
for (const roomState of rooms.values()) {
|
|
241
|
+
if (roomState.room.isClosed()) {
|
|
242
|
+
logger_1.default.info({ roomId: roomState.id }, `[ROOMS] Removing closed room during periodic check.`);
|
|
243
|
+
rooms.delete(roomState.id);
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
if (roomState.needsPersist && !roomState.persistPromise) {
|
|
247
|
+
roomState.needsPersist = false;
|
|
248
|
+
updatedRoomCount++;
|
|
249
|
+
roomState.persistPromise = saveSnapshotToBackend(roomState.id, roomState.room, roomState.credentials)
|
|
250
|
+
.catch((error) => {
|
|
251
|
+
logger_1.default.error({ err: error, roomId: roomState.id }, `[ROOMS] Periodic save failed.`);
|
|
252
|
+
})
|
|
253
|
+
.finally(() => {
|
|
254
|
+
roomState.persistPromise = null;
|
|
255
|
+
logger_1.default.debug({ roomId: roomState.id }, "[ROOMS] Persistence promise cleared.");
|
|
256
|
+
});
|
|
257
|
+
}
|
|
237
258
|
}
|
|
238
|
-
|
|
239
|
-
if (
|
|
240
|
-
|
|
241
|
-
updatedRoomCount++;
|
|
242
|
-
// Track the save operation promise
|
|
243
|
-
roomState.persistPromise = saveSnapshotToBackend(roomState.id, roomState.room, roomState.authHeader)
|
|
244
|
-
.catch((error) => {
|
|
245
|
-
// Log errors from periodic save but don't stop the interval
|
|
246
|
-
logger_1.default.error({ err: error, roomId: roomState.id }, // Pass error object
|
|
247
|
-
`[ROOMS] Periodic save failed for room ${roomState.id}`);
|
|
248
|
-
})
|
|
249
|
-
.finally(() => {
|
|
250
|
-
// Clear the promise tracker when done
|
|
251
|
-
roomState.persistPromise = null;
|
|
252
|
-
logger_1.default.debug({ roomId: roomState.id }, "[ROOMS] Persistence promise cleared.");
|
|
253
|
-
});
|
|
259
|
+
logger_1.default.debug({ roomsChecked: rooms.size, roomsUpdatedThisInterval: updatedRoomCount }, "[ROOMS] Periodic persistence check completed.");
|
|
260
|
+
if (rooms.size === 0) {
|
|
261
|
+
stopPersistenceInterval();
|
|
254
262
|
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
}, SAVE_INTERVAL_MS);
|
|
258
|
-
// --- End Periodic Persistence ---
|
|
263
|
+
}, SAVE_INTERVAL_MS);
|
|
264
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -48,13 +48,17 @@ app.register(async (svc) => {
|
|
|
48
48
|
const sessionId = req.query?.sessionId;
|
|
49
49
|
// Client provides sessionId via query param, handled by TLSocketRoom
|
|
50
50
|
// Capture the Authorization header from the incoming request
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
// Capture both potential auth headers
|
|
52
|
+
const credentials = {
|
|
53
|
+
authorization: req.headers.authorization,
|
|
54
|
+
cookie: req.headers.cookie,
|
|
55
|
+
};
|
|
56
|
+
if (!credentials) {
|
|
53
57
|
req.log.warn({ roomId }, `[SERVER] Connection attempt without Authorization header.`);
|
|
54
58
|
}
|
|
55
59
|
try {
|
|
56
60
|
// Get or create the room instance (loads/creates state)
|
|
57
|
-
const room = await (0, rooms_1.getOrCreateRoom)(roomId,
|
|
61
|
+
const room = await (0, rooms_1.getOrCreateRoom)(roomId, credentials);
|
|
58
62
|
req.log.debug(`[SERVER] Handling WebSocket connection for room ${roomId}`);
|
|
59
63
|
room.handleSocketConnect({ sessionId, socket });
|
|
60
64
|
}
|
|
@@ -73,7 +77,10 @@ app.register(async (svc) => {
|
|
|
73
77
|
svc.put("/assets/:id", async (req, reply) => {
|
|
74
78
|
const { id } = req.params;
|
|
75
79
|
const contentType = req.headers["content-type"] || "application/octet-stream";
|
|
76
|
-
const
|
|
80
|
+
const credentials = {
|
|
81
|
+
authorization: req.headers.authorization,
|
|
82
|
+
cookie: req.headers.cookie,
|
|
83
|
+
};
|
|
77
84
|
// Extract original filename from custom header
|
|
78
85
|
const originalFilenameHeaderRaw = req.headers["x-original-filename"];
|
|
79
86
|
const originalFilenameHeader = Array.isArray(originalFilenameHeaderRaw)
|
|
@@ -108,7 +115,7 @@ app.register(async (svc) => {
|
|
|
108
115
|
}
|
|
109
116
|
try {
|
|
110
117
|
// Call the asset storage logic (which proxies to the backend)
|
|
111
|
-
await (0, assets_1.storeAsset)(id, req.raw, contentType, originalFilename,
|
|
118
|
+
await (0, assets_1.storeAsset)(id, req.raw, contentType, originalFilename, credentials);
|
|
112
119
|
req.log.debug({ assetId: id }, `[SERVER] Asset stored successfully.`);
|
|
113
120
|
reply.code(200).send({ success: true });
|
|
114
121
|
}
|
|
@@ -128,11 +135,14 @@ app.register(async (svc) => {
|
|
|
128
135
|
app.get("/assets/:id", async (req, reply) => {
|
|
129
136
|
const { id } = req.params;
|
|
130
137
|
req.log.debug({ assetId: id }, `[SERVER] GET /assets/:id`);
|
|
131
|
-
// Capture the
|
|
132
|
-
const
|
|
138
|
+
// Capture the credentials if included from client
|
|
139
|
+
const credentials = {
|
|
140
|
+
authorization: req.headers.authorization,
|
|
141
|
+
cookie: req.headers.cookie,
|
|
142
|
+
};
|
|
133
143
|
try {
|
|
134
144
|
// Call the asset loading logic (which proxies to the backend)
|
|
135
|
-
const { stream: dataStream, contentType } = await (0, assets_1.loadAsset)(id,
|
|
145
|
+
const { stream: dataStream, contentType } = await (0, assets_1.loadAsset)(id, credentials);
|
|
136
146
|
req.log.debug({ assetId: id, contentType: contentType }, `[SERVER] Asset loaded. Sending reply...`);
|
|
137
147
|
// Set the correct Content-Type header and send the stream
|
|
138
148
|
reply.header("Content-Type", contentType);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smoothglue/sync-whiteboard",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"main": "dist/server.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "ts-node-dev --respawn --transpile-only src/server.ts",
|
|
@@ -19,10 +19,10 @@
|
|
|
19
19
|
"README.md",
|
|
20
20
|
"LICENSE"
|
|
21
21
|
],
|
|
22
|
-
"homepage": "https://
|
|
22
|
+
"homepage": "https://code.build.smoothglue.io/braingu/smoothglue/frontend/sync-whiteboard/blob/main/README.md",
|
|
23
23
|
"repository": {
|
|
24
24
|
"type": "git",
|
|
25
|
-
"url": "https://
|
|
25
|
+
"url": "https://code.build.smoothglue.io/braingu/smoothglue/frontend/sync-whiteboard.git"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^22.14.1",
|