Skip to content
Snippets Groups Projects
Select Git revision
  • 4277a99505c2fb9c26147c79b86d4b97d20fd905
  • mui5-annotation-on-video-stable default
  • get_setter_canvasSizeInformations
  • fix-error-div-into-p
  • annotation-on-video-v2
  • detached
  • annotation-on-video-r17
  • mui5
  • mui5-react-18
  • jacob-test
  • annotation-on-video protected
  • master
  • test-antoinev1
  • 20-fetch-thumbnail-on-annotation
  • add-research-field
  • Save
  • add-plugin
  • 14-wip-no-seek-to
  • 14-bug-on-video-time-control
  • 9_wip_videotests
  • _upgrade_material_ui
  • latest-tetras-16
  • v3.3.0
  • v3.2.0
  • v3.1.1
  • v3.1.0
  • v3.0.0
  • v3.0.0-rc.7
  • v3.0.0-rc.6
  • v3.0.0-rc.5
  • v3.0.0-rc.4
  • v3.0.0-rc.3
  • v3.0.0-rc.2
  • v3.0.0-rc.1
  • v3.0.0-beta.10
  • v3.0.0-beta.9
  • v3.0.0-beta.8
  • v3.0.0-beta.7
  • v3.0.0-beta.6
  • v3.0.0-beta.5
  • v3.0.0-beta.3
41 results

iiif.js

