Skip to content

Commit

Permalink
feat: Dilicom API "apiapp" integration (PR #1684)
Browse files Browse the repository at this point in the history
  • Loading branch information
panaC authored Jun 8, 2022
1 parent 6cda247 commit fec61ed
Show file tree
Hide file tree
Showing 40 changed files with 838 additions and 7 deletions.
2 changes: 2 additions & 0 deletions src/common/api/api.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ICatalogModuleApi } from "./interface/catalog.interface";
import { IKeyboardModuleApi } from "./interface/keyboardApi.interface";
import { ILcpModuleApi } from "./interface/lcpApi.interface";
import { IOpdsModuleApi } from "./interface/opdsApi.interface";
import { IApiappModuleApi } from "./interface/apiappApi.interface";
import { IHttpBrowserModuleApi } from "./interface/httpBrowser.interface";
import { IPublicationModuleApi } from "./interface/publicationApi.interface";
import { IReaderModuleApi } from "./interface/readerApi.interface";
Expand All @@ -18,6 +19,7 @@ export type TApiMethod =
ICatalogModuleApi &
ILcpModuleApi &
IOpdsModuleApi &
IApiappModuleApi &
IHttpBrowserModuleApi &
IKeyboardModuleApi &
IPublicationModuleApi &
Expand Down
25 changes: 25 additions & 0 deletions src/common/api/interface/apiappApi.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// ==LICENSE-BEGIN==
// Copyright 2017 European Digital Reading Lab. All rights reserved.
// Licensed to the Readium Foundation under one or more contributor license agreements.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file exposed on Github (readium) in the project repository.
// ==LICENSE-END==

import { SagaGenerator } from "typed-redux-saga";

export interface IApiappSearchResultView {
id: string; // gln
name: string;
address: string;
url: string;
}

export interface IApiappApi {
search: (
query: string,
) => SagaGenerator<IApiappSearchResultView[]>;
}

export interface IApiappModuleApi {
"apiapp/search": IApiappApi["search"];
}
2 changes: 2 additions & 0 deletions src/common/api/methodApi.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ import { IOpdsApi } from "./interface/opdsApi.interface";
import { IPublicationApi } from "./interface/publicationApi.interface";
import { IReaderApi } from "./interface/readerApi.interface";
import { ISessionApi } from "./interface/session.interface";
import { IApiappApi } from "./interface/apiappApi.interface";

