openvibers/openvibe.media Commits

Document Media’s resumable multipart uploads and pin contracts v0.373.0 (#373)

OP OpenVibers committed 4 days ago · bdad7b122b · 5 files, +47 −7

* Document Media's resumable multipart uploads and pin contracts v0.359.0

* Pin contracts v0.373.0, the latest build with media.upload@1

* Name contracts v0.373.0 in the README's pin list

* Merge remote-tracking branch 'origin/main' into feat/upload-kit-1109

# Conflicts:
#	README.md
#	package-lock.json
#	package.json

Changes

ddiff --git a/README.md b/README.md
iindex ffee5ba..e918c4d 100644
---- a/README.md
++++ b/README.md
@@@ -45,7 +45,7 @@ without `DATABASE_URL`, development uses an embedded PGlite database in `data/pg
export and deletion), OpenVibe.Live (`live.lineage.resolve`), OpenVibe.Events (the outbox relay and
the account and revocation subscriptions)
- Backblaze B2 and Cloudflare R2 (S3 API) when configured; `ffmpeg`/`ffprobe` on the host
--- `openvibe-contracts` v0.338.0, `openvibe-sdk` v0.125.0 (tokens, events outbox, usage readings, per-actor limits),
++- `openvibe-contracts` v0.373.0, `openvibe-sdk` v0.125.0 (tokens, events outbox, usage readings, per-actor limits),
`openvibe-shared` v4.173.0, pinned by release tarball
## Capabilities
@@@ -129,7 +129,7 @@ server/
storage.orphans.scan (monthly storage orphan report, service-wide, report only),
storage.move.cleanup (the alert for placement moves whose delete keeps failing, service-wide)
client-ip.js trust proxy = loopback; req.ip is the only client address
--(openvibe-shared v4.173.0, openvibe-contracts v0.338.0, openvibe-sdk v0.125.0: pinned release tarballs, installed by npm)
++(openvibe-shared v4.173.0, openvibe-contracts v0.373.0, openvibe-sdk v0.125.0: pinned release tarballs, installed by npm)
scripts/smoke-test.sh end-to-end smoke test (boots a temp instance)
scripts/reconcile-objects.js / object-invariant.js / no-good-copy-report.js object-model operator tools
scripts/media-jobs.js list jobs, run the size-invariant scan (dry run by default), approve/cancel proposals
ddiff --git a/docs/upload-kit.md b/docs/upload-kit.md
nnew file mode 100644
iindex 0000000..6f34dcd
---- /dev/null
++++ b/docs/upload-kit.md
@@@ -0,0 +1,36 @@
++# Resumable uploads for OpenVibe apps
++
++Media's native object API already accepts resumable multipart uploads. Its contract is
++`media.upload@1`; `/openapi.json` is built from `openvibe-contracts` and lists the create,
++part, status, complete and abort operations. The canonical behavior and limits remain in
++[the object model](object-model.md#multipart-uploads).
++
++1. Create an object with `POST /api/v2/:app/objects` and `multipart: true`, a known
++ `size_bytes`, and `part_size: 8388608` (8 MiB). The caller needs a service or
++ developer-app token with `media.object.upload`. Do not put an app key in browser code.
++2. Save the returned `id` and `upload.multipart.upload_id` before sending bytes. The
++ `part_url_template` carries a short-lived upload token. Send parts 1 through N to
++ the template's URL. Three can run concurrently; failed parts can be retried with
++ backoff. Parts may arrive out of order. Every part except the last is exactly the
++ session's `part_size`.
++3. After a disconnect or reload, fetch
++ `GET /api/v2/:app/objects/:id/multipart/:uploadId`. Its `parts`, `missing`, and
++ `received_bytes` fields tell the app what Media has stored. Compare the original
++ file's size and hashes before resuming. An upload token or a suitable service
++ credential can read the session.
++4. Complete with `POST /api/v2/:app/objects/:id/multipart/:uploadId/complete`.
++ `content_hash` and each part's SHA-256 can be supplied for verification. The
++ answer is a ready object. A repeated completion of an already completed upload
++ returns that same object. To discard an upload, `DELETE` its session URL.
++
++`openvibe-sdk/media-upload` exposes `createUploadClient({ app, tokenClient })`. Its
++`upload(file, { onSession, onProgress })` uses 8 MiB parts and three concurrent
++requests; `resume({ objectId, uploadId }, file, { onProgress })` sends only missing or
++changed parts. The app is responsible for securely storing a file and its session
++reference between page loads. A browser must receive a scoped upload path from its
++own app server; Media's v2 create route does not accept a Network user JWT directly.
++
++Progress in a page is `OVUpload.progress(el)` from the pinned
++`/shared/ov-upload.js` and `/shared/ov-upload.css`. The SDK also exposes
++`ov.ui.progress(el)` as a wrapper. Keep transfer, processing, retry, and done as
++distinct states; a completed transfer can still need server processing.
ddiff --git a/package-lock.json b/package-lock.json
iindex 415ac96..b9d2564 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -16,7 +16,7 @@
"express": "^4.19.2",
"iovalkey": "^0.4.0",
"multer": "^1.4.5-lts.1",
-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.338.0",
++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.373.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.125.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.173.0",
"pg": "^8.23.0",
@@@ -2122,9 +2122,9 @@
}
},
"node_modules/openvibe-contracts": {
-- "version": "0.338.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.338.0",
-- "integrity": "sha512-75FD6IxMsXoud3NFXKDs0/D0rhJtF2D+WL8M92ArIyabkUnM/sZvw6E6L97EqwQ3O7M/kB+uwsNSxGudbil+ng==",
++ "version": "0.373.0",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.373.0",
++ "integrity": "sha512-ddW6yG5deFxRgS/MQLgV6pyYx8vco49Nt83DrE+m9XAPwuHxFSeSZgvZ5CznK0cjul3CMmB5NaVXA6jdEbXAfQ==",
"license": "AGPL-3.0-only",
"dependencies": {
"ajv": "^8.17.1",
ddiff --git a/package.json b/package.json
iindex c36d681..f2b84df 100644
---- a/package.json
++++ b/package.json
@@@ -20,7 +20,7 @@
"express": "^4.19.2",
"iovalkey": "^0.4.0",
"multer": "^1.4.5-lts.1",
-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.338.0",
++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.373.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.125.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.173.0",
"pg": "^8.23.0",
ddiff --git a/test/openapi-json.test.js b/test/openapi-json.test.js
iindex 0628534..eba6212 100644
---- a/test/openapi-json.test.js
++++ b/test/openapi-json.test.js
@@@ -6,6 +6,10 @@ const fs = require('fs');
const path = require('path');
const doc = require('openvibe-contracts').openapi.document('media');
assert.ok(doc && doc.openapi && /^3\.1/.test(doc.openapi), 'the contracts carry an OpenAPI 3.1 document for media');
++assert.ok(doc.paths['/api/v2/{app}/objects']?.post, 'upload creation is described');
++assert.ok(doc.paths['/api/v2/{app}/objects/{id}/multipart/{uploadId}']?.get, 'upload resume status is described');
++assert.ok(doc.paths['/api/v2/{app}/objects/{id}/multipart/{uploadId}/parts/{n}']?.put, 'part upload is described');
++assert.ok(doc.paths['/api/v2/{app}/objects/{id}/multipart/{uploadId}/complete']?.post, 'completion is described');
const src = fs.readFileSync(path.join(__dirname, '..', 'server/index.js'), 'utf8');
assert.match(src, /app\.get\('\/openapi\.json'/, 'the route is mounted');
assert.match(src, /openapi\.document\('media'\)/);