Blame
  • user avatar
    Justin Coyne authored and Chris Beer committed
    fetch is built into the browser
    6150c0ae
    History
    iiif.js 7.35 KiB
    import {
      all, call, put, select, takeEvery,
    } from 'redux-saga/effects';
    import { Utils } from 'manifesto.js';
    import normalizeUrl from 'normalize-url';
    import ActionTypes from '../actions/action-types';
    import {
      receiveManifest, receiveManifestFailure, receiveInfoResponse,
      receiveInfoResponseFailure, receiveDegradedInfoResponse,
      receiveSearch, receiveSearchFailure,
      receiveAnnotation, receiveAnnotationFailure,
    } from '../actions';
    import {
      getManifests,
      getRequestsConfig,
      getAccessTokens,
      selectInfoResponse,
    } from '../selectors';
    
    /** */
    function fetchWrapper(url, options, { success, degraded, failure }) {
      return fetch(url, options)
        .then(response => response.json().then((json) => {
          if (response.status === 401) return (degraded || success)({ json, response });
          if (response.ok) return success({ json, response });
          return failure({ error: response.statusText, json, response });
        }).catch(error => failure({ error, response })))
        .catch(error => failure({ error }));
    }
    
    /** */
    function* fetchIiifResource(url, options, { success, degraded, failure }) {
      const { preprocessors = [], postprocessors = [] } = yield select(getRequestsConfig);
    
      try {
        const reqOptions = preprocessors.reduce((acc, f) => f(url, acc) || acc, options);
    
        let action = yield call(fetchWrapper, url, reqOptions, { degraded, failure, success });
        action = postprocessors.reduce((acc, f) => f(url, acc) || acc, action);
        return action;
      } catch (error) { return failure({ error }); }
    }
    
    /** */
    function* fetchIiifResourceWithAuth(url, iiifResource, options, { degraded, failure, success }) {
      const urlOptions = { ...options };
      let tokenServiceId;
    
      // If we have a requested IIIF resource (say, the image description from the manifest)
      // we can optimistically try an appropriate access token.
      //
      // TODO: there might be multiple applicable access token services
      if (iiifResource) {
        const tokenService = yield call(getAccessTokenService, iiifResource);
        tokenServiceId = tokenService && tokenService.id;
    
        if (tokenService && tokenService.json) {
          urlOptions.headers = {
            Authorization: `Bearer ${tokenService.json.accessToken}`,
            ...options.headers,
          };
        }
      }
    
      const { error, json, response } = yield call(
        fetchIiifResource,
        url,
        urlOptions,
        { failure: arg => arg, success: arg => arg },
      );
    
      // Hard error either requesting the resource or deserializing the JSON.
      if (error) {
        yield put(failure({
          error, json, response, tokenServiceId,
        }));
        return;
      }
    
      const id = json['@id'] || json.id;
      if (response.ok) {
        if (normalizeUrl(id, { stripAuthentication: false })
          === normalizeUrl(url.replace(/info\.json$/, ''), { stripAuthentication: false })) {
          yield put(success({ json, response, tokenServiceId }));
          return;
        }
      } else if (response.status !== 401) {
        yield put(failure({
          error, json, response, tokenServiceId,
        }));
    
        return;
      }
    
      // Start attempting some IIIF Auth;
      // First, the IIIF resource we were given may not be authoritative; check if
      // it suggests a different access token service and re-enter the auth workflow
      const authoritativeTokenService = yield call(getAccessTokenService, json);
      if (authoritativeTokenService && authoritativeTokenService.id !== tokenServiceId) {
        yield call(fetchIiifResourceWithAuth, url, json, options, { degraded, failure, success });
        return;
      }
    
      // Record the response (potentially kicking off other auth flows)
      yield put((degraded || success)({ json, response, tokenServiceId }));
    }
    
    /** */
    export function* fetchManifest({ manifestId }) {
      const callbacks = {
        failure: ({ error, json, response }) => receiveManifestFailure(manifestId, typeof error === 'object' ? String(error) : error),
        success: ({ json, response }) => receiveManifest(manifestId, json),
      };
      const dispatch = yield call(fetchIiifResource, manifestId, {}, callbacks);
      yield put(dispatch);
    }
    
    /** @private */
    function* getAccessTokenService(resource) {
      const manifestoCompatibleResource = resource && resource.__jsonld
        ? resource
        : { ...resource, options: {} };
      const services = Utils.getServices(manifestoCompatibleResource).filter(s => s.getProfile().match(/http:\/\/iiif.io\/api\/auth\//));
      if (services.length === 0) return undefined;
    
      const accessTokens = yield select(getAccessTokens);
      if (!accessTokens) return undefined;
    
      for (let i = 0; i < services.length; i += 1) {
        const authService = services[i];
        const accessTokenService = Utils.getService(authService, 'http://iiif.io/api/auth/1/token')
          || Utils.getService(authService, 'http://iiif.io/api/auth/0/token');
        const token = accessTokenService && accessTokens[accessTokenService.id];
        if (token && token.json) return token;
      }
    
      return undefined;
    }
    
    /** @private */
    export function* fetchInfoResponse({ imageResource, infoId, windowId }) {
      let iiifResource = imageResource;
      if (!iiifResource) {
        iiifResource = yield select(selectInfoResponse, { infoId });
      }
    
      const callbacks = {
        degraded: ({
          json, response, tokenServiceId,
        }) => receiveDegradedInfoResponse(infoId, json, response.ok, tokenServiceId, windowId),
        failure: ({
          error, json, response, tokenServiceId,
        }) => (
          receiveInfoResponseFailure(infoId, error, tokenServiceId)
        ),
        success: ({
          json, response, tokenServiceId,
        }) => receiveInfoResponse(infoId, json, response.ok, tokenServiceId),
      };
    
      yield call(fetchIiifResourceWithAuth, `${infoId.replace(/\/$/, '')}/info.json`, iiifResource, {}, callbacks);
    }
    
    /** @private */
    export function* fetchSearchResponse({
      windowId, companionWindowId, query, searchId,
    }) {
      const callbacks = {
        failure: ({ error, json, response }) => (
          receiveSearchFailure(windowId, companionWindowId, searchId, error)
        ),
        success: ({ json, response }) => receiveSearch(windowId, companionWindowId, searchId, json),
      };
      const dispatch = yield call(fetchIiifResource, searchId, {}, callbacks);
      yield put(dispatch);
    }
    
    /** @private */
    export function* fetchAnnotation({ targetId, annotationId }) {
      const callbacks = {
        failure: ({ error, json, response }) => (
          receiveAnnotationFailure(targetId, annotationId, error)
        ),
        success: ({ json, response }) => receiveAnnotation(targetId, annotationId, json),
      };
      const dispatch = yield call(fetchIiifResource, annotationId, {}, callbacks);
      yield put(dispatch);
    }
    
    /** */
    export function* fetchResourceManifest({ manifestId, manifestJson }) {
      if (manifestJson) {
        yield put(receiveManifest(manifestId, manifestJson));
        return;
      }
    
      if (!manifestId) return;
    
      const manifests = yield select(getManifests) || {};
      if (!manifests[manifestId]) yield* fetchManifest({ manifestId });
    }
    
    /** */
    export function* fetchManifests(...manifestIds) {
      const manifests = yield select(getManifests);
    
      for (let i = 0; i < manifestIds.length; i += 1) {
        const manifestId = manifestIds[i];
        if (!manifests[manifestId]) yield call(fetchManifest, { manifestId });
      }
    }
    
    /** */
    export default function* iiifSaga() {
      yield all([
        takeEvery(ActionTypes.REQUEST_MANIFEST, fetchManifest),
        takeEvery(ActionTypes.REQUEST_INFO_RESPONSE, fetchInfoResponse),
        takeEvery(ActionTypes.REQUEST_SEARCH, fetchSearchResponse),
        takeEvery(ActionTypes.REQUEST_ANNOTATION, fetchAnnotation),
        takeEvery(ActionTypes.ADD_RESOURCE, fetchResourceManifest),
      ]);
    }