@uniweb/kit 0.10.17 → 0.10.18
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/package.json +1 -1
- package/src/utils/submitForm.js +53 -5
package/package.json
CHANGED
package/src/utils/submitForm.js
CHANGED
|
@@ -116,10 +116,14 @@ export async function submitForm({
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
const result = await res.json()
|
|
119
|
+
|
|
120
|
+
// A submission with no attachments is COMPLETE at this point — the create
|
|
121
|
+
// call wrote the whole record. Finalizing anyway would re-assert the state it
|
|
122
|
+
// already has: accepted, and pointless.
|
|
119
123
|
if (entries.length === 0) return result
|
|
120
124
|
|
|
121
|
-
await uploadFiles(entries, result, target, fetchFn)
|
|
122
|
-
return { ...result, filesUploaded: entries.length }
|
|
125
|
+
const report = await uploadFiles(entries, result, target, fetchFn)
|
|
126
|
+
return { ...result, filesUploaded: entries.length, ...report }
|
|
123
127
|
}
|
|
124
128
|
|
|
125
129
|
/**
|
|
@@ -146,8 +150,13 @@ function normalizeFiles(files) {
|
|
|
146
150
|
* headers, then `{target}/finalize`. This is the shape the endpoint
|
|
147
151
|
* documents, and it is the default rather than a fallback.
|
|
148
152
|
*
|
|
149
|
-
* `X-Slot` is the index into the manifest sent in phase one
|
|
150
|
-
* entries and the slots are built from one list in one order
|
|
153
|
+
* `X-Slot` is the **0-based index into the manifest sent in phase one** — which
|
|
154
|
+
* is why the entries and the slots are built from one list in one order, and
|
|
155
|
+
* why nothing here reorders them. An endpoint bounds it to the declared count
|
|
156
|
+
* and rejects anything outside the range.
|
|
157
|
+
*
|
|
158
|
+
* The filename is NOT sent as a header. It travels in the manifest, which is
|
|
159
|
+
* the contract; an endpoint takes the name from there.
|
|
151
160
|
*
|
|
152
161
|
* **Failures throw, and the message says what did land.** The submission row
|
|
153
162
|
* already exists at this point, so a silent failure here is the same
|
|
@@ -193,10 +202,23 @@ async function uploadFiles(entries, result, target, fetchFn) {
|
|
|
193
202
|
}
|
|
194
203
|
}
|
|
195
204
|
|
|
205
|
+
// The manifest rides the finalize body as well as the create body. An
|
|
206
|
+
// endpoint may or may not trust it — the one we are built against verifies
|
|
207
|
+
// each slot against storage instead, precisely because a client-supplied
|
|
208
|
+
// count is what a quota or an invoice would otherwise derive from. Sending it
|
|
209
|
+
// costs a few bytes and satisfies the stricter reading of the contract, in
|
|
210
|
+
// which `files` is required and its absence is a malformed call.
|
|
211
|
+
const manifest = entries.map(({ file }, slot) => ({
|
|
212
|
+
slot,
|
|
213
|
+
name: file.name,
|
|
214
|
+
size: file.size,
|
|
215
|
+
mime: file.type || 'application/octet-stream',
|
|
216
|
+
}))
|
|
217
|
+
|
|
196
218
|
const done = await fetchFn(`${base}/finalize`, {
|
|
197
219
|
method: 'POST',
|
|
198
220
|
headers: { 'Content-Type': 'application/json' },
|
|
199
|
-
body: JSON.stringify({ submissionId }),
|
|
221
|
+
body: JSON.stringify({ submissionId, files: manifest }),
|
|
200
222
|
})
|
|
201
223
|
if (!done.ok) {
|
|
202
224
|
throw new Error(
|
|
@@ -204,6 +226,32 @@ async function uploadFiles(entries, result, target, fetchFn) {
|
|
|
204
226
|
`were uploaded, but finalizing failed (HTTP ${done.status}).`,
|
|
205
227
|
)
|
|
206
228
|
}
|
|
229
|
+
|
|
230
|
+
// Finalize reports what the endpoint actually found in storage, which is not
|
|
231
|
+
// necessarily what we believe we sent. Checking it is the whole point of
|
|
232
|
+
// reading this body: every upload can return 2xx and one can still be absent,
|
|
233
|
+
// and the alternative to catching it here is a support ticket about an
|
|
234
|
+
// attachment nobody received.
|
|
235
|
+
//
|
|
236
|
+
// Only acted on when the endpoint reports a number — an endpoint that returns
|
|
237
|
+
// nothing (or something else) is not thereby claiming a loss.
|
|
238
|
+
let report
|
|
239
|
+
try {
|
|
240
|
+
report = await done.json()
|
|
241
|
+
} catch {
|
|
242
|
+
return undefined // not JSON — nothing to verify against
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const recorded = report?.filesRecorded
|
|
246
|
+
if (typeof recorded === 'number' && recorded < entries.length) {
|
|
247
|
+
throw new Error(
|
|
248
|
+
`submitForm: submission ${submissionId} was recorded, but only ${recorded} of ` +
|
|
249
|
+
`${entries.length} attachment(s) reached storage. The endpoint verifies each upload, ` +
|
|
250
|
+
'so the difference did not arrive.',
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return report && typeof report === 'object' ? report : undefined
|
|
207
255
|
}
|
|
208
256
|
|
|
209
257
|
/**
|