iiif

How to add a manifest to Universal Viewer that is behind authorization?


The origin of my question comes from loading in a manifest object since the request url requires an authorization header.

I can fetch the a valid IIIF manifest but have not found a way to pass it into Universal Viewer.

The only valid option for IIIF what I have found is passing the URL via iiifManifestId as option

import { init } from 'universalviewer'

init('uv-viewer', {
  iiifManifestId: myManifestUrl
})

From the examples of the repo manifest is used but that should not make a difference with iiifManifestId according to UniversalViewer.ts#L57

What I want to be able to do is something like:

import { init } from 'universalviewer'

const myManifestData = await getManifest(myManifestUrl)

init('uv-viewer', {
  iiifManifestData: myManifestData
})

OR like what Mirador Viewer does:

import { init } from 'universalviewer'

init('uv-viewer', {
  iiifManifestId: myManifestUrl,
  requests: {
    preprocessors: [
      (options) => {
        return {
          ...options,
          headers: { authorization: 'Bearer ' + getToken() },
        }
      },
    ],
  },
})

I have seen GitHub issue #43 witch could resolve this issue.

However due to the lack of my understanding of Universal Viewer I wont be continuing that route. Unless the maintainers agree on my findings (in said issue).


Solution

  • Conal-Tuohy commented on Sep 18

    Having read your Stack Overflow post, I now see that your use case is different to the one originally raised in this issue: it's actually about authentication is it not?

    A Service Worker might be a good way to intercept and authenticate the HTTPS requests made by UV

    A Service Worker resolves the issue to add an authorization header to the request for Universal Vieuwer. Below you can see my implementation of the Service Worker, registration, and initialization.

    I explained what is going on in the comments in the code.

    UniversalViewer.worker.js

    self.addEventListener('install', () => {
      // Skip over the "waiting" lifecycle state, to ensure that our
      // new service worker is activated immediately, even if there's
      // another tab open controlled by our older service worker code.
      self.skipWaiting()
    })
    
    self.addEventListener('activate', () => {
      // Optional: Get a list of all the current open windows/tabs under
      // our service worker's control, and force them to reload.
      // This can "unbreak" any open windows/tabs as soon as the new
      // service worker activates, rather than users having to manually reload.
      self.clients.matchAll({ type: 'window' }).then((windowClients) => {
        windowClients.forEach((windowClient) => {
          windowClient.navigate(windowClient.url)
        })
      })
    })
    
    self.addEventListener('message', (event) => {
      if (event.data?.source === 'universal-viewer') {
        token = `${event.data.token}`
      }
    })
    
    // NOTE: My regexp is more complex but it is a start to identify the iiif manifest
    const manifestRegexp =
      /\/iiif\/[0-9]\/manifest\.json$/
    
    /**
     * Catches fetch events and checks if request is made by UniversalViewer
     * @param {FetchEvent} event
     */
    const fetchEvent = async (event) => {
      // Stop modification for requests that are not fit the expected iiif manifest url
      if (manifestRegexp.test(event.request.url) !== true) {
        return
      }
    
      const modifiedRequest = new Request(event.request, {
        headers: {
          authorization: token,
        },
      })
    
      event.respondWith(fetch(modifiedRequest))
    }
    
    self.addEventListener('fetch', fetchEvent)
    

    UniversalViewer.register.ts

    const name = 'UniversalViewer Worker'
    
    export const registerUniversalViewerWorker = (token = 'No Provider') => {
      if ('serviceWorker' in navigator) {
        navigator.serviceWorker
          .register('/UniversalViewer.worker.js', {
            scope: '/',
            updateViaCache: 'none',
          })
          .then((registration) => {
            console.log(`${name} registered service worker`)
            registration.update()
            console.log(`${name} updated service worker`)
            registration.active?.postMessage({
              source: 'universal-viewer',
              token: `Bearer ${token}`,
            })
          })
      }
    }
    

    main.ts // NOTE: this is a simplified example on my implementation

    import { registerUniversalViewerWorker } from './UniversalViewer.register'
    
    initAuthProfider().then((provider) => {
      // other code you do with your provider
    
      registerUniversalViewerWorker(provider.token)
    })