Skip to content

Commit 0231d8e

Browse files
committed
Auth2 Probe responses WIP - needs specs for:
- src/state/sagas/auth.js - src/state/sagas/iiif.js - src/state/sagas/windows.js - src/state/selectors/auth.js - src/state/selectors/canvases.js
1 parent bc166cb commit 0231d8e

5 files changed

Lines changed: 175 additions & 29 deletions

File tree

src/state/sagas/auth.js

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Utils } from 'manifesto.js';
55
import flatten from 'lodash/flatten';
66
import ActionTypes from '../actions/action-types';
77
import MiradorCanvas from '../../lib/MiradorCanvas';
8-
import { getTokenService } from '../../lib/getServices';
8+
import { getTokenService, getProbeService } from '../../lib/getServices';
99
import {
1010
addAuthenticationRequest,
1111
resolveAuthenticationRequest,
@@ -14,13 +14,14 @@ import {
1414
} from '../actions';
1515
import {
1616
selectInfoResponses,
17+
selectProbeResponses,
1718
getVisibleCanvases,
1819
getWindows,
1920
getConfig,
2021
getAuth,
2122
getAccessTokens,
2223
} from '../selectors';
23-
import { fetchInfoResponse } from './iiif';
24+
import { fetchInfoResponse, fetchProbeResponse } from './iiif';
2425

2526
/** */
2627
export function* refetchInfoResponsesOnLogout({ tokenServiceId }) {
@@ -70,6 +71,54 @@ export function* refetchInfoResponses({ serviceId }) {
7071
}));
7172
}
7273