export type TMethodApi =
keyof ICatalogApi |
keyof IPublicationApi |
keyof IOpdsApi |
keyof IApiappApi |
keyof IHttpBrowserApi |
keyof IKeyboardApi |
keyof ILcpApi |
Expand Down
2 changes: 2 additions & 0 deletions src/common/api/moduleApi.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
type TCatalogApi = "catalog";
type TPublicationApi = "publication";
type TOpdsApi = "opds";
type TApiappApi = "apiapp";
type THttpBrowserApi = "httpbrowser";
type TKeyboardApi = "keyboardShortcuts";
type TLcpApi = "lcp";
Expand All @@ -18,6 +19,7 @@ export type TModuleApi =
TCatalogApi |
TPublicationApi |
TOpdsApi |
TApiappApi |
THttpBrowserApi |
TKeyboardApi |
TLcpApi |
Expand Down
2 changes: 2 additions & 0 deletions src/common/models/dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export enum DialogTypeName {
PublicationInfoLib = "publication-info-lib",
PublicationInfoReader = "publication-info-reader",
OpdsFeedAddForm = "opds-feed-add-form",
ApiappAddForm = "apiapp-add-form",
DeletePublicationConfirm = "delete-publication-confirm",
DeleteOpdsFeedConfirm = "delete-opds-feed-confirm",
LcpAuthentication = "lcp-authentication",
Expand All @@ -51,6 +52,7 @@ export interface DialogType {
[DialogTypeName.PublicationInfoLib]: IPubInfoState;
[DialogTypeName.PublicationInfoReader]: IPubInfoStateReader;
[DialogTypeName.OpdsFeedAddForm]: {};
[DialogTypeName.ApiappAddForm]: {};
[DialogTypeName.DeletePublicationConfirm]: {
publicationView: PublicationView;
};
Expand Down
20 changes: 20 additions & 0 deletions src/common/redux/actions/auth/done.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// ==LICENSE-BEGIN==
// Copyright 2017 European Digital Reading Lab. All rights reserved.
// Licensed to the Readium Foundation under one or more contributor license agreements.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file exposed on Github (readium) in the project repository.
// ==LICENSE-END==

import { Action } from "readium-desktop/common/models/redux";

export const ID = "AUTH_DONE";

export function build():
Action<typeof ID> {

return {
type: ID,
};
}
build.toString = () => ID; // Redux StringableActionCreator
export type TAction = ReturnType<typeof build>;
2 changes: 2 additions & 0 deletions src/common/redux/actions/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
// ==LICENSE-END==

import * as wipeData from "./wipeData";
import * as done from "./done";

export {
done,
wipeData,
};
3 changes: 3 additions & 0 deletions src/main/di.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { ReaderApi } from "./api/reader";
import { SessionApi } from "./api/session";
import { httpBrowserApi, publicationApi } from "./redux/sagas/api";
import { opdsApi } from "./redux/sagas/api/opds";
import { apiappApi } from "./redux/sagas/api";
import { RootState } from "./redux/states";
import { OpdsService } from "./services/opds";

Expand Down Expand Up @@ -226,6 +227,7 @@ container.bind<CatalogApi>(diSymbolTable["catalog-api"]).to(CatalogApi).inSingle

container.bind(diSymbolTable["publication-api"]).toConstantValue(publicationApi);
container.bind(diSymbolTable["opds-api"]).toConstantValue(opdsApi);
container.bind(diSymbolTable["apiapp-api"]).toConstantValue(apiappApi);
container.bind(diSymbolTable["httpbrowser-api"]).toConstantValue(httpBrowserApi);

container.bind<KeyboardApi>(diSymbolTable["keyboard-api"]).to(KeyboardApi).inSingletonScope();
Expand Down Expand Up @@ -281,6 +283,7 @@ interface IGet {
(s: "catalog-api"): CatalogApi;
(s: "publication-api"): typeof publicationApi;
(s: "opds-api"): typeof opdsApi;
(s: "apiapp-api"): typeof apiappApi;
(s: "httpbrowser-api"): typeof httpBrowserApi;
(s: "keyboard-api"): KeyboardApi;
(s: "lcp-api"): LcpApi;
Expand Down
1 change: 1 addition & 0 deletions src/main/diSymbolTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const diSymbolTable = {
"catalog-api": Symbol("catalog-api"),
"publication-api": Symbol("publication-api"),
"opds-api": Symbol("opds-api"),
"apiapp-api": Symbol("apiapp-api"),
"httpbrowser-api": Symbol("httpbrowser-api"),
"keyboard-api": Symbol("keyboard-api"),
"lcp-api": Symbol("lcp-api"),
Expand Down
8 changes: 8 additions & 0 deletions src/main/network/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,14 @@ export async function httpFetchRawResponse(
locale = tryCatchSync(() => diMainGet("store")?.getState()?.i18n?.locale, filename_) || "en-US",
): Promise<THttpResponse> {

url = new URL(url);
if (url.host.startsWith("apiapploans.org.edrlab.thoriumreader")) {
const [, idGln, host] = url.host.split(".break.");

debug("http fetch dilicom api app server ", idGln, url.toString());
url.host = host;
}

options.headers = options.headers instanceof Headers
? options.headers
: new Headers(options.headers || {});
Expand Down
13 changes: 13 additions & 0 deletions src/main/redux/sagas/api/apiapp/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// ==LICENSE-BEGIN==
// Copyright 2017 European Digital Reading Lab. All rights reserved.
// Licensed to the Readium Foundation under one or more contributor license agreements.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file exposed on Github (readium) in the project repository.
// ==LICENSE-END==

import { IApiappApi } from "readium-desktop/common/api/interface/apiappApi.interface";
import { search } from "./search";

export const apiappApi: IApiappApi = {
search,
};
27 changes: 27 additions & 0 deletions src/main/redux/sagas/api/apiapp/search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// ==LICENSE-BEGIN==
// Copyright 2017 European Digital Reading Lab. All rights reserved.
// Licensed to the Readium Foundation under one or more contributor license agreements.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file exposed on Github (readium) in the project repository.
// ==LICENSE-END==

import * as debug_ from "debug";
import { IApiappSearchResultView } from "readium-desktop/common/api/interface/apiappApi.interface";
import { call } from "typed-redux-saga/macro";
import { SagaGenerator } from "typed-redux-saga/dist";
import { librarySearch } from "../../apiapp";

const debug = debug_("readium-desktop:main/redux/sagas/api/apiapp/search");

export function* search(query: string): SagaGenerator<IApiappSearchResultView[]> {

debug(query); // FIXME DEBUG

if (query) {
const res = yield* call(librarySearch, query);
return res as IApiappSearchResultView[];
}


return [];
}
119 changes: 116 additions & 3 deletions src/main/redux/sagas/api/browser/browse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ import { IOpdsResultView } from "readium-desktop/common/views/opds";
import { IProblemDetailsResultView } from "readium-desktop/common/views/problemDetails";
import { diMainGet } from "readium-desktop/main/di";
import { httpGet } from "readium-desktop/main/network/http";
import { contentTypeisApiProblem, parseContentType } from "readium-desktop/utils/contentType";
import { OpdsService } from "readium-desktop/main/services/opds";
import { ContentType, contentTypeisApiProblem, parseContentType } from "readium-desktop/utils/contentType";
import { call, SagaGenerator } from "typed-redux-saga";
import { authenticationRequestFromLibraryWebServiceURL, convertLoansPublicationToOpdsPublicationsRawJson, getEndpointFromAuthenticationRequest, getLoansPublicationFromLibrary } from "../../apiapp";

const debug = debug_("readium-desktop:main#redux/saga/api/browser");

Expand All @@ -31,9 +33,120 @@ const checkUrl = (url: string) => {

export function* browse(urlRaw: string): SagaGenerator<THttpGetBrowserResultView> {

const url = checkUrl(urlRaw);
debug("BROWSE=", urlRaw);
const opdsService = diMainGet("opds-service") as OpdsService;

if (urlRaw.startsWith("apiapp://")) {
const urlApiapp = urlRaw.slice("apiapp://".length);
const [idGln, urlLib] = urlApiapp.split(":apiapp:");

debug("APIAPP");
debug("ID_GNL=", idGln);
debug("URL_LIB=", urlLib);

const res = yield* call(authenticationRequestFromLibraryWebServiceURL, urlLib);

debug("authentication Result from dilicom :" + idGln + ":", res);
const endpoint = getEndpointFromAuthenticationRequest(res);
if (!endpoint) {
return {
url: "",
isFailure: true,
isSuccess: false,
};
}

const endpointURL = new URL(endpoint);
endpointURL.host = `apiapploans.org.edrlab.thoriumreader.break.${idGln}.break.${endpointURL.host}`;
const endpointFormated = endpointURL.toString();
const loansPubArray = yield* call(getLoansPublicationFromLibrary, endpointFormated);

const opdsService = diMainGet("opds-service");
// this does not work, because auth flow leaves blank JSX comp until refreshed with correct credentials
// if (!loansPubArray) { // note: empty array is not falsy
// const translator = diMainGet("translator");
// return {
// url: "",
// isFailure: true,
// isSuccess: false,
// statusCode: 403,
// statusMessage: translator.trans__late("apiapp.incorrectCredentials"),
// };
// }

if (Array.isArray(loansPubArray)) {
// return opdsauthentication
return {
url: "",
responseUrl: "",
contentType: ContentType.Opds2,
isFailure: false,
isSuccess: true,
data: {
opds: yield* call(() => opdsService.opdsRequestTransformer({
url: "",
responseUrl: "",
contentType: ContentType.Opds2,
isFailure: false,
isSuccess: true,
/**
* Why do I need a ts-expect-error flag : That's a very good question !
@ts-expect-error */
response: {
json: async () => {
return {
"metadata": {
"title": "loans",
},
"publications": convertLoansPublicationToOpdsPublicationsRawJson(loansPubArray),
};
},
},
})),
},
};
} else {
// return opds feed

return {
url: "",
responseUrl: "",
contentType: ContentType.Opds2,
isFailure: false,
isSuccess: true,
data: {
opds: yield* call(() => opdsService.opdsRequestTransformer({
url: "",
responseUrl: endpointFormated, // check base url in auth
contentType: ContentType.Opds2Auth,
isFailure: false,
isSuccess: true,
/**
* I need it because I'm too lazy to complete the type
@ts-expect-error */
response: {
json: async () => {
return {
"id": idGln,
"title": "auth",
"authentication": [
{
"type": "http://opds-spec.org/auth/oauth/password/apiapp",
"links": [
{ "rel": "authenticate", "href": res.authentication.get_token},
{ "rel": "refresh", "href": res.authentication.refresh_token},
],
},
],
};
},
},
})),
},
};
}
}

const url = checkUrl(urlRaw);

const result = yield* call(() => httpGet<IBrowserResultView>(
url,
Expand Down
1 change: 1 addition & 0 deletions src/main/redux/sagas/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export * from "./publication";
export * from "./api";
export * from "./opds";
export * from "./browser";
export * from "./apiapp";
Loading

0 comments on commit fec61ed

Please sign in to comment.