1 | # NodeJS / TypeScript Readium-2 "streamer"
|
2 |
|
3 | NodeJS implementation (written in TypeScript) and HTTP micro-services (Express middleware) for https://github.com/readium/architecture/tree/master/streamer
|
4 |
|
5 | [![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](/LICENSE)
|
6 |
|
7 | ## Build status
|
8 |
|
9 | [![NPM](https://img.shields.io/npm/v/dita-streamer-js.svg)](https://www.npmjs.com/package/dita-streamer-js) [![David](https://david-dm.org/d-i-t-a/dita-streamer-js/status.svg)](https://david-dm.org/d-i-t-a/dita-streamer-js) [![Travis](https://travis-ci.org/d-i-t-a/dita-streamer-js.svg?branch=develop)](https://travis-ci.org/d-i-t-a/dita-streamer-js) [![Heroku](https://img.shields.io/badge/app-Heroku-blue.svg)](https://readium2.herokuapp.com)
|
10 |
|
11 | [Changelog](/CHANGELOG.md)
|
12 |
|
13 | ## Prerequisites
|
14 |
|
15 | 1) https://nodejs.org NodeJS >= 8, NPM >= 5 (check with command line `node --version` and `npm --version`)
|
16 | 2) OPTIONAL: https://yarnpkg.com Yarn >= 1.0 (check with command line `yarn --version`)
|
17 |
|
18 | ## GitHub repository
|
19 |
|
20 | https://github.com/d-i-t-a/dita-streamer-js
|
21 |
|
22 | There is no [github.io](https://d-i-t-a.github.io/dita-streamer-js) site for this project (no [gh-pages](https://github.com/d-i-t-a/dita-streamer-js/tree/gh-pages) branch).
|
23 |
|
24 | ## NPM package
|
25 |
|
26 | https://www.npmjs.com/package/dita-streamer-js
|
27 |
|
28 | Command line install:
|
29 |
|
30 | `npm install dita-streamer-js`
|
31 | OR
|
32 | `yarn add dita-streamer-js`
|
33 |
|
34 | ...or manually add in your `package.json`:
|
35 | ```json
|
36 | "dependencies": {
|
37 | "dita-streamer-js": "latest"
|
38 | }
|
39 | ```
|
40 |
|
41 | The JavaScript code distributed in the NPM package is usable as-is (no transpilation required), as it is automatically-generated from the TypeScript source.
|
42 |
|
43 | Several ECMAScript flavours are provided out-of-the-box: ES5, ES6-2015, ES7-2016, ES8-2017:
|
44 |
|
45 | https://unpkg.com/dita-streamer-js/dist/
|
46 |
|
47 | (alternatively, GitHub mirror with semantic-versioning release tags: https://github.com/d-i-t-a/dita-streamer-js-dist/tree/develop/dist/ )
|
48 |
|
49 | The JavaScript code is not bundled, and it uses `require()` statement for imports (NodeJS style).
|
50 |
|
51 | More information about NodeJS compatibility:
|
52 |
|
53 | http://node.green
|
54 |
|
55 | Note that web-browser Javascript is currently not supported (only NodeJS runtimes).
|
56 |
|
57 | The type definitions (aka "typings") are included as `*.d.ts` files in `./node_modules/dita-streamer-js/dist/**`, so this package can be used directly in a TypeScript project.
|
58 |
|
59 | Example usage:
|
60 |
|
61 | ```javascript
|
62 | // from the index file
|
63 | import { Server } from "dita-streamer-js";
|
64 |
|
65 | // ES5 import (assuming node_modules/dita-streamer-js/):
|
66 | import { Server } from "dita-streamer-js/dist/es5/src/http/server";
|
67 |
|
68 | // ... or alternatively using a convenient path alias in the TypeScript config (+ WebPack etc.):
|
69 | import { Server } from "@dita-streamer-js/http/server";
|
70 | ```
|
71 |
|
72 | ## Dependencies
|
73 |
|
74 | https://david-dm.org/d-i-t-a/dita-streamer-js
|
75 |
|
76 | A [package-lock.json](https://github.com/d-i-t-a/dita-streamer-js/blob/develop/package-lock.json) is provided (modern NPM replacement for `npm-shrinkwrap.json`).
|
77 |
|
78 | A [yarn.lock](https://github.com/d-i-t-a/dita-streamer-js/blob/develop/yarn.lock) file is currently *not* provided at the root of the source tree.
|
79 |
|
80 | ## Continuous Integration
|
81 |
|
82 | https://travis-ci.org/d-i-t-a/dita-streamer-js
|
83 |
|
84 | TravisCI builds are triggered automatically at every Git "push" in the `develop` branch.
|
85 |
|
86 | ## Live demos
|
87 |
|
88 | A test server app (not production-ready) is automatically deployed at **Heroku**, at every Git "push" in the `develop` branch:
|
89 |
|
90 | https://readium2.herokuapp.com
|
91 |
|
92 | A mirror app used to be deployed at **Now.sh** (https://readium2.now.sh), but this is not available anymore due to technical reasons (i.e. the new Now deployment model does not support our custom Express server)
|
93 |
|
94 | Deployed servers run NodeJS 10+, and the apps are based on the ES8-2017 code transpiled from TypeScript.
|
95 |
|
96 | HTTP CORS headers are served to allow cross-origin / remote API requests.
|
97 |
|
98 | ## Version(s), Git revision(s)
|
99 |
|
100 | NPM package (latest published):
|
101 |
|
102 | https://unpkg.com/dita-streamer-js/dist/gitrev.json
|
103 |
|
104 | Alternatively, GitHub mirror with semantic-versioning release tags:
|
105 |
|
106 | https://raw.githack.com/edrd-i-t-a/dita-streamer-js-dist/develop/dist/gitrev.json
|
107 |
|
108 | Heroku app (latest deployed):
|
109 |
|
110 | https://readium2.herokuapp.com/version
|
111 |
|
112 | ## Developer quick start
|
113 |
|
114 | Command line steps (NPM, but similar with YARN):
|
115 |
|
116 | 1) `cd dita-streamer-js`
|
117 | 2) `git status` (please ensure there are no local changes, especially in `package-lock.json` and the dependency versions in `package.json`)
|
118 | 3) `rm -rf node_modules` (to start from a clean slate)
|
119 | 4) `npm install`, or alternatively `npm ci` (both commands initialize the `node_modules` tree of package dependencies, based on the strict `package-lock.json` definition)
|
120 | 5) `npm run build:all` (invoke the main build script: clean, lint, compile)
|
121 | 6) `ls dist` (that's the build output which gets published as NPM package)
|
122 | 7) `npm run server-debug -- PATH_TO_EPUB_OR_DIR " -1"` (ES8-2017 dist, path is relative or absolute, -1 means no limits for HTTP header prefetch Links)
|
123 | 8) or: `npm run start -- 99` (ES6-2015 dist, default `./misc/epubs` folder, the 99 value overrides the default maximum number of HTTP header prefetch Links)
|
124 |
|
125 | ## Documentation
|
126 |
|
127 | ### Basic usage
|
128 |
|
129 | ```javascript
|
130 | // ES5 import (assuming node_modules/dita-streamer-js/):
|
131 | import { Server } from "dita-streamer-js/dist/es5/src/http/server";
|
132 |
|
133 | // ... or alternatively using a convenient path alias in the TypeScript config (+ WebPack etc.):
|
134 | import { Server } from "@dita-streamer-js/http/server";
|
135 |
|
136 | // Constructor parameter is optional:
|
137 | // disableDecryption: true
|
138 | // disableOPDS
|
139 | // disableReaders: true
|
140 | // disableRemotePubUrl: true to deactivate
|
141 | const server = new Server({
|
142 | disableDecryption: false, // deactivates the decryption of encrypted resources (Readium LCP).
|
143 | disableOPDS: true, // deactivates the HTTP routes for the OPDS "micro services" (browser, converter)
|
144 | disableReaders: true, // deactivates the built-in "readers" for ReadiumWebPubManifest (HTTP static host / route).
|
145 | disableRemotePubUrl: true, // deactivates the HTTP route for loading a remote publication.
|
146 | maxPrefetchLinks: 5, // Link HTTP header, with rel = prefetch, see server.ts MAX_PREFETCH_LINKS (default = 10)
|
147 | readers: [ // the example readers that will show up on the publication page
|
148 | {
|
149 | title: "My Example",
|
150 | getUrl: manifestUrl => `/myreader?url=${manifestUrl}`
|
151 | }
|
152 | ]
|
153 | });
|
154 |
|
155 | // First parameter: port number, zero means default (3000),
|
156 | // unless specified via the environment variable `PORT` (process.env.PORT).
|
157 | // Tip: the NPM package `portfinder` can be used to automatically find an available port number.
|
158 | const url = await server.start(3000, false);
|
159 |
|
160 | // Second constructor parameter: if true, HTTPS instead of HTTP, using a randomly-generated self-signed certificate.
|
161 | // Also validates encrypted HTTP header during request-request cycles, so should only be used in runtime
|
162 | // contexts where the client side has access to the private encryption key (i.e. Electron app, see r2-navigator-js)
|
163 | console.log(server.isSecured()); // false
|
164 |
|
165 | // A client app that is capable of setting HTTP headers for every request originating from content webviews
|
166 | // can obtain the special encrypted header using this function:
|
167 | // (as used internally by the Electron-based `r2-navigator-js` component to secure the transport layer)
|
168 | const nameValuePair = server.getSecureHTTPHeader(url + "/PATH_TO_RESOURCE");
|
169 | console.log(nameValuePair.name);
|
170 | console.log(nameValuePair.value);
|
171 |
|
172 | // http://127.0.0.1:3000
|
173 | // Note that ports 80 and 443 (HTTPS) are always implicit (ommitted).
|
174 | console.log(url);
|
175 |
|
176 | // `serverInfo.urlScheme` ("http")
|
177 | // `serverInfo.urlHost` ("127.0.0.1")
|
178 | // `serverInfo.urlPort` (3000)
|
179 | console.log(server.serverInfo());
|
180 |
|
181 | // Calls `uncachePublications()` (see below)
|
182 | server.stop();
|
183 |
|
184 | console.log(server.isStarted()); // false
|
185 | ```
|
186 |
|
187 | To serve a `/robots.txt` file that completely disables search robots:
|
188 |
|
189 | ```javascript
|
190 | // Call this before `server.start()`
|
191 | server.preventRobots();
|
192 | ```
|
193 |
|
194 | To add custom HTTP routes:
|
195 |
|
196 | ```javascript
|
197 | // Call these before `server.start()`.
|
198 | // They are equivalent to `app.use()` and `app.get()`, where `app` is the underlying Express instance:
|
199 |
|
200 | server.expressUse("/static-files", express.static("/path/to/files", {
|
201 | dotfiles: "ignore",
|
202 | etag: true,
|
203 | fallthrough: false,
|
204 | immutable: true,
|
205 | index: false,
|
206 | maxAge: "1d",
|
207 | redirect: false,
|
208 | }));
|
209 |
|
210 | server.expressGet(["/hello.html"], (req: express.Request, res: express.Response) => {
|
211 |
|
212 | // Optionally, to add permissive CORS headers to the HTTP response
|
213 | server.setResponseCORS(res);
|
214 |
|
215 | res.status(200).send("<html><body>Hello</body></html>");
|
216 | });
|
217 | ```
|
218 |
|
219 | To register publications references (local filesystem paths) inside the internal server state
|
220 | (which is used to create the OPDS2 feed, see below):
|
221 |
|
222 | ```javascript
|
223 | // This can be called before or after `server.start()`:
|
224 |
|
225 | // the returned array contains URL routes to the ReadiumWebPubManifests,
|
226 | // e.g. `/pub/ID/manifest.json`, where `ID` is the base64 encoding of the registered path.
|
227 | // Note that the returned base64 URL path components are already URI-encoded (escaped).
|
228 | // (`=` and `/` are typically problematic edge-cases)
|
229 | const publicationURLs = server.addPublications(["/path/to/book.epub"]);
|
230 |
|
231 | // ...then:
|
232 | const publicationPaths = server.getPublications(); // ["/path/to/book.epub"]
|
233 |
|
234 | // ...and (calls `uncachePublication()`, see below):
|
235 | const publicationURLs = server.removePublications(["/path/to/book.epub"]);
|
236 | ```
|
237 |
|
238 | To get the OPDS2 feed for the currently registered publications:
|
239 |
|
240 | ```javascript
|
241 | // This launches a potentially time-consuming Node process that scans (loads) each registered Publication,
|
242 | // and stores the generated OPDS2 feed inside a temporary filesystem location.
|
243 | // So this returns `undefined` at the first call, and the client must invoke the function again later.
|
244 | // Note that both `addPublications()` and `removePublications()` clear the OPDS2 feed entirely,
|
245 | // requiring its subsequent re-generation (full scan of registered publication paths).
|
246 | // (poor design, but at this stage really just an OPDS2 demo without real use-case)
|
247 | const opds2 = server.publicationsOPDS();
|
248 | ```
|
249 |
|
250 | To actually load+parse a publication reference (local filesystem path) into a ReadiumWebPubManifest
|
251 | Publication instance, stored in the server's state:
|
252 |
|
253 | ```javascript
|
254 | // The Publication object model is defined in `r2-shared-js`
|
255 | const publication = await server.loadOrGetCachedPublication("/path/to/book.epub");
|
256 |
|
257 | // The above is basically a lazy-loader that checks the cache before loading+parsing a publication,
|
258 | // equivalent to:
|
259 | const publication = server.cachedPublication("/path/to/book.epub");
|
260 | if (!publication) {
|
261 | publication = ...; // load and parse "/path/to/book.epub"
|
262 | server.cachePublication("/path/to/book.epub", publication);
|
263 | }
|
264 |
|
265 | console.log(server.isPublicationCached("/path/to/book.epub")); // true
|
266 |
|
267 | // see also:
|
268 | // (calls `publication.freeDestroy()` to cleanup allocated objects in the Publication,
|
269 | // particularly the file handle to the underlying zip/EPUB/CBZ file)
|
270 | server.uncachePublication("/path/to/book.epub");
|
271 | server.uncachePublications();
|
272 | ```
|
273 |
|
274 | Note that HTTP/remote publications URLs can be loaded into the server's cache
|
275 | and subsequently served by the streamer without prior registration via `addPublications()`.
|
276 | However, publications from the local filesytem will only be served when registered,
|
277 | even if they are cached (in other words, the HTTP route is disabled when the publication is non-registered).
|
278 |
|
279 | ### HTTP API (built-in routes / micro-services)
|
280 |
|
281 | [docs/http.md](/docs/http.md)
|
282 |
|
283 | ### Support for remote publications
|
284 |
|
285 | [docs/remote-epub.md](/docs/remote-epub.md)
|
286 |
|
287 | ### Support for OPDS feeds
|
288 |
|
289 | [docs/opds.md](/docs/opds.md)
|
290 |
|
291 | ### Support for encrypted content
|
292 |
|
293 | [docs/encryption.md](/docs/encryption.md)
|