74+
/** */
75+
export function* refetchProbeResponsesOnLogout({ tokenServiceId }) {
76+
// delay logout actions to give the cookie service a chance to invalidate our cookies
77+
// before we reinitialize openseadragon and rerequest images.
78+
79+
yield delay(2000);
80+
yield call(refetchProbeResponses, { serviceId: tokenServiceId });
81+
}
82+
83+
/**
84+
* Figure out what probe responses could have used the access token service and:
85+
* - refetch, if they are currently visible
86+
* - throw them out (and lazy re-fetch) otherwise
87+
*/
88+
export function* refetchProbeResponses({ serviceId }) {
89+
const windows = yield select(getWindows);
90+
91+
const canvases = yield all(
92+
Object.keys(windows).map(windowId => select(getVisibleCanvases, { windowId })),
93+
);
94+
95+
const visibleProbeServiceIds = flatten(flatten(canvases).map((canvas) => {
96+
const miradorCanvas = new MiradorCanvas(canvas);
97+
return miradorCanvas.imageResources.filter((r) => getProbeService(r)).map((r) => getProbeService(r));
98+
}));
99+
100+
const probeResponses = yield select(selectProbeResponses);
101+
/** */
102+
const haveThisTokenService = probeResponse => {
103+
const services = Utils.getServices(probeResponse);
104+
return services.some(e => {
105+
const probeTokenService = getTokenService(e);
106+
return probeTokenService && probeTokenService.id === serviceId;
107+
});
108+
};
109+
110+
const obsoleteProbeResponses = Object.values(probeResponses).filter(
111+
i => i.json && haveThisTokenService(i.json),
112+
);
113+
114+
yield all(obsoleteProbeResponses.map(({ id: probeId }) => {
115+
if (visibleProbeServiceIds.includes(probeId)) {
116+
return call(fetchProbeResponse, { probeId });
117+
}
118+
return put({ probeId, type: ActionTypes.REMOVE_PROBE_RESPONSE });
119+
}));
120+
}
121+
73122
/** try to start any non-interactive auth flows */
74123
export function* doAuthWorkflow({ infoJson, windowId }) {
75124
const auths = yield select(getAuth);
@@ -152,9 +201,13 @@ export function* invalidateInvalidAuth({ serviceId }) {
152201
export default function* authSaga() {
153202
yield all([
154203
takeEvery(ActionTypes.RECEIVE_DEGRADED_INFO_RESPONSE, rerequestOnAccessTokenFailure),
204+
takeEvery(ActionTypes.RECEIVE_DEGRADED_PROBE_RESPONSE, rerequestOnAccessTokenFailure),
155205
takeEvery(ActionTypes.RECEIVE_ACCESS_TOKEN_FAILURE, invalidateInvalidAuth),
156206
takeEvery(ActionTypes.RECEIVE_DEGRADED_INFO_RESPONSE, doAuthWorkflow),
207+
takeEvery(ActionTypes.RECEIVE_DEGRADED_PROBE_RESPONSE, doAuthWorkflow),
157208
takeEvery(ActionTypes.RECEIVE_ACCESS_TOKEN, refetchInfoResponses),
209+
takeEvery(ActionTypes.RECEIVE_ACCESS_TOKEN, refetchProbeResponses),
158210
takeEvery(ActionTypes.RESET_AUTHENTICATION_STATE, refetchInfoResponsesOnLogout),
211+
takeEvery(ActionTypes.RESET_AUTHENTICATION_STATE, refetchProbeResponsesOnLogout),
159212
]);
160213
}

src/state/sagas/iiif.js

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
import {
22
all, call, put, select, takeEvery,
33
} from 'redux-saga/effects';
4-
import { Utils } from 'manifesto.js';
54
import normalizeUrl from 'normalize-url';
65
import ActionTypes from '../actions/action-types';
76
import {
87
receiveManifest, receiveManifestFailure, receiveInfoResponse,
98
receiveInfoResponseFailure, receiveDegradedInfoResponse,
109
receiveSearch, receiveSearchFailure,
1110
receiveAnnotation, receiveAnnotationFailure,
11+
receiveProbeResponse, receiveProbeResponseFailure,
12+
receiveDegradedProbeResponse,
1213
} from '../actions';
13-
import { getTokenService } from '../../lib/getServices';
14+
import { anyAuthServices, getTokenService } from '../../lib/getServices';
1415
import {
1516
getManifests,
1617
getRequestsConfig,
1718
getAccessTokens,
1819
selectInfoResponse,
20+
selectProbeResponse,
1921
} from '../selectors';
2022

2123
/** */
@@ -80,10 +82,13 @@ function* fetchIiifResourceWithAuth(url, iiifResource, options, { degraded, fail
8082

8183
const id = json['@id'] || json.id;
8284
if (response.ok) {
83-
if (normalizeUrl(id, { stripAuthentication: false })
85+
if (id && normalizeUrl(id, { stripAuthentication: false })
8486
=== normalizeUrl(url.replace(/info\.json$/, ''), { stripAuthentication: false })) {
85-
yield put(success({ json, response, tokenServiceId }));
86-
return;
87+
if (!json.substitute) {
88+
// substitute indicates the Auth2 equivalent of a degraded response, should fall through
89+
yield put(success({ json, response, tokenServiceId }));
90+
return;
91+
}
8792
}
8893
} else if (response.status !== 401) {
8994
yield put(failure({
@@ -121,7 +126,7 @@ function* getAccessTokenService(resource) {
121126
const manifestoCompatibleResource = resource && resource.__jsonld
122127
? resource
123128
: { ...resource, options: {} };
124-
const services = Utils.getServices(manifestoCompatibleResource).filter(s => s.getProfile().match(/http:\/\/iiif.io\/api\/auth\//));
129+
const services = anyAuthServices(manifestoCompatibleResource);
125130
if (services.length === 0) return undefined;
126131

127132
const accessTokens = yield select(getAccessTokens);
@@ -161,6 +166,30 @@ export function* fetchInfoResponse({ imageResource, infoId, windowId }) {
161166
yield call(fetchIiifResourceWithAuth, `${infoId.replace(/\/$/, '')}/info.json`, iiifResource, {}, callbacks);
162167
}
163168

169+
/** @private */
170+
export function* fetchProbeResponse({ resource, probeId, windowId }) {
171+
let iiifResource = resource;
172+
if (!iiifResource) {
173+
iiifResource = yield select(selectProbeResponse, { probeId });
174+
}
175+
176+
const callbacks = {
177+
degraded: ({
178+
json, response, tokenServiceId,
179+
}) => receiveDegradedProbeResponse(probeId, json, response.ok, tokenServiceId, windowId),
180+
failure: ({
181+
error, json, response, tokenServiceId,
182+
}) => (
183+
receiveProbeResponseFailure(probeId, error, tokenServiceId)
184+
),
185+
success: ({
186+
json, response, tokenServiceId,
187+
}) => receiveProbeResponse(probeId, json, response.ok, tokenServiceId),
188+
};
189+
190+
yield call(fetchIiifResourceWithAuth, probeId, iiifResource, {}, callbacks);
191+
}
192+
164193
/** @private */
165194
export function* fetchSearchResponse({
166195
windowId, companionWindowId, query, searchId,
@@ -215,6 +244,7 @@ export default function* iiifSaga() {
215244
yield all([
216245
takeEvery(ActionTypes.REQUEST_MANIFEST, fetchManifest),
217246
takeEvery(ActionTypes.REQUEST_INFO_RESPONSE, fetchInfoResponse),
247+
takeEvery(ActionTypes.REQUEST_PROBE_RESPONSE, fetchProbeResponse),
218248
takeEvery(ActionTypes.REQUEST_SEARCH, fetchSearchResponse),
219249
takeEvery(ActionTypes.REQUEST_ANNOTATION, fetchAnnotation),
220250
takeEvery(ActionTypes.ADD_RESOURCE, fetchResourceManifest),

src/state/sagas/windows.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
import ActionTypes from '../actions/action-types';
55
import MiradorManifest from '../../lib/MiradorManifest';
66
import MiradorCanvas from '../../lib/MiradorCanvas';
7+
import { getProbeService } from '../../lib/getServices';
78
import {
89
setContentSearchCurrentAnnotation,
910
selectAnnotation,
@@ -13,6 +14,7 @@ import {
1314
fetchSearch,
1415
receiveManifest,
1516
fetchInfoResponse,
17+
fetchProbeResponse,
1618
showCollectionDialog,
1719
} from '../actions';
1820
import {
@@ -27,6 +29,7 @@ import {
2729
getElasticLayout,
2830
getCanvases,
2931
selectInfoResponses,
32+
selectProbeResponses,
3033
getWindowConfig,
3134
} from '../selectors';
3235
import { fetchManifests } from './iiif';
@@ -250,6 +253,21 @@ export function* fetchInfoResponses({ visibleCanvases: visibleCanvasIds, windowI
250253
}));
251254
}
252255

256+
/** Fetch probe responses for the visible canvases */
257+
export function* fetchProbeResponses({ visibleCanvases: visibleCanvasIds, windowId }) {
258+
const canvases = yield select(getCanvases, { windowId });
259+
const probeResponses = yield select(selectProbeResponses);
260+
const visibleCanvases = (canvases || []).filter(c => visibleCanvasIds.includes(c.id));
261+
262+
yield all(visibleCanvases.map((canvas) => {
263+
const miradorCanvas = new MiradorCanvas(canvas);
264+
return all(miradorCanvas.imageResources.filter((r) => getProbeService(r)).map(resource => (
265+
!probeResponses[getProbeService(resource).id]
266+
&& put(fetchProbeResponse({ resource, windowId }))
267+
)).filter(Boolean));
268+
}));
269+
}
270+
253271
/** */
254272
export function* determineAndShowCollectionDialog(manifestId, windowId) {
255273
const manifestoInstance = yield select(getManifestoInstance, { manifestId });
@@ -266,6 +284,7 @@ export default function* windowsSaga() {
266284
takeEvery(ActionTypes.UPDATE_WINDOW, setCanvasOnNewSequence),
267285
takeEvery(ActionTypes.SET_CANVAS, setCurrentAnnotationsOnCurrentCanvas),
268286
takeEvery(ActionTypes.SET_CANVAS, fetchInfoResponses),
287+
takeEvery(ActionTypes.SET_CANVAS, fetchProbeResponses),
269288
takeEvery(ActionTypes.UPDATE_COMPANION_WINDOW, fetchCollectionManifests),
270289
takeEvery(ActionTypes.SET_WINDOW_VIEW_TYPE, updateVisibleCanvases),
271290
takeEvery(ActionTypes.RECEIVE_SEARCH, setCanvasOfFirstSearchResult),

src/state/selectors/auth.js

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { createSelector } from 'reselect';
22
import { Utils } from 'manifesto.js';
33
import flatten from 'lodash/flatten';
4+
import { anyProbeServices } from '../../lib/getServices';
45
import {
5-
audioResourcesFrom, filterByTypes, textResourcesFrom, videoResourcesFrom,
6+
audioResourcesFrom, iiifImageResourcesFrom, textResourcesFrom, videoResourcesFrom,
67
} from '../../lib/typeFilters';
78
import MiradorCanvas from '../../lib/MiradorCanvas';
89
import { miradorSlice } from './utils';
910
import { getConfig } from './config';
10-
import { getVisibleCanvases, selectInfoResponses } from './canvases';
11+
import { getVisibleCanvases, selectInfoResponses, selectProbeResponses } from './canvases';
1112

1213
export const getAuthProfiles = createSelector(
1314
[
@@ -26,19 +27,19 @@ export const selectCurrentAuthServices = createSelector(
2627
[
2728
getVisibleCanvases,
2829
selectInfoResponses,
30+
selectProbeResponses,
2931
getAuthProfiles,
3032
getAuth,
3133
(state, { iiifResources }) => iiifResources,
3234
],
33-
(canvases, infoResponses = {}, serviceProfiles, auth, iiifResources) => {
35+
(canvases, infoResponses = {}, probeResponses = {}, serviceProfiles, auth, iiifResources) => {
3436
let currentAuthResources = iiifResources;
3537

3638
if (!currentAuthResources && canvases) {
3739
currentAuthResources = flatten(canvases.map(c => {
3840
const miradorCanvas = new MiradorCanvas(c);
39-
const images = miradorCanvas.iiifImageResources;
40-
41-
return images.map(i => {
41+
const canvasResources = miradorCanvas.imageResources;
42+
const authResources = iiifImageResourcesFrom(canvasResources).map(i => {
4243
const iiifImageService = i.getServices()[0];
4344

4445
const infoResponse = infoResponses[iiifImageService.id];
@@ -48,14 +49,7 @@ export const selectCurrentAuthServices = createSelector(
4849

4950
return iiifImageService;
5051
});
51-
}));
52-
}
53-
54-
if (currentAuthResources.length === 0 && canvases) {
55-
currentAuthResources = flatten(canvases.map(c => {
56-
const miradorCanvas = new MiradorCanvas(c);
57-
const canvasResources = miradorCanvas.imageResources;
58-
return videoResourcesFrom(canvasResources)
52+
return authResources.concat(videoResourcesFrom(canvasResources))
5953
.concat(audioResourcesFrom(canvasResources))
6054
.concat(textResourcesFrom(canvasResources));
6155
}));
@@ -67,7 +61,7 @@ export const selectCurrentAuthServices = createSelector(
6761
const currentAuthServices = currentAuthResources.map(resource => {
6862
let lastAttemptedService;
6963
const resourceServices = Utils.getServices(resource);
70-
const probeServices = filterByTypes(resourceServices, 'AuthProbeService2');
64+
const probeServices = anyProbeServices(resource);
7165
const probeServiceServices = flatten(probeServices.map(p => Utils.getServices(p)));
7266

7367
for (const authProfile of serviceProfiles) {
@@ -91,7 +85,6 @@ export const selectCurrentAuthServices = createSelector(
9185
if (service && !h[service.id]) {
9286
h[service.id] = service; // eslint-disable-line no-param-reassign
9387
}
94-
9588
return h;
9689
}, {}));
9790
},

0 commit comments

Comments
 (0)