├── .slugignore ├── .flox ├── .gitignore ├── env.json └── env │ └── manifest.toml ├── .npmrc ├── docs ├── index.md ├── remote-epub.md ├── encryption.md ├── http.md └── origin.md ├── tsconfig.json ├── .npmignore ├── misc ├── readers │ ├── reader-NYPL │ │ ├── appcache.html │ │ ├── webapp.webmanifest │ │ ├── manifest.appcache │ │ ├── sw.js │ │ ├── index.html │ │ └── promise.js │ └── reader-HADRIEN │ │ ├── paged.webmanifest │ │ ├── overflow.css │ │ ├── columns.css │ │ ├── sandbox.html │ │ ├── index.html │ │ ├── paginate.js │ │ └── urlsearchparams.js ├── epubs │ ├── wasteland-otf-obf_LCP_dan.epub.userkey │ ├── wasteland-otf-obf.epub │ ├── childrens-literature.epub │ ├── wasteland-otf-obf_LCP_raw.epub.contentkey │ ├── wasteland-otf-obf_LCP_dan.epub │ ├── wasteland-otf-obf_LCP_raw.epub │ └── wasteland-otf-obf_LCP_dan.lcpl └── json-schema │ ├── lcp │ ├── README.md │ ├── lcpdf.schema.json │ ├── link.schema.json │ ├── status.schema.json │ └── license.schema.json │ ├── webpub-manifest │ ├── README.md │ ├── subject.schema.json │ ├── contributor.schema.json │ ├── extensions │ │ ├── epub │ │ │ ├── metadata.schema.json │ │ │ ├── subcollections.schema.json │ │ │ └── properties.schema.json │ │ └── presentation │ │ │ ├── properties.schema.json │ │ │ └── metadata.schema.json │ ├── language-map.schema.json │ ├── subcollection.schema.json │ ├── subject-object.schema.json │ ├── contributor-object.schema.json │ ├── publication.schema.json │ ├── a11y.schema.json │ ├── link.schema.json │ └── metadata.schema.json │ └── opds │ ├── README.md │ ├── acquisition-object.schema.json │ ├── feed-metadata.schema.json │ ├── profile.schema.json │ ├── authentication.schema.json │ ├── catalog-entry.schema.json │ ├── publication.schema.json │ ├── properties.schema.json │ └── feed.schema.json ├── LCP ├── .contentkeys └── .userkeys ├── tsconfigs ├── tsconfig-es5-all.json ├── tsconfig-es6-es2015-all.json ├── tsconfig-es7-es2016-all.json ├── tsconfig-es8-es2017-all.json ├── tsconfig-es5.json ├── tsconfig-es6-es2015.json ├── tsconfig-es7-es2016.json ├── tsconfig-es8-es2017.json └── tsconfig-common.json ├── .eslintignore ├── .prettierrc.js ├── .prettierignore ├── Procfile ├── .gitignore ├── src ├── index.ts ├── declarations.d.ts ├── http │ ├── server-trailing-slash-redirect.ts │ ├── request-ext.ts │ ├── server-version.ts │ ├── server-root.ts │ ├── server-url.ts │ ├── opds2-create-cli.ts │ ├── server-pub.ts │ ├── url-signed-expiry.ts │ └── server-secure.ts └── utils │ └── self-signed.ts ├── package-scripts-build.cson ├── .editorconfig ├── .vscode ├── keybindings.json ├── settings.json ├── tasks.json └── launch.json ├── docker.sh ├── .travis.yml ├── Dockerfile ├── LICENSE ├── tools ├── gitrev.js ├── typescript_compile_single.js └── typescript_relativize_path_mapping_imports.js ├── package-scripts-bundle.cson ├── .eslintrc.js └── package-scripts.cson /.slugignore: -------------------------------------------------------------------------------- 1 | yarn.lock 2 | package-lock.json 3 | -------------------------------------------------------------------------------- /.flox/.gitignore: -------------------------------------------------------------------------------- 1 | run/ 2 | cache/ 3 | lib/ 4 | log/ 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | NPM_CONFIG_PRODUCTION=false 2 | NODE_ENV=dev 3 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | See [README](/README.md#documentation). 2 | -------------------------------------------------------------------------------- /.flox/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "r2-streamer-js", 3 | "version": 1 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfigs/tsconfig-es8-es2017-all" 3 | } 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | node_modules_NPM 3 | node_modules_YARN 4 | .DS_Store 5 | lcp.node 6 | -------------------------------------------------------------------------------- /misc/readers/reader-NYPL/appcache.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /misc/epubs/wasteland-otf-obf_LCP_dan.epub.userkey: -------------------------------------------------------------------------------- 1 | ec4f2dbb3b140095550c9afbbb69b5d6fd9e814b9da82fad0b34e9fcbe56f1cb 2 | -------------------------------------------------------------------------------- /misc/epubs/wasteland-otf-obf.epub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edrlab/r2-streamer-js/HEAD/misc/epubs/wasteland-otf-obf.epub -------------------------------------------------------------------------------- /misc/epubs/childrens-literature.epub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edrlab/r2-streamer-js/HEAD/misc/epubs/childrens-literature.epub -------------------------------------------------------------------------------- /misc/epubs/wasteland-otf-obf_LCP_raw.epub.contentkey: -------------------------------------------------------------------------------- 1 | 2 | 4a297afd457fa9caf9d1ad20a2b59cd1ac61b4d9792add9f8f032cf1775798b3 3 | -------------------------------------------------------------------------------- /LCP/.contentkeys: -------------------------------------------------------------------------------- 1 | 2 | misc/epubs/wasteland-otf-obf_LCP_raw.epub _::_ 4a297afd457fa9caf9d1ad20a2b59cd1ac61b4d9792add9f8f032cf1775798b3 3 | -------------------------------------------------------------------------------- /misc/epubs/wasteland-otf-obf_LCP_dan.epub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edrlab/r2-streamer-js/HEAD/misc/epubs/wasteland-otf-obf_LCP_dan.epub -------------------------------------------------------------------------------- /misc/epubs/wasteland-otf-obf_LCP_raw.epub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edrlab/r2-streamer-js/HEAD/misc/epubs/wasteland-otf-obf_LCP_raw.epub -------------------------------------------------------------------------------- /tsconfigs/tsconfig-es5-all.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig-es5", 3 | "include": [ 4 | "../src/**/*", 5 | "../test/**/*" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .vscode/* 2 | .history/* 3 | .github/* 4 | .git/* 5 | dist/* 6 | docs/* 7 | misc/* 8 | node_modules/* 9 | tools/* 10 | tsconfigs/* 11 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: true, 3 | trailingComma: "all", 4 | singleQuote: false, 5 | printWidth: 120, 6 | tabWidth: 4, 7 | }; 8 | -------------------------------------------------------------------------------- /tsconfigs/tsconfig-es6-es2015-all.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig-es6-es2015", 3 | "include": [ 4 | "../src/**/*", 5 | "../test/**/*" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /tsconfigs/tsconfig-es7-es2016-all.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig-es7-es2016", 3 | "include": [ 4 | "../src/**/*", 5 | "../test/**/*" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /tsconfigs/tsconfig-es8-es2017-all.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig-es8-es2017", 3 | "include": [ 4 | "../src/**/*", 5 | "../test/**/*" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .vscode/* 2 | .history/* 3 | .github/* 4 | .git/* 5 | dist/* 6 | docs/* 7 | misc/* 8 | node_modules/* 9 | tools/* 10 | tsconfigs/* 11 | 12 | src/* 13 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node --version && npm --version && DEBUG=r2:* R2_STREAMER_URL_EXPIRE_SECRET=test R2_STREAMER_URL_EXPIRE_SECONDS=5 node ./dist/es8-es2017/src/http/server-cli.js ./misc/epubs 2 | -------------------------------------------------------------------------------- /misc/readers/reader-HADRIEN/paged.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Paged Prototype", 3 | "short_name": "Paged Prototype", 4 | "background_color": "#4976be", 5 | "display": "standalone" 6 | } -------------------------------------------------------------------------------- /misc/readers/reader-NYPL/webapp.webmanifest: -------------------------------------------------------------------------------- 1 | {"short_name":"Ulysses","name":"Ulysses","start_url":"index.html","display":"standalone","icons":[{"src":"images/cover.png","size":"144x144","type":"image/png"}]} -------------------------------------------------------------------------------- /LCP/.userkeys: -------------------------------------------------------------------------------- 1 | provider_::_ec4f2dbb3b140095550c9afbbb69b5d6fd9e814b9da82fad0b34e9fcbe56f1cb 2 | 3 | f327b94f-7e05-406e-8524-84ef729db700 _::_ ec4f2dbb3b140095550c9afbbb69b5d6fd9e814b9da82fad0b34e9fcbe56f1cb 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | now 3 | node_modules 4 | node_modules_NPM 5 | node_modules_YARN 6 | .DS_Store 7 | .history 8 | lcp.node 9 | 10 | .zed 11 | .flox/cache 12 | .flox/log 13 | .flox/run 14 | .flox/lib 15 | .flox/NPM_PREFIX 16 | .flox/NPM_CACHE 17 | -------------------------------------------------------------------------------- /misc/readers/reader-NYPL/manifest.appcache: -------------------------------------------------------------------------------- 1 | CACHE MANIFEST 2 | # timestamp Wed May 3 11:42:04 -0400 EDT 2017 3 | 4 | index.html 5 | sw.js 6 | webpub-viewer.js 7 | require.js 8 | fetch.js 9 | url.js 10 | promise.js 11 | main.css 12 | 13 | NETWORK: 14 | * 15 | -------------------------------------------------------------------------------- /misc/readers/reader-HADRIEN/overflow.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 2em; 3 | overflow: paged-x; 4 | overflow: -webkit-paged-x; 5 | overflow: -o-paged-x; 6 | } 7 | 8 | /*nav.publication { 9 | display: none; 10 | }*/ 11 | 12 | ::-webkit-scrollbar { 13 | display: none; 14 | } -------------------------------------------------------------------------------- /misc/json-schema/lcp/README.md: -------------------------------------------------------------------------------- 1 | From: 2 | 3 | https://github.com/readium/lcp-specs/tree/master/schema 4 | 5 | https://github.com/readium/lcp-specs/commit/e53a44315e699130c56a800fbc1a210541962cc1 6 | 7 | https://github.com/readium/lcp-specs/tree/e53a44315e699130c56a800fbc1a210541962cc1/schema 8 | 9 | https://github.com/readium/lcp-specs/compare/e53a44315e699130c56a800fbc1a210541962cc1...master 10 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // ==LICENSE-BEGIN== 2 | // Copyright 2017 European Digital Reading Lab. All rights reserved. 3 | // Licensed to the Readium Foundation under one or more contributor license agreements. 4 | // Use of this source code is governed by a BSD-style license 5 | // that can be found in the LICENSE file exposed on Github (readium) in the project repository. 6 | // ==LICENSE-END== 7 | 8 | export { Server } from "./http/server"; 9 | -------------------------------------------------------------------------------- /misc/json-schema/webpub-manifest/README.md: -------------------------------------------------------------------------------- 1 | From: 2 | 3 | https://github.com/readium/webpub-manifest/tree/master/schema 4 | 5 | https://github.com/readium/webpub-manifest/commit/03d7681cf1ff689bad76efaabc9c77423296a94c 6 | 7 | https://github.com/readium/webpub-manifest/tree/03d7681cf1ff689bad76efaabc9c77423296a94c/schema 8 | 9 | https://github.com/readium/webpub-manifest/compare/03d7681cf1ff689bad76efaabc9c77423296a94c...master 10 | -------------------------------------------------------------------------------- /package-scripts-build.cson: -------------------------------------------------------------------------------- 1 | 'prebuild:#ECMASCRIPT#': ' 2 | npm run lint && 3 | npm run clean 4 | ' 5 | 6 | 'build:#ECMASCRIPT#': ' 7 | npm run transpile:typescript:#ECMASCRIPT# 8 | ' 9 | 10 | 'postbuild:#ECMASCRIPT#': ' 11 | npm run afterdist && 12 | npm run bundle:#ECMASCRIPT# 13 | ' 14 | 15 | 'transpile:typescript:#ECMASCRIPT#': ' 16 | tsc --pretty -p "./tsconfigs/tsconfig-#ECMASCRIPT#-all.json" 17 | ' 18 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root=true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = crlf lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | 9 | [*.{md}] 10 | indent_style = ignore 11 | indent_size = ignore 12 | 13 | [*.{js,ts}] 14 | indent_style = space 15 | indent_size = 4 16 | 17 | [*.{yml,json}] 18 | indent_style = space 19 | indent_size = 2 20 | 21 | [*.{html,css,scss}] 22 | indent_style = space 23 | indent_size = 2 24 | -------------------------------------------------------------------------------- /misc/json-schema/opds/README.md: -------------------------------------------------------------------------------- 1 | From: 2 | 3 | https://github.com/opds-community/drafts/pull/38 4 | 5 | https://github.com/opds-community/drafts/tree/master/schema 6 | 7 | https://github.com/opds-community/drafts/commit/4b060f11684590f40d6293523d2d25ab343a6f51 8 | 9 | https://github.com/opds-community/drafts/tree/4b060f11684590f40d6293523d2d25ab343a6f51/schema 10 | 11 | https://github.com/opds-community/drafts/compare/4b060f11684590f40d6293523d2d25ab343a6f51...master 12 | -------------------------------------------------------------------------------- /misc/json-schema/opds/acquisition-object.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "https://drafts.opds.io/schema/acquisition-object.schema.json", 4 | "title": "OPDS Acquisition Object", 5 | "type": "object", 6 | "properties": { 7 | "type": { 8 | "type": "string" 9 | }, 10 | "child": { 11 | "type": "array", 12 | "items": { 13 | "$ref": "acquisition-object.schema.json" 14 | } 15 | } 16 | }, 17 | "required": [ 18 | "type" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/declarations.d.ts: -------------------------------------------------------------------------------- 1 | // ==LICENSE-BEGIN== 2 | // Copyright 2017 European Digital Reading Lab. All rights reserved. 3 | // Licensed to the Readium Foundation under one or more contributor license agreements. 4 | // Use of this source code is governed by a BSD-style license 5 | // that can be found in the LICENSE file exposed on Github (readium) in the project repository. 6 | // ==LICENSE-END== 7 | 8 | // declare module "*"; 9 | 10 | declare module "css2json"; 11 | declare module "json-markup"; 12 | declare module "filehound"; 13 | declare module "selfsigned"; 14 | declare module "dot-prop"; 15 | -------------------------------------------------------------------------------- /.vscode/keybindings.json: -------------------------------------------------------------------------------- 1 | // Place your key bindings in this file to overwrite the defaults 2 | [ 3 | { 4 | "key": "shift+alt+b", 5 | "command": "workbench.action.tasks.runTask", 6 | "args": "typescript" 7 | }, 8 | { 9 | "key": "shift+ctrl+alt+b", 10 | "command": "workbench.action.tasks.runTask", 11 | "args": "lintbuild" 12 | }, 13 | { 14 | "key": "shift+alt+s", 15 | "command": "sortImports.sort" 16 | }, 17 | { 18 | "key": "shift+ctrl+alt+s", 19 | "command": "macros.sortImports" 20 | } 21 | ] 22 | -------------------------------------------------------------------------------- /misc/json-schema/webpub-manifest/subject.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "https://readium.org/webpub-manifest/schema/subject.schema.json", 4 | "title": "Subject", 5 | "anyOf": [ 6 | { 7 | "type": "string" 8 | }, 9 | { 10 | "type": "array", 11 | "items": { 12 | "anyOf": [ 13 | { 14 | "type": "string" 15 | }, 16 | { 17 | "$ref": "subject-object.schema.json" 18 | } 19 | ] 20 | } 21 | }, 22 | { 23 | "$ref": "subject-object.schema.json" 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /misc/json-schema/webpub-manifest/contributor.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "https://readium.org/webpub-manifest/schema/contributor.schema.json", 4 | "title": "Contributor", 5 | "anyOf": [ 6 | { 7 | "type": "string" 8 | }, 9 | { 10 | "type": "array", 11 | "items": { 12 | "anyOf": [ 13 | { 14 | "type": "string" 15 | }, 16 | { 17 | "$ref": "contributor-object.schema.json" 18 | } 19 | ] 20 | } 21 | }, 22 | { 23 | "$ref": "contributor-object.schema.json" 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /misc/json-schema/webpub-manifest/extensions/epub/metadata.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "https://readium.org/webpub-manifest/schema/extensions/epub/metadata.schema.json", 4 | "title": "EPUB Extension - Metadata", 5 | "type": "object", 6 | "properties": { 7 | "presentation": { 8 | "type": "object", 9 | "properties": { 10 | "layout": { 11 | "description": "Hints how the layout of the resource should be presented", 12 | "type": "string", 13 | "enum": [ 14 | "fixed", 15 | "reflowable" 16 | ] 17 | } 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | docker version 4 | 5 | docker info 6 | 7 | docker build --progress=plain -f ./Dockerfile -t streamer-docker-image . 8 | 9 | #docker image ls 10 | 11 | #docker ps -a 12 | 13 | (docker stop streamer-docker-container || echo ok_stop) && echo _ok_stop 14 | 15 | #(docker kill streamer-docker-container || echo ok_kill) && echo _ok_kill 16 | 17 | (docker rm --force streamer-docker-container || echo ok_rm) && echo _ok_rm 18 | 19 | docker run -p 3000:3000 --name streamer-docker-container streamer-docker-image 20 | 21 | (docker stop streamer-docker-container || echo ok_stop) && echo _ok_stop 22 | docker logs -f streamer-docker-container 23 | docker logs streamer-docker-container 24 | -------------------------------------------------------------------------------- /tsconfigs/tsconfig-es5.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig-common", 3 | "compilerOptions": { 4 | "target": "es5", 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "outDir": "../dist/es5/", 8 | "lib": [ 9 | "dom", 10 | "es2015", 11 | "dom.iterable" 12 | ], 13 | "paths": { 14 | "@r2-opds-js/*": [ 15 | "node_modules/r2-opds-js/dist/es5/src/*" 16 | ], 17 | "@r2-utils-js/*": [ 18 | "node_modules/r2-utils-js/dist/es5/src/*" 19 | ], 20 | "@r2-shared-js/*": [ 21 | "node_modules/r2-shared-js/dist/es5/src/*" 22 | ], 23 | "@r2-lcp-js/*": [ 24 | "node_modules/r2-lcp-js/dist/es5/src/*" 25 | ] 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /misc/json-schema/lcp/lcpdf.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "https://readium.org/lcp-specs/schema/lcpdf.schema.json", 4 | "title": "LCP for PDF", 5 | "type": "object", 6 | "allOf": [ 7 | { 8 | "$ref": "https://readium.org/webpub-manifest/schema/publication.schema.json" 9 | }, 10 | { 11 | "description": "The readingOrder specified in manifest.json must strictly reference one or more PDF documents", 12 | "properties": { 13 | "readingOrder": { 14 | "items": { 15 | "properties": { 16 | "type": { 17 | "const": "application/pdf" 18 | } 19 | } 20 | } 21 | } 22 | } 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /tsconfigs/tsconfig-es6-es2015.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig-common", 3 | "compilerOptions": { 4 | "target": "es2015", 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "outDir": "../dist/es6-es2015/", 8 | "lib": [ 9 | "dom", 10 | "es2015", 11 | "dom.iterable" 12 | ], 13 | "paths": { 14 | "@r2-opds-js/*": [ 15 | "node_modules/r2-opds-js/dist/es6-es2015/src/*" 16 | ], 17 | "@r2-utils-js/*": [ 18 | "node_modules/r2-utils-js/dist/es6-es2015/src/*" 19 | ], 20 | "@r2-shared-js/*": [ 21 | "node_modules/r2-shared-js/dist/es6-es2015/src/*" 22 | ], 23 | "@r2-lcp-js/*": [ 24 | "node_modules/r2-lcp-js/dist/es6-es2015/src/*" 25 | ] 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tsconfigs/tsconfig-es7-es2016.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig-common", 3 | "compilerOptions": { 4 | "target": "es2016", 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "outDir": "../dist/es7-es2016/", 8 | "lib": [ 9 | "dom", 10 | "es2015", 11 | "dom.iterable" 12 | ], 13 | "paths": { 14 | "@r2-opds-js/*": [ 15 | "node_modules/r2-opds-js/dist/es7-es2016/src/*" 16 | ], 17 | "@r2-utils-js/*": [ 18 | "node_modules/r2-utils-js/dist/es7-es2016/src/*" 19 | ], 20 | "@r2-shared-js/*": [ 21 | "node_modules/r2-shared-js/dist/es7-es2016/src/*" 22 | ], 23 | "@r2-lcp-js/*": [ 24 | "node_modules/r2-lcp-js/dist/es7-es2016/src/*" 25 | ] 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tsconfigs/tsconfig-es8-es2017.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig-common", 3 | "compilerOptions": { 4 | "target": "es2017", 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "outDir": "../dist/es8-es2017/", 8 | "lib": [ 9 | "dom", 10 | "es2015", 11 | "dom.iterable" 12 | ], 13 | "paths": { 14 | "@r2-opds-js/*": [ 15 | "node_modules/r2-opds-js/dist/es8-es2017/src/*" 16 | ], 17 | "@r2-utils-js/*": [ 18 | "node_modules/r2-utils-js/dist/es8-es2017/src/*" 19 | ], 20 | "@r2-shared-js/*": [ 21 | "node_modules/r2-shared-js/dist/es8-es2017/src/*" 22 | ], 23 | "@r2-lcp-js/*": [ 24 | "node_modules/r2-lcp-js/dist/es8-es2017/src/*" 25 | ] 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | esudo: false 2 | branches: 3 | only: 4 | - develop 5 | language: node_js 6 | node_js: 7 | - '12' 8 | before_install: 9 | - 'pwd && ls -als' 10 | - npm --global install npm@latest 11 | #- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.22.4 12 | #- 'export PATH="$HOME/.yarn/bin:$PATH"' 13 | #cache: 14 | # yarn: true 15 | install: 16 | - 'pwd && ls -als' 17 | before_script: 18 | - 'pwd && ls -als' 19 | #- 'yarn --version' 20 | #- 'yarn install --ignore-engines' 21 | - 'node --version' 22 | - 'npm --version' 23 | - 'npm ci' 24 | script: 25 | - npm run build 26 | #- 'npm run now' 27 | #- 'npx now alias --token=$NOW_TOKEN $(npx now ./now --no-clipboard --token=$NOW_TOKEN --public) readium2.now.sh' 28 | after_success: 29 | - 'pwd && ls -als' 30 | - 'ls dist' 31 | after_failure: 32 | - 'pwd && ls -als' 33 | -------------------------------------------------------------------------------- /misc/readers/reader-NYPL/sw.js: -------------------------------------------------------------------------------- 1 | var CACHE_NAME = "webpub-viewer"; 2 | self.addEventListener('activate', function () { 3 | self.clients.claim(); 4 | }); 5 | self.addEventListener('fetch', function (event) { 6 | // Response from the cache immediately if possible, but also fetch an update. 7 | var cachedOrFetchedResponse = self.caches.open(CACHE_NAME).then(function (cache) { 8 | return self.caches.match(event.request).then(function (cacheResponse) { 9 | var fetchPromise = self.fetch(event.request).then(function (fetchResponse) { 10 | cache.put(event.request, fetchResponse.clone()); 11 | return fetchResponse; 12 | }); 13 | return cacheResponse || fetchPromise; 14 | }); 15 | }); 16 | event.respondWith(cachedOrFetchedResponse); 17 | }); 18 | //# sourceMappingURL=sw.js.map -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20 2 | 3 | WORKDIR /streamer 4 | 5 | # rm -rf /streamer/* &&\ 6 | # RUN ls -alsR /streamer 7 | 8 | COPY ./.editor* /streamer/ 9 | COPY ./.eslint* /streamer/ 10 | COPY ./.npm* /streamer/ 11 | COPY ./.gitignore* /streamer/ 12 | COPY ./.prettier* /streamer/ 13 | COPY ./.slugignore* /streamer/ 14 | COPY ./package* /streamer/ 15 | COPY ./tsconfig* /streamer/ 16 | 17 | ADD ./LCP /streamer/LCP 18 | ADD ./.git /streamer/.git 19 | ADD ./misc /streamer/misc 20 | ADD ./src /streamer/src 21 | ADD ./tools /streamer/tools 22 | ADD ./tsconfigs /streamer/tsconfigs 23 | 24 | # RUN ls -alsR /streamer 25 | 26 | RUN arch &&\ 27 | uname &&\ 28 | npm install -g npm@10.x &&\ 29 | node --version &&\ 30 | npm --version 31 | 32 | RUN cd /streamer/ &&\ 33 | npm ci && npm run build 34 | 35 | EXPOSE 3000 36 | 37 | CMD ./node_modules/cross-env/src/bin/cross-env-shell.js "DEBUG=r2:* NODE_ENV=development STREAMER_WATCH=0 STREAMER_DISABLE_EXPIRY=1" node "./dist/es8-es2017/src/http/server-cli.js" ./misc/epubs/ 38 | -------------------------------------------------------------------------------- /misc/readers/reader-HADRIEN/columns.css: -------------------------------------------------------------------------------- 1 | html { 2 | position: absolute; 3 | left: 0; 4 | top: 0; 5 | bottom: 0; 6 | right: 0; 7 | } 8 | 9 | body { 10 | /* Note: 11 | Getting the margin right is tricky with this pagination method: 12 | the margin and padding absolutely needs to be reset for the columns to work 13 | right. 14 | If the reference document were loaded in an iframe then we get a level of 15 | seperation where this might not be necessary 16 | */ 17 | margin: 0 !important; 18 | padding: 0 !important; 19 | 20 | column-count: 1; 21 | column-gap: 0; 22 | column-width: auto; 23 | column-fill: auto; 24 | 25 | /* firefox still needs these to be prefixed */ 26 | -moz-column-count: 1; 27 | -moz-column-gap: 0; 28 | -moz-column-width: auto; 29 | -moz-column-fill: auto; 30 | 31 | /* safari needs this, 'auto' doesn't work for some reason */ 32 | -webkit-column-width: 1vw; 33 | 34 | height: 100%; 35 | } 36 | 37 | main { 38 | /* The margin that was there originally on the body. See the note above. */ 39 | margin: 2em; 40 | } 41 | -------------------------------------------------------------------------------- /misc/json-schema/webpub-manifest/language-map.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "https://readium.org/webpub-manifest/schema/language-map.schema.json", 4 | "title": "Language Map", 5 | "anyOf": [ 6 | { 7 | "type": "string" 8 | }, 9 | { 10 | "type": "object", 11 | "patternProperties": { 12 | "^((?(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang))|((?([A-Za-z]{2,3}(-(?[A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-(? 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /misc/json-schema/webpub-manifest/extensions/epub/subcollections.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "https://readium.org/webpub-manifest/schema/extensions/epub/subcollections.schema.json", 4 | "title": "EPUB Extension - Sub-Collections", 5 | "type": "object", 6 | "properties": { 7 | "pageList": { 8 | "type": "array", 9 | "items": { 10 | "$ref": "../../link.schema.json" 11 | } 12 | }, 13 | "landmarks": { 14 | "type": "array", 15 | "items": { 16 | "$ref": "../../link.schema.json" 17 | } 18 | }, 19 | "loa": { 20 | "type": "array", 21 | "items": { 22 | "$ref": "../../link.schema.json" 23 | } 24 | }, 25 | "loi": { 26 | "type": "array", 27 | "items": { 28 | "$ref": "../../link.schema.json" 29 | } 30 | }, 31 | "lot": { 32 | "type": "array", 33 | "items": { 34 | "$ref": "../../link.schema.json" 35 | } 36 | }, 37 | "lov": { 38 | "type": "array", 39 | "items": { 40 | "$ref": "../../link.schema.json" 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /misc/readers/reader-HADRIEN/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Web Publication Viewer 5 | 6 | 7 | 8 | 9 | 10 | 20 |
21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /misc/json-schema/opds/feed-metadata.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "https://drafts.opds.io/schema/feed-metadata.schema.json", 4 | "title": "OPDS Metadata", 5 | "type": "object", 6 | "properties": { 7 | "identifier": { 8 | "type": "string", 9 | "format": "uri" 10 | }, 11 | "@type": { 12 | "type": "string", 13 | "format": "uri" 14 | }, 15 | "title": { 16 | "type": [ 17 | "string", 18 | "array", 19 | "object" 20 | ] 21 | }, 22 | "subtitle": { 23 | "type": [ 24 | "string", 25 | "array", 26 | "object" 27 | ] 28 | }, 29 | "modified": { 30 | "type": "string", 31 | "format": "date-time" 32 | }, 33 | "description": { 34 | "type": "string" 35 | }, 36 | "itemsPerPage": { 37 | "type": "integer", 38 | "exclusiveMinimum": 0 39 | }, 40 | "currentPage": { 41 | "type": "integer", 42 | "exclusiveMinimum": 0 43 | }, 44 | "numberOfItems": { 45 | "type": "integer", 46 | "minimum": 0 47 | } 48 | }, 49 | "required": [ 50 | "title" 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "./node_modules/typescript/lib", 3 | "typescript.tsserver.trace": "off", 4 | "files.exclude": { 5 | "**/.git": true, 6 | "**/.DS_Store": true, 7 | "**/.vscode": false, 8 | "**/.history": true, 9 | "**/.github": true, 10 | "**/.gitignore": true, 11 | "**/.gitmodules": true, 12 | "**/.idea": true, 13 | "**/node_modules*": true, 14 | "**/bower_components": true 15 | }, 16 | "search.exclude": {}, 17 | "git.enabled": false, 18 | "telemetry.enableCrashReporter": false, 19 | "telemetry.enableTelemetry": false, 20 | "typescript.surveys.enabled": false, 21 | "editor.roundedSelection": false, 22 | "editor.wordWrap": "off", 23 | "editor.quickSuggestions": { 24 | "comments": "off", 25 | "strings": "off", 26 | "other": "off" 27 | }, 28 | "editor.autoClosingBrackets": "never", 29 | "editor.scrollBeyondLastLine": false, 30 | "editor.formatOnType": false, 31 | "editor.suggestOnTriggerCharacters": false, 32 | "editor.folding": false, 33 | "editor.renderWhitespace": "all", 34 | "editor.tabSize": 4, 35 | "editor.insertSpaces": true, 36 | "editor.minimap.enabled": false, 37 | "sort-imports.on-save": false, 38 | "importSorter.importStringConfiguration.quoteMark": "double", 39 | "importSorter.importStringConfiguration.trailingComma": "multiLine", 40 | "importSorter.sortConfiguration.importMembers.order": "lowercaseLast" 41 | } 42 | -------------------------------------------------------------------------------- /src/http/server-trailing-slash-redirect.ts: -------------------------------------------------------------------------------- 1 | // ==LICENSE-BEGIN== 2 | // Copyright 2017 European Digital Reading Lab. All rights reserved. 3 | // Licensed to the Readium Foundation under one or more contributor license agreements. 4 | // Use of this source code is governed by a BSD-style license 5 | // that can be found in the LICENSE file exposed on Github (readium) in the project repository. 6 | // ==LICENSE-END== 7 | 8 | import * as debug_ from "debug"; 9 | import * as express from "express"; 10 | 11 | const debug = debug_("r2:streamer#http/server-trailing-slash-redirect"); 12 | 13 | // https://github.com/avinoamr/connect-slashes 14 | export function trailingSlashRedirect(req: express.Request, res: express.Response, next: express.NextFunction) { 15 | 16 | const i = req.originalUrl.indexOf("?"); 17 | 18 | let pathWithoutQuery = req.originalUrl; 19 | if (i >= 0) { 20 | pathWithoutQuery = pathWithoutQuery.substr(0, i); 21 | } 22 | if (pathWithoutQuery.substr(-1) === "/" 23 | || pathWithoutQuery.indexOf(".") >= 0) { 24 | return next(); 25 | } 26 | 27 | let redirect = pathWithoutQuery + "/"; 28 | if (i >= 0) { 29 | redirect += req.originalUrl.substr(i); 30 | } 31 | 32 | // Note that the HTTP 301 redirect does not contain CORS headers 33 | // server.setResponseCORS(res); 34 | 35 | debug(`REDIRECT: ${req.originalUrl} ==> ${redirect}`); 36 | res.redirect(301, redirect); 37 | } 38 | -------------------------------------------------------------------------------- /misc/json-schema/webpub-manifest/subcollection.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "https://readium.org/webpub-manifest/schema/subcollection.schema.json", 4 | "title": "Core Collection Model", 5 | "anyOf": [ 6 | { 7 | "type": "object", 8 | "properties": { 9 | "metadata": { 10 | "type": "object" 11 | }, 12 | "links": { 13 | "type": "array", 14 | "items": { 15 | "$ref": "link.schema.json" 16 | } 17 | }, 18 | "additionalProperties": { 19 | "$ref": "subcollection.schema.json" 20 | } 21 | }, 22 | "required": [ 23 | "metadata", 24 | "links" 25 | ] 26 | }, 27 | { 28 | "type": "array", 29 | "items": { 30 | "anyOf": [ 31 | { 32 | "$ref": "link.schema.json" 33 | }, 34 | { 35 | "type": "object", 36 | "properties": { 37 | "metadata": { 38 | "type": "object" 39 | }, 40 | "links": { 41 | "type": "array", 42 | "items": { 43 | "$ref": "link.schema.json" 44 | } 45 | } 46 | }, 47 | "additionalProperties": { 48 | "$ref": "subcollection.schema.json" 49 | } 50 | } 51 | ] 52 | } 53 | } 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, Readium Foundation 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /misc/json-schema/opds/profile.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "https://drafts.opds.io/schema/profile.schema.json", 4 | "title": "OPDS Profile Document", 5 | "type": "object", 6 | "properties": { 7 | "name": { 8 | "description": "Given name for the user", 9 | "type": "string" 10 | }, 11 | "email": { 12 | "description": "Email address associated to the user", 13 | "type": "string", 14 | "format": "email" 15 | }, 16 | "links": { 17 | "type": "array", 18 | "items": { 19 | "$ref": "https://readium.org/webpub-manifest/schema/link.schema.json" 20 | }, 21 | "uniqueItems": true 22 | }, 23 | "loans": { 24 | "type": "object", 25 | "properties": { 26 | "total": { 27 | "description": "Number of loans allowed at any time for the users", 28 | "type": "integer", 29 | "minimum": 0 30 | }, 31 | "available": { 32 | "description": "Number of loans currently available to the user", 33 | "type": "integer", 34 | "minimum": 0 35 | } 36 | } 37 | }, 38 | "holds": { 39 | "type": "object", 40 | "properties": { 41 | "total": { 42 | "description": "Number of holds allowed at any time for the users", 43 | "type": "integer", 44 | "minimum": 0 45 | }, 46 | "available": { 47 | "description": "Number of holds currently available to the user", 48 | "type": "integer", 49 | "minimum": 0 50 | } 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /misc/json-schema/webpub-manifest/extensions/presentation/properties.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "https://readium.org/webpub-manifest/schema/extensions/presentation/properties.schema.json", 4 | "title": "Presentation Hints - Link Properties", 5 | "type": "object", 6 | "properties": { 7 | "clipped": { 8 | "description": "Specifies whether or not the parts of a linked resource that flow out of the viewport are clipped.", 9 | "type": "boolean" 10 | }, 11 | "fit": { 12 | "description": "Specifies constraints for the presentation of a linked resource within the viewport.", 13 | "type": "string", 14 | "enum": [ 15 | "contain", 16 | "cover", 17 | "width", 18 | "height" 19 | ] 20 | }, 21 | "orientation": { 22 | "description": "Suggested orientation for the device when displaying the linked resource.", 23 | "type": "string", 24 | "enum": [ 25 | "auto", 26 | "landscape", 27 | "portrait" 28 | ] 29 | }, 30 | "page": { 31 | "description": "Indicates how the linked resource should be displayed in a reading environment that displays synthetic spreads.", 32 | "type": "string", 33 | "enum": [ 34 | "left", 35 | "right", 36 | "center" 37 | ] 38 | }, 39 | "spread": { 40 | "description": "Indicates the condition to be met for the linked resource to be rendered within a synthetic spread.", 41 | "type": "string", 42 | "enum": [ 43 | "auto", 44 | "both", 45 | "none", 46 | "landscape" 47 | ] 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /misc/json-schema/webpub-manifest/subject-object.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "https://readium.org/webpub-manifest/schema/subject-object.schema.json", 4 | "title": "Subject Object", 5 | "type": "object", 6 | "properties": { 7 | "name": { 8 | "anyOf": [ 9 | { 10 | "type": "string" 11 | }, 12 | { 13 | "description": "The language in a language map must be a valid BCP 47 tag.", 14 | "type": "object", 15 | "patternProperties": { 16 | "^((?(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang))|((?([A-Za-z]{2,3}(-(?[A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-(? 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /misc/epubs/wasteland-otf-obf_LCP_dan.lcpl: -------------------------------------------------------------------------------- 1 | {"provider":"provider","id":"f327b94f-7e05-406e-8524-84ef729db700","issued":"2017-10-31T11:23:03.250720346Z","encryption":{"profile":"http://readium.org/lcp/basic-profile","content_key":{"algorithm":"http://www.w3.org/2001/04/xmlenc#aes256-cbc","encrypted_value":"YGlj0vldEzpiBYojGSaw7iSlXQ6PlXJv5gkD/+jRdloeekilc9B9CSKWf1n70s0lE1qeVJrZFZY6DsyyKZn4bQ=="},"user_key":{"algorithm":"http://www.w3.org/2001/04/xmlenc#sha256","text_hint":"Your nickname?","key_check":"EFjG/+kNf4jIBaPCzzn8+7h8b6obAF1vKCTvwLtAvmzw/bTM4n2NyvtyO+ZkXiiJcUFEkA/O5xexQ8EHb/SPTQ==","value":"7E8tuzsUAJVVDJr7u2m11v2egUudqC+tCzTp/L5W8cs="}},"links":[{"rel":"hint","href":"https://rcs-test.edrlab.org/10013/"},{"rel":"publication","href":"https://rcs-test.edrlab.org/repository/edrlab/e5458386-2465-46a7-b9c4-21d019d40c4e","type":"application/epub+zip","title":"wasteland","length":752692,"hash":"8e5ea4a9c12f65e33f4dccd25135d59ad4d1fda110dfc921aceab1c4f01bfab5"},{"rel":"status","href":"https://rcs-test.edrlab.org/10012/licenses/f327b94f-7e05-406e-8524-84ef729db700/status","type":"application/vnd.readium.license.status.v1.0+json"}],"user":{"id":"27d9c941-b02d-429f-b6ef-7028885870ac","email":"COVcOIMzecUMAq5kuDzqDhcmmpM90PzyONBJQWPPO6vHGgil05I8RndDBoOTzgoM","name":"qseGOArwuWjp4Q4TXepX9KHO6LgMoXtY/TXuVnY1FDg=","encrypted":["email","name"]},"rights":{"print":100,"copy":2048},"signature":{"certificate":"MIIFpTCCA42gAwIBAgIBATANBgkqhkiG9w0BAQsFADBnMQswCQYDVQQGEwJGUjEOMAwGA1UEBxMFUGFyaXMxDzANBgNVBAoTBkVEUkxhYjESMBAGA1UECxMJTENQIFRlc3RzMSMwIQYDVQQDExpFRFJMYWIgUmVhZGl1bSBMQ1AgdGVzdCBDQTAeFw0xNjAzMjUwMzM3MDBaFw0yNjAzMjMwNzM3MDBaMIGQMQswCQYDVQQGEwJGUjEOMAwGA1UEBxMFUGFyaXMxDzANBgNVBAoTBkVEUkxhYjESMBAGA1UECxMJTENQIFRlc3RzMSIwIAYDVQQDExlUZXN0IHByb3ZpZGVyIGNlcnRpZmljYXRlMSgwJgYJKoZIhvcNAQkBFhlsYXVyZW50LmxlbWV1ckBlZHJsYWIub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq/gFXdvKb+EOzsEkHcoSOcPQmNzivzf+9NOJcxWi1/BwuxqAAPv+4LKoLz89U1xx5TE1swL11BsEkIdVYrjl1RiYRa8YV4bb4xyMTm8lm39P16H1fG7Ep8yyoVuN6LT3WT2xHGp2jYU8I2nW78cyYApAWAuiMc3epeIOxC2mKgf1pGnaX9j5l/Rx8hhxULqoHIHpR8e1eVRC7tgAz4Oy5qeLxGoL4S+GK/11eRlDO37whAWaMRbPnJDqqi8Z0Beovf6jmdoUTJdcPZZ9kFdtPsWjPNNHDldPuJBtCd7lupc0K4pClJSqtJKyxs05Yeb1j7kbs/i3grdlUcxz0zOaPN1YzrzOO7GLEWUnIe+LwVXAeUseHedOexITyDQXXCqMoQw/BC6ApGzR0FynC6ojq98tStYGJAGbKBN/9p20CvYf4/hmPU3fFkImWguPIoeJT//0rz+nSynykeEVtORRIcdyOnX2rL03xxBW7qlTlUXOfQk5oLIWXBW9Z2Q63MPWi8jQhSI0jC12iEqCT54xKRHNWKr04at9pJL85M0bDCbBH/jJ+AIbVx02ewtXcWgWTgK9vgSPN5kRCwIGaV9PMS193KHfNpGqV45EKrfP8U2nvNDeyqLqAN5847ABSW7UmA5Kj/x5uGxIWu9MUKjZlT0FpepswFvMMo1InLHANMcCAwEAAaMyMDAwDAYDVR0TAQH/BAIwADALBgNVHQ8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggIBAEGAqzHsCbrfQwlWas3q66FG/xbiOYQxpngA4CZWKQzJJDyOFgWEihW+H6NlSIH8076srpIZByjEGXZfOku4NH4DGNOj6jQ9mEfEwbrvCoEVHQf5YXladXpKqZgEB2FKeJVjC7yplelBtjBpSo23zhG/o3/Bj7zRySL6gUCewn7z/DkxM6AshDE4HKQxjxp7stpESev+0VTL813WXvwzmucr94H1VPrasFyVzQHj4Ib+Id1OAmgfzst0vSZyX6bjAuiN9yrs7wze5cAYTaswWr7GAnAZ/r1Z3PiDp50qaGRhHqJ+lRAhihpFP+ZjsYWRqnxZnDzJkJ6RZAHi2a3VN8x5WhOUMTf3JZcFVheDmA4SaEjAZAHU8zUxx1Fstjc8GJcjTwWxCsVM2aREBKXAYDhPTVLRKt6PyQxB0GxjDZZSvGI9uXn6S5wvjuE4T2TUwbJeGHqJr4FNpXVQ2XNww+sV2QSiAwrlORm8HNXqavj4rqz1PkUySXJ6b7zbjZoiACq4C7zb70tRYDyCfLTYtaTL3UK2Sa9ePSl0Fe6QfcqlGjalrqOo4GI6oqbAIkIXocHHksbLx0mIMSEWQOax+DqXhsl8tNGVwa5EiUSy83Sc0LyYXoWA35q8dugbkeNnY94rNG/hYKeci1VHhyg4rqxEeVwfBx121JqQSs+hHGKt","value":"e3wmj17SDrCuqmDBJ7cR8FVam8v5Aw0LZXAaOCYCyxlX8fHIV+XKU6S4eullAL1jgDpVNFhjrPsj4cGbMrhmqdLvVN3RSuAUCBrRm10DbcIzBRFm8MLHJlsxeyIV/tnzmc4eRzD4ce/TFJ8CpBTRFY7Lj9XlABwtPLuaQfDOjS4w3k5rGLh4t1Vx7H6zzC84+VxMaZ8FRRWiYGLlz8u8oNb4MO2G9/n9ZjjdU9R/zyWnnTkhUApB055n+B06OFRC+3XpKjSvhH/j+QPT9y1zdKTEU8m6flxO+z/Xwmc4HtmoJsZMONm8Xchb/RhCU/2sRiaoBg7Go8MJxLDJH065Eq4D3lup8GBFpIMPTPuLGUNDBg4hunP+9IC/FXkVzxjzy1pLuEcgAM58nPN0eY/Xmg2h57mBO1Hfci5/6606LhrDIaRN8m0or7yqWG8JjrSR6tN3wo090CO4MrdV42oHEE0D9qI78ht0mx4EgdAD96fnIgcndXawq46qKt9zc4RcNEK2Om3XnWlh30ylmpv6kZR2V1B+ZVUFSt8Hdq3q7AKXFnDUUK73l6MOHE++KYLtD8gB5hIMYNZ41enIvDgn1aH30FzI4NTOcvehNbSe63mX23p17IGlvqqJLEWO9LXmHx1QJYFTfTpD/Zm6qajB9jnfLQH3lHQdc3ks1Cd41Fs=","algorithm":"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"}} 2 | -------------------------------------------------------------------------------- /src/http/server-version.ts: -------------------------------------------------------------------------------- 1 | // ==LICENSE-BEGIN== 2 | // Copyright 2017 European Digital Reading Lab. All rights reserved. 3 | // Licensed to the Readium Foundation under one or more contributor license agreements. 4 | // Use of this source code is governed by a BSD-style license 5 | // that can be found in the LICENSE file exposed on Github (readium) in the project repository. 6 | // ==LICENSE-END== 7 | 8 | import * as crypto from "crypto"; 9 | import * as css2json from "css2json"; 10 | import * as debug_ from "debug"; 11 | import * as express from "express"; 12 | import * as fs from "fs"; 13 | import * as jsonMarkup from "json-markup"; 14 | import * as path from "path"; 15 | 16 | import { 17 | IRequestPayloadExtension, IRequestQueryParams, _jsonPath, _show, 18 | } from "./request-ext"; 19 | import { Server } from "./server"; 20 | 21 | const debug = debug_("r2:streamer#http/server-version"); 22 | 23 | // https://github.com/mafintosh/json-markup/blob/master/style.css 24 | const jsonStyle = ` 25 | .json-markup { 26 | line-height: 17px; 27 | font-size: 13px; 28 | font-family: monospace; 29 | white-space: pre; 30 | } 31 | .json-markup-key { 32 | font-weight: bold; 33 | } 34 | .json-markup-bool { 35 | color: firebrick; 36 | } 37 | .json-markup-string { 38 | color: green; 39 | } 40 | .json-markup-null { 41 | color: gray; 42 | } 43 | .json-markup-number { 44 | color: blue; 45 | } 46 | `; 47 | 48 | // tslint:disable-next-line:variable-name 49 | export const serverVersion_PATH = "/version"; 50 | export function serverVersion(server: Server, topRouter: express.Application) { 51 | 52 | topRouter.get([serverVersion_PATH, serverVersion_PATH + "/" + _show + "{/:" + _jsonPath + "}"], 53 | (req: express.Request, res: express.Response) => { 54 | 55 | const reqparams = (req as IRequestPayloadExtension).params; 56 | 57 | const isShow = req.url.indexOf("/show") >= 0 || (req.query as IRequestQueryParams).show; 58 | if (!reqparams.jsonPath && (req.query as IRequestQueryParams).show) { 59 | reqparams.jsonPath = (req.query as IRequestQueryParams).show; 60 | } 61 | 62 | const gitRevJson = "../../../gitrev.json"; 63 | if (!fs.existsSync(path.resolve(path.join(__dirname, gitRevJson)))) { 64 | 65 | const err = "Missing Git rev JSON! "; 66 | debug(err + gitRevJson); 67 | res.status(500).send("

Internal Server Error

" 68 | + err + "

"); 69 | return; 70 | } 71 | 72 | // eslint-disable-next-line @typescript-eslint/no-var-requires,@typescript-eslint/no-require-imports 73 | const jsonObj = require(gitRevJson); 74 | // debug(jsonObj); 75 | 76 | if (isShow) { 77 | const jsonPretty = jsonMarkup(jsonObj, css2json(jsonStyle)); 78 | 79 | res.status(200).send("" + 80 | "

R2-STREAMER-JS VERSION INFO

" + 81 | "

" + jsonPretty + "

" + 82 | // "

" + jsonStr + "

" + 83 | // "

" + dumpStr + "

" + 84 | ""); 85 | } else { 86 | server.setResponseCORS(res); 87 | res.set("Content-Type", "application/json; charset=utf-8"); 88 | 89 | const jsonStr = JSON.stringify(jsonObj, null, " "); 90 | 91 | const checkSum = crypto.createHash("sha256"); 92 | checkSum.update(jsonStr); 93 | const hash = checkSum.digest("hex"); 94 | 95 | const match = req.header("If-None-Match"); 96 | if (match === hash) { 97 | debug("publications.json cache"); 98 | res.status(304); // StatusNotModified 99 | res.end(); 100 | return; 101 | } 102 | 103 | res.setHeader("ETag", hash); 104 | // server.setResponseCacheHeaders(res, true); 105 | 106 | res.status(200).send(jsonStr); 107 | } 108 | }); 109 | } 110 | -------------------------------------------------------------------------------- /misc/json-schema/lcp/status.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "https://readium.org/lcp-specs/schema/status.schema.json", 4 | "title": "Readium LCP Status Document", 5 | "type": "object", 6 | "properties": { 7 | "id": { 8 | "description": "Unique identifier for the License Document associated to the Status Document.", 9 | "type": "string" 10 | }, 11 | "status": { 12 | "description": "Current status of the License.", 13 | "type": "string", 14 | "enum": [ 15 | "ready", 16 | "active", 17 | "revoked", 18 | "returned", 19 | "cancelled", 20 | "expired" 21 | ] 22 | }, 23 | "message": { 24 | "description": "A message meant to be displayed to the User regarding the current status of the license.", 25 | "type": "string" 26 | }, 27 | "updated": { 28 | "type": "object", 29 | "properties": { 30 | "license": { 31 | "description": "Time and Date when the License Document was last updated", 32 | "type": "string", 33 | "format": "date-time" 34 | }, 35 | "status": { 36 | "description": "Time and Date when the Status Document was last updated", 37 | "type": "string", 38 | "format": "date-time" 39 | } 40 | }, 41 | "required": [ 42 | "license", 43 | "status" 44 | ] 45 | }, 46 | "links": { 47 | "type": "array", 48 | "items": { 49 | "$ref": "link.schema.json" 50 | }, 51 | "uniqueItems": true 52 | }, 53 | "potential_rights": { 54 | "type": "object", 55 | "properties": { 56 | "end": { 57 | "description": "Date and time when the license ends", 58 | "type": "string", 59 | "format": "date-time" 60 | } 61 | } 62 | }, 63 | "events": { 64 | "type": "array", 65 | "items": { 66 | "type": "object", 67 | "properties": { 68 | "type": { 69 | "description": "Identifies the type of event", 70 | "type": "string", 71 | "enum": [ 72 | "register", 73 | "renew", 74 | "return", 75 | "revoke", 76 | "cancel" 77 | ] 78 | }, 79 | "name": { 80 | "description": "Name of the client, as provided by the client during an interaction", 81 | "type": "string" 82 | }, 83 | "id": { 84 | "description": "Identifies the client, as provided by the client during an interaction", 85 | "type": "string" 86 | }, 87 | "timestamp": { 88 | "description": "Time and date when the event occurred", 89 | "type": "string", 90 | "format": "date-time" 91 | } 92 | } 93 | } 94 | } 95 | }, 96 | "required": [ 97 | "id", 98 | "status", 99 | "message", 100 | "updated", 101 | "links" 102 | ], 103 | "allOf": [ 104 | { 105 | "description": "An LCP Status Document MUST contain a link to a license", 106 | "type": "object", 107 | "properties": { 108 | "links": { 109 | "type": "array", 110 | "contains": { 111 | "type": "object", 112 | "properties": { 113 | "href": { 114 | "type": "string", 115 | "format": "uri" 116 | }, 117 | "rel": { 118 | "anyOf": [ 119 | { 120 | "type": "string", 121 | "const": "license" 122 | }, 123 | { 124 | "type": "array", 125 | "contains": { 126 | "type": "string", 127 | "const": "license" 128 | } 129 | } 130 | ] 131 | } 132 | } 133 | } 134 | } 135 | } 136 | } 137 | ] 138 | } -------------------------------------------------------------------------------- /src/http/server-root.ts: -------------------------------------------------------------------------------- 1 | // ==LICENSE-BEGIN== 2 | // Copyright 2017 European Digital Reading Lab. All rights reserved. 3 | // Licensed to the Readium Foundation under one or more contributor license agreements. 4 | // Use of this source code is governed by a BSD-style license 5 | // that can be found in the LICENSE file exposed on Github (readium) in the project repository. 6 | // ==LICENSE-END== 7 | 8 | import * as express from "express"; 9 | import { html as beautifyHtml } from "js-beautify"; 10 | import * as path from "path"; 11 | 12 | import { encodeURIComponent_RFC3986, isHTTP } from "@r2-utils-js/_utils/http/UrlUtils"; 13 | 14 | import { _show } from "./request-ext"; 15 | import { Server } from "./server"; 16 | import { serverLCPLSD_show_PATH } from "./server-lcp-lsd-show"; 17 | import { serverOPDS_browse_v1_PATH } from "./server-opds-browse-v1"; 18 | import { serverOPDS_browse_v2_PATH } from "./server-opds-browse-v2"; 19 | import { serverOPDS_convert_v1_to_v2_PATH } from "./server-opds-convert-v1-to-v2"; 20 | import { serverOPDS_local_feed_PATH } from "./server-opds-local-feed"; 21 | import { serverPub_PATH } from "./server-pub"; 22 | import { serverRemotePub_PATH } from "./server-url"; 23 | import { serverVersion_PATH } from "./server-version"; 24 | 25 | // import * as debug_ from "debug"; 26 | // const debug = debug_("r2:streamer#http/server-root"); 27 | 28 | // const IS_DEV = (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "dev"); 29 | 30 | export function serverRoot(server: Server, topRouter: express.Application) { 31 | 32 | topRouter.options(/(.*)/, (_req: express.Request, res: express.Response) => { 33 | // Express 4 -> 5 wildcard (new router / path-to-regexp parser) 34 | // topRouter.options("*", (_req: express.Request, res: express.Response) => { 35 | 36 | // console.log(req.url); 37 | 38 | // Object.keys(req.headers).forEach((header: string) => { 39 | // console.log(header + " => " + req.headers[header]); 40 | // }); 41 | 42 | server.setResponseCORS(res); 43 | 44 | const serverData = server.serverInfo(); 45 | if (serverData && serverData.trustKey && 46 | serverData.trustCheck && serverData.trustCheckIV) { 47 | 48 | res.setHeader("Access-Control-Allow-Headers", 49 | (res.getHeader("Access-Control-Allow-Headers") as string).toString() + 50 | ", X-" + serverData.trustCheck); // access-control-request-headers 51 | } 52 | 53 | // Object.keys(res.getHeaders()).forEach((header: string) => { 54 | // console.log(header + " => " + res.getHeaders()[header]); 55 | // }); 56 | 57 | res.status(200).end(); 58 | }); 59 | 60 | topRouter.get("/", (_req: express.Request, res: express.Response) => { 61 | 62 | const html = 63 | `\ 64 | 65 | 66 | 67 |

Local Publications

68 | ${server.getPublications().map((pub) => { 69 | const filePathBase64 = Buffer.from(pub).toString("base64"); 70 | return `\ 71 |

\ 72 | ${isHTTP(pub) ? pub : path.basename(pub)}\ 73 |

74 | `; 75 | }).join("")}\ 76 | ${server.disableOPDS ? "" : `\ 77 |

78 | See OPDS2 Feed (JSON) 79 |

80 | `}\ 81 |

Additional Services

82 | 83 |

Display Server Version

84 | 85 | ${server.disableRemotePubUrl ? "" : `\ 86 |

Load Remote Publication (HTTP URL)

87 |

Show LCP / LSD (HTTP URL)

88 | `}\ 89 | 90 | ${server.disableOPDS ? "" : `\ 91 |

Browse OPDS1 (XML/Atom) feed (HTTP URL)

92 |

Browse OPDS2 (JSON) feed (HTTP URL)

93 |

Convert OPDS v1 to v2 (HTTP URL)

94 | `}\ 95 | 96 | 97 | 98 | `; 99 | 100 | res.status(200).send(beautifyHtml(html)); 101 | }); 102 | } 103 | -------------------------------------------------------------------------------- /docs/encryption.md: -------------------------------------------------------------------------------- 1 | # Support for encrypted resources 2 | 3 | ## Obfuscated fonts 4 | 5 | Both IDPF and Adobe font de-obfuscation schemes are supported. 6 | 7 | ## LCP DRM 8 | 9 | ### Demonstration (LCP test/basic profile, not suitable for production/1.0 profile) 10 | 11 | Let's consider the SHA256 hash of the user passphrase (or more precisely, the SHA256 digest / hexadecimal representation of the binary buffer / byte array), and its subsequent base64 encoding. For example, user passphrase `dan` becomes `ec4f2dbb3b140095550c9afbbb69b5d6fd9e814b9da82fad0b34e9fcbe56f1cb` (SHA256), which becomes `ZWM0ZjJkYmIzYjE0MDA5NTU1MGM5YWZiYmI2OWI1ZDZmZDllODE0YjlkYTgyZmFkMGIzNGU5ZmNiZTU2ZjFjYg==` (base64). 12 | 13 | Now, let's consider the special URL syntax `http://domain.com/pub/*-{LCP_PASS}-*{PUB_ID}/manifest.json`, which references the "webpub manifest" of a particular publication identified by `{PUB_ID}`, and which passes the LCP base64-encoded passphrase into the request, denoted by the delimiters `*-` and `-*` around `{LCP_PASS}`. 14 | 15 | Once the above URL request is made, the server stores the LCP passphrase and utilises it for any subsequent requests to resources (e.g. assets such as CSS, HTML, etc.) that belong to the publication identified by `{PUB_ID}`. 16 | 17 | Note that this special URL syntax is implemented to enable LCP testing directly from vanilla web browsers. In a real-world application, the underlying programmatic API should be used instead. 18 | 19 | Let's look at a concrete example. First, let's reset the LCP passphrase using an incorrect value: 20 | 21 | https://streamer.edrlab.org/pub/*-YmQzZGFlNWZiOTFmODhhNGYwOTc4MjIyZGZkNThmNTlhMTI0MjU3Y2IwODE0ODYzODdjYmFlOWRmMTFmYjg3OQ%3D%3D-*L2FwcC9taXNjL2VwdWJzL3dhc3RlbGFuZC1vdGYtb2JmX0xDUF9kYW4uZXB1Yg%3D%3D/manifest.json/show/all 22 | 23 | Let's load the manifest without any LCP passphrase: 24 | 25 | https://streamer.edrlab.org/pub/L2FwcC9taXNjL2VwdWJzL3dhc3RlbGFuZC1vdGYtb2JmX0xDUF9kYW4uZXB1Yg%3D%3D/manifest.json/show/all 26 | 27 | Notice that we did not provide the LCP passphrase in the URL, yet the publication's cover image is displayed correctly. That is because cover images are not encrypted in LCP-protected publications. The same principle applies to the navigation document: 28 | 29 | https://streamer.edrlab.org/pub/L2FwcC9taXNjL2VwdWJzL3dhc3RlbGFuZC1vdGYtb2JmX0xDUF9kYW4uZXB1Yg%3D%3D/EPUB/wasteland-nav.xhtml?show=1 30 | 31 | Now let's load an encrypted page (it should fail): 32 | 33 | https://streamer.edrlab.org/pub/L2FwcC9taXNjL2VwdWJzL3dhc3RlbGFuZC1vdGYtb2JmX0xDUF9kYW4uZXB1Yg%3D%3D/EPUB/wasteland-content.xhtml?show=1 34 | 35 | Now let's set the correct passphrase ("dan"): 36 | 37 | https://streamer.edrlab.org/pub/*-ZWM0ZjJkYmIzYjE0MDA5NTU1MGM5YWZiYmI2OWI1ZDZmZDllODE0YjlkYTgyZmFkMGIzNGU5ZmNiZTU2ZjFjYg%3D%3D-*L2FwcC9taXNjL2VwdWJzL3dhc3RlbGFuZC1vdGYtb2JmX0xDUF9kYW4uZXB1Yg%3D%3D/manifest.json/show/all 38 | 39 | ...and let's load the encrypted page once again (it should succeed): 40 | 41 | https://streamer.edrlab.org/pub/L2FwcC9taXNjL2VwdWJzL3dhc3RlbGFuZC1vdGYtb2JmX0xDUF9kYW4uZXB1Yg%3D%3D/EPUB/wasteland-content.xhtml?show=1 42 | 43 | Note that in this example fonts are obfuscated (IDPF algorithm), therefore they are not encrypted via LCP. Also note that "streaming" of encrypted audio / video content is supported too, but test files are too large to demonstrate here (strictly-speaking, this is not "streaming", but rather: support for HTTP partial byte ranges, random access into the AES-256-CBC resource). 44 | 45 | ### Implementation status (TypeScript / JavaScript) 46 | 47 | Note that TypeScript / JavaScript implementation is just for demonstration purposes, it only supports the LCP profile test/basic (not production/1.0), and it only handles decryption (no license signature checking, no expiration date/time verification, no Certificate Revocation List processing, no support for LSD License Status Document, etc.) 48 | 49 | Currently, the Javascript decryption code utilizes the NodeJS native crypto implementation (which is based on OpenSSL). Performance is acceptable, but of course the private decryption key is not protected (it can easily be debugged / reverse-engineered). This architecture is therefore only suitable for the LCP basic/test profile. 50 | 51 | Support for LCP 1.0/production profile relies upon a native C++ module, which replaces the Javascript implementation. 52 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: "@typescript-eslint/parser", 3 | env: { 4 | node: true, 5 | browser: true, 6 | es6: true, 7 | es2020: true, 8 | }, 9 | parserOptions: { 10 | project: true, 11 | tsconfigRootDir: __dirname, 12 | ecmaVersion: 2020, 13 | sourceType: "module", 14 | ecmaFeatures: { 15 | jsx: true, 16 | }, 17 | }, 18 | extends: ["plugin:@typescript-eslint/recommended-type-checked", "prettier", "plugin:prettier/recommended"], 19 | plugins: [ 20 | // "unused-imports", 21 | "prettier", 22 | ], 23 | rules: { 24 | quotes: ["error", "double"], 25 | "comma-dangle": ["error", "always-multiline"], 26 | "eol-last": ["error", "always"], 27 | semi: ["error", "always"], 28 | 29 | "@typescript-eslint/no-unsafe-member-access": 0, 30 | "@typescript-eslint/no-unsafe-return": 0, 31 | "@typescript-eslint/no-unsafe-assignment": 0, 32 | "@typescript-eslint/no-unsafe-call": 0, 33 | "@typescript-eslint/no-unsafe-argument": 0, 34 | "@typescript-eslint/no-unnecessary-type-assertion": 0, 35 | "@typescript-eslint/restrict-template-expressions": 0, 36 | "@typescript-eslint/no-redundant-type-constituents": 0, 37 | "@typescript-eslint/no-base-to-string": 0, 38 | "@typescript-eslint/no-misused-promises": 0, 39 | "@typescript-eslint/require-await": 0, 40 | "@typescript-eslint/no-floating-promises": 0, 41 | "@typescript-eslint/unbound-method": 0, 42 | 43 | "@typescript-eslint/no-unsafe-enum-comparison": 0, 44 | "@typescript-eslint/restrict-plus-operands": 0, 45 | 46 | "no-unused-vars": 0, 47 | // "@typescript-eslint/no-unused-vars": 0, 48 | "@typescript-eslint/no-unused-vars": [ 49 | "error", 50 | { 51 | vars: "all", 52 | args: "all", 53 | argsIgnorePattern: "^_", 54 | varsIgnorePattern: "^_", 55 | caughtErrorsIgnorePattern: "^_", 56 | caughtErrors: "all", 57 | }, 58 | ], 59 | // "unused-imports/no-unused-imports": "error", 60 | // "unused-imports/no-unused-vars": [ 61 | // "error", 62 | // { 63 | // vars: "all", 64 | // args: "all", 65 | // argsIgnorePattern: "^_", 66 | // varsIgnorePattern: "^_", 67 | // caughtErrorsIgnorePattern: "^_", 68 | // caughtErrors: "all", 69 | // }, 70 | // ], 71 | 72 | // TODO ({} used as anonymous / generic object type) 73 | // "@typescript-eslint/ban-types": 0, 74 | 75 | // TODO (many any!!) 76 | // "@typescript-eslint/no-explicit-any": 0, 77 | 78 | // TODO (missing return types on functions) 79 | "@typescript-eslint/explicit-module-boundary-types": 0, 80 | // "@typescript-eslint/explicit-module-boundary-types": [ 81 | // "error", 82 | // { 83 | // allowArgumentsExplicitlyTypedAsAny: true, 84 | // allowDirectConstAssertionInArrowFunctions: true, 85 | // allowedNames: [], 86 | // allowHigherOrderFunctions: true, 87 | // allowTypedFunctionExpressions: true, 88 | // }, 89 | // ], 90 | 91 | // "@typescript-eslint/explicit-function-return-type": 0, 92 | // "@typescript-eslint/explicit-function-return-type": [ 93 | // "error", 94 | // { 95 | // allowExpressions: true, 96 | // allowTypedFunctionExpressions: true, 97 | // }, 98 | // ], 99 | 100 | "prettier/prettier": "error", 101 | }, 102 | // overrides: [ 103 | // { 104 | // files: ["*.ts", "*.tsx"], 105 | // rules: { 106 | // "@typescript-eslint/explicit-function-return-type": [ 107 | // "error", 108 | // { 109 | // allowExpressions: true, 110 | // }, 111 | // ], 112 | // }, 113 | // }, 114 | // ], 115 | }; 116 | -------------------------------------------------------------------------------- /misc/json-schema/webpub-manifest/link.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "https://readium.org/webpub-manifest/schema/link.schema.json", 4 | "title": "Link Object for the Readium Web Publication Manifest", 5 | "type": "object", 6 | "properties": { 7 | "href": { 8 | "description": "URI or URI template of the linked resource", 9 | "type": "string" 10 | }, 11 | "type": { 12 | "description": "MIME type of the linked resource", 13 | "type": "string" 14 | }, 15 | "templated": { 16 | "description": "Indicates that a URI template is used in href", 17 | "type": "boolean" 18 | }, 19 | "title": { 20 | "description": "Title of the linked resource", 21 | "type": "string" 22 | }, 23 | "rel": { 24 | "description": "Relation between the linked resource and its containing collection", 25 | "type": [ 26 | "string", 27 | "array" 28 | ], 29 | "items": { 30 | "type": "string" 31 | } 32 | }, 33 | "properties": { 34 | "description": "Properties associated to the linked resource", 35 | "allOf": [ 36 | { "$ref": "https://drafts.opds.io/schema/properties.schema.json" }, 37 | { "$ref": "extensions/epub/properties.schema.json" }, 38 | { "$ref": "extensions/presentation/properties.schema.json" } 39 | ] 40 | }, 41 | "height": { 42 | "description": "Height of the linked resource in pixels", 43 | "type": "integer", 44 | "exclusiveMinimum": 0 45 | }, 46 | "width": { 47 | "description": "Width of the linked resource in pixels", 48 | "type": "integer", 49 | "exclusiveMinimum": 0 50 | }, 51 | "bitrate": { 52 | "description": "Bitrate of the linked resource in kbps", 53 | "type": "number", 54 | "exclusiveMinimum": 0 55 | }, 56 | "duration": { 57 | "description": "Length of the linked resource in seconds", 58 | "type": "number", 59 | "exclusiveMinimum": 0 60 | }, 61 | "language": { 62 | "description": "Expected language of the linked resource", 63 | "type": [ 64 | "string", 65 | "array" 66 | ], 67 | "items": { 68 | "type": "string", 69 | "pattern": "^((?(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang))|((?([A-Za-z]{2,3}(-(?[A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-(?"; 47 | html += ""; 48 | 49 | html += "

Publication URL

"; 50 | 51 | html += "
" + 52 | "" + 53 | "
"; 54 | 55 | html += ""; 56 | 57 | res.status(200).send(html); 58 | }); 59 | 60 | routerUrl.param("urlEncoded", (req, _res, next, value, _name) => { 61 | // Express 4 -> 5 wildcard (new router / path-to-regexp parser) 62 | if (typeof value !== "string") { 63 | if (Array.isArray(value)) { 64 | value = value.join("/"); 65 | } 66 | } 67 | (req as IRequestPayloadExtension).urlEncoded = value; 68 | next(); 69 | }); 70 | 71 | // RegExp.escape() NOT AVAILABLE in NodeJS yet new RegExp(RegExp.escape()) 72 | // routerUrl.get(new RegExp(regexpEscape("/:" + _urlEncoded) + "(.*)"), (req: express.Request, res: express.Response) => { 73 | routerUrl.get("/*" + _urlEncoded, (req: express.Request, res: express.Response) => { 74 | // Express 4 -> 5 wildcard (new router / path-to-regexp parser) 75 | // routerUrl.get("/:" + _urlEncoded + "(*)", (req: express.Request, res: express.Response) => { 76 | 77 | const reqparams = (req as IRequestPayloadExtension).params; 78 | 79 | if (!reqparams.urlEncoded) { 80 | reqparams.urlEncoded = (req as IRequestPayloadExtension).urlEncoded; 81 | } 82 | if (reqparams.urlEncoded && typeof reqparams.urlEncoded !== "string") { 83 | if (Array.isArray(reqparams.urlEncoded)) { 84 | reqparams.urlEncoded = (reqparams.urlEncoded as []).join("/"); 85 | } 86 | } 87 | 88 | const urlDecoded = reqparams.urlEncoded; 89 | // if (urlDecoded.substr(-1) === "/") { 90 | // urlDecoded = urlDecoded.substr(0, urlDecoded.length - 1); 91 | // } 92 | debug(urlDecoded); 93 | 94 | const urlDecodedBase64 = encodeURIComponent_RFC3986(Buffer.from(urlDecoded).toString("base64")); 95 | const redirect = req.originalUrl.substr(0, 96 | req.originalUrl.indexOf(serverRemotePub_PATH + "/")) 97 | + "/pub/" + urlDecodedBase64 + "/"; 98 | 99 | // No need for CORS with this URL redirect (HTML page lists available services) 100 | // server.setResponseCORS(res); 101 | 102 | debug(`REDIRECT: ${req.originalUrl} ==> ${redirect}`); 103 | res.redirect(301, redirect); 104 | }); 105 | 106 | topRouter.use(serverRemotePub_PATH, routerUrl); 107 | } 108 | -------------------------------------------------------------------------------- /misc/json-schema/webpub-manifest/metadata.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "https://readium.org/webpub-manifest/schema/metadata.schema.json", 4 | "title": "Metadata", 5 | "type": "object", 6 | "properties": { 7 | "identifier": { 8 | "type": "string", 9 | "format": "uri" 10 | }, 11 | "@type": { 12 | "type": "string", 13 | "format": "uri" 14 | }, 15 | "conformsTo": { 16 | "type": [ 17 | "string", 18 | "array" 19 | ], 20 | "format": "uri", 21 | "items": { 22 | "type": "string", 23 | "format": "uri" 24 | } 25 | }, 26 | "title": { 27 | "$ref": "language-map.schema.json" 28 | }, 29 | "sortAs": { 30 | "$ref": "language-map.schema.json" 31 | }, 32 | "subtitle": { 33 | "$ref": "language-map.schema.json" 34 | }, 35 | "accessibility": { 36 | "$ref": "a11y.schema.json" 37 | }, 38 | "modified": { 39 | "type": "string", 40 | "format": "date-time" 41 | }, 42 | "published": { 43 | "type": "string", 44 | "anyOf": [ 45 | { 46 | "format": "date" 47 | }, 48 | { 49 | "format": "date-time" 50 | } 51 | ] 52 | }, 53 | "language": { 54 | "description": "The language must be a valid BCP 47 tag.", 55 | "type": [ 56 | "string", 57 | "array" 58 | ], 59 | "items": { 60 | "type": "string", 61 | "pattern": "^((?(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang))|((?([A-Za-z]{2,3}(-(?[A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-(?