/** * This code provides a reference implementation in TypeScript for the Web * Anotation Discovery mechanism. * * It implements functionality on the browser side, to detect annotations * embedded in the page, and to detect links to annotation ‘feeds’. */ import MIMEType from 'whatwg-mimetype'; /** * The type used here for Web Annotation objects. For valid input, the object * should include other properties (e.g. the target, id, …). However, this type * defines only the minimum outline required for the functions below; hence the * “should” in the name. */ type ShouldBeAnnotation = { '@context': | 'http://www.w3.org/ns/anno.jsonld' | [...any, 'http://www.w3.org/ns/anno.jsonld', ...any]; type: 'Annotation' | [...any, 'Annotation', ...any]; }; /** * Likewise for an Annotation Collection. */ type ShouldBeAnnotationCollection = { '@context': | 'http://www.w3.org/ns/anno.jsonld' | [...any, 'http://www.w3.org/ns/anno.jsonld', ...any]; type: 'AnnotationCollection' | [...any, 'AnnotationCollection', ...any]; first?: | string | { items: Omit[]; }; }; function isAnnotation(value: any): value is ShouldBeAnnotation { if (typeof value !== 'object') return false; const hasCorrectContext = asArray(value['@context']).some( (context) => context === 'http://www.w3.org/ns/anno.jsonld', ); const hasCorrectType = asArray(value.type).some( (type) => type === 'Annotation', ); return hasCorrectContext && hasCorrectType; } function isAnnotationCollection( value: any, ): value is ShouldBeAnnotationCollection { if (typeof value !== 'object') return false; const hasCorrectContext = asArray(value['@context']).some( (context) => context === 'http://www.w3.org/ns/anno.jsonld', ); const hasCorrectType = asArray(value.type).some( (type) => type === 'AnnotationCollection', ); return hasCorrectContext && hasCorrectType; } /** * Helper function to detect if a script/link has the media type. While an extract string match may be simple and tempting, * many type strings are equivalent. Some examples: * * application/ld+json;profile="http://www.w3.org/ns/anno.jsonld" * application/ld+json;profile="something and http://www.w3.org/ns/anno.jsonld" * application/ld+json;profile="\"with\\escapes\" http://www.w3.org/ns/anno.jsonld" * application/ld+json; charset="utf-8"; profile="http://www.w3.org/ns/anno.jsonld" */ export function isAnnotationMimeType(type: string): boolean { let mimeType: MIMEType; try { mimeType = new MIMEType(type); } catch (error) { return false; } if (mimeType.essence !== 'application/ld+json') return false; const profile = mimeType.parameters.get('profile'); if (!profile) return false; return profile.split(' ').includes('http://www.w3.org/ns/anno.jsonld'); } /** * To discover annotations when navigating to a URL, simply check the content * type of the response. * * If positive, response.json() can be passed into getAnnotationsFromParsedJson(). */ export function responseContainsAnnotations(response: Response) { return ( response.ok && isAnnotationMimeType(response.headers.get('Content-Type') || '') ); } export function getAnnotationsFromParsedJson(value: any): ShouldBeAnnotation[] { // The content could be one annotation, or a collection of annotations. if (isAnnotation(value)) { return [value]; } else if (isAnnotationCollection(value) && typeof value.first === 'object') { return value.first.items.map((annotation) => ({ '@context': value['@context'], ...annotation, })); } else { // Perhaps we got invalid data or an empty collection. return []; } } /** * Find annotations embedded as JSON within