@lde/iiif-validator 0.1.1 → 0.1.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/README.md CHANGED
@@ -28,7 +28,7 @@ interface ManifestValidation {
28
28
 
29
29
  ## Behaviour
30
30
 
31
- - **Dereference over HTTP** with `Accept: application/ld+json, application/json`, following redirects, using the global `fetch` with an `AbortSignal` timeout (default 10 000 ms). Both are injectable via the options argument (`fetch`, `timeoutMs`).
31
+ - **Dereference over HTTP** with `Accept: */*`, following redirects, using the global `fetch` with an `AbortSignal` timeout (default 10 000 ms). The wildcard mirrors what real IIIF viewers send (the browser `fetch` default); a JSON-specific `Accept` would be more correct but trips up hosts that do backwards content negotiation – serving the manifest to `*/*` while returning 404 for a JSON-specific request. Both `fetch` and `timeoutMs` are injectable via the options argument.
32
32
  - **Lightweight, version-aware structural check.** A document is valid when the response is HTTP 2xx, the body parses as JSON, its `@context` references an IIIF Presentation context, and its `type`/`@type` indicates a manifest – accepting both v3 (`Manifest`) and v2 (`sc:Manifest`). The `@context` value may be a string, an array, or an object; all forms are handled. The version segment of the context is accepted version-agnostically.
33
33
  - **Strict failure semantics, no retries.** A timeout, network error, non-2xx status, unparseable body, missing IIIF `@context`, or wrong `type` all yield `valid: false` with the corresponding coarse `reason`. There is no deep JSON Schema validation and no dependency on the hosted IIIF Presentation Validator service.
34
34
 
@@ -3,7 +3,7 @@
3
3
  * *what kind* of failure occurred, not a detailed diagnosis — only enough to
4
4
  * tell apart an unreachable host from a malformed document.
5
5
  */
6
- export type ManifestValidationReason = 'valid-manifest' | 'timeout' | 'network-error' | 'http-error' | 'invalid-json' | 'not-a-manifest';
6
+ export type ManifestValidationReason = 'valid-manifest' | 'timeout' | 'network-error' | 'http-error' | 'invalid-json' | 'binary-content' | 'not-a-manifest';
7
7
  /**
8
8
  * Verdict returned by {@link validateManifest}.
9
9
  */
@@ -1 +1 @@
1
- {"version":3,"file":"validateManifest.d.ts","sourceRoot":"","sources":["../src/validateManifest.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,MAAM,wBAAwB,GAChC,gBAAgB,GAChB,SAAS,GACT,eAAe,GACf,YAAY,GACZ,cAAc,GACd,gBAAgB,CAAC;AAErB;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,0EAA0E;IAC1E,KAAK,EAAE,OAAO,CAAC;IACf,4CAA4C;IAC5C,MAAM,EAAE,wBAAwB,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAKD;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,uBAAuB,GAChC,OAAO,CAAC,kBAAkB,CAAC,CA6B7B"}
1
+ {"version":3,"file":"validateManifest.d.ts","sourceRoot":"","sources":["../src/validateManifest.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,MAAM,wBAAwB,GAChC,gBAAgB,GAChB,SAAS,GACT,eAAe,GACf,YAAY,GACZ,cAAc,GACd,gBAAgB,GAChB,gBAAgB,CAAC;AAErB;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,0EAA0E;IAC1E,KAAK,EAAE,OAAO,CAAC;IACf,4CAA4C;IAC5C,MAAM,EAAE,wBAAwB,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAKD;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,uBAAuB,GAChC,OAAO,CAAC,kBAAkB,CAAC,CA8C7B"}
@@ -11,7 +11,13 @@ export async function validateManifest(url, options) {
11
11
  let response;
12
12
  try {
13
13
  response = await doFetch(url, {
14
- headers: { Accept: 'application/ld+json, application/json' },
14
+ // Request with `Accept: */*`, matching what real IIIF viewers (Mirador,
15
+ // Universal Viewer) send via the browser `fetch` default. A more specific
16
+ // `application/ld+json` is technically correct, but some manifest hosts do
17
+ // backwards content negotiation: they serve the manifest to `*/*` yet 404
18
+ // a JSON-specific request. Asking for anything keeps the validator as
19
+ // permissive as the viewers whose access it stands in for.
20
+ headers: { Accept: '*/*' },
15
21
  signal: AbortSignal.timeout(timeoutMs),
16
22
  });
17
23
  }
@@ -21,6 +27,16 @@ export async function validateManifest(url, options) {
21
27
  if (!response.ok) {
22
28
  return { valid: false, reason: 'http-error' };
23
29
  }
30
+ // A manifest is JSON. When the server announces a binary media type, skip
31
+ // reading the body: a sampled `schema:contentUrl` can dereference to the
32
+ // full-resolution image, audio or video asset, and `response.json()` would
33
+ // buffer the whole thing before failing to parse it — wasting bandwidth and
34
+ // time in the pipeline that calls this for every sampled manifest. Cancel the
35
+ // stream so the connection is freed without the download.
36
+ if (isBinaryMedia(response.headers.get('content-type'))) {
37
+ await response.body?.cancel();
38
+ return { valid: false, reason: 'binary-content' };
39
+ }
24
40
  let body;
25
41
  try {
26
42
  body = await response.json();
@@ -33,6 +49,20 @@ export async function validateManifest(url, options) {
33
49
  }
34
50
  return { valid: false, reason: 'not-a-manifest' };
35
51
  }
52
+ /**
53
+ * Whether a `Content-Type` header announces a binary media asset (image, audio
54
+ * or video) rather than a JSON document. Used to skip downloading non-manifest
55
+ * media. A missing or ambiguous type (e.g. `text/plain`, `application/octet-stream`)
56
+ * returns `false` so a manifest served with an odd type is still parsed.
57
+ */
58
+ function isBinaryMedia(contentType) {
59
+ if (contentType === null)
60
+ return false;
61
+ const mediaType = contentType.toLowerCase();
62
+ return (mediaType.startsWith('image/') ||
63
+ mediaType.startsWith('audio/') ||
64
+ mediaType.startsWith('video/'));
65
+ }
36
66
  /**
37
67
  * Classify a thrown `fetch` error. An aborted request (our own
38
68
  * `AbortSignal.timeout` firing, surfaced as `AbortError`/`TimeoutError`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lde/iiif-validator",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "repository": {
5
5
  "url": "git+https://github.com/ldelements/lde.git",
6
6
  "directory": "packages/iiif-validator"