Skip to content

Commit

Permalink
Update devserver to use express, Mustache and serve PDFJS-enabled PDFs
Browse files Browse the repository at this point in the history
- Rewrite dev server as `express` app
- Add `mustache-express` as template engine
- Restructure document directories; add PDFs
- Add `pdfjs-init.js` script for embedding PDFJS with H client
- Update linting config to ignore static scripts
  • Loading branch information
lyzadanger committed Aug 25, 2020
1 parent bbd365a commit da9c1d7
Show file tree
Hide file tree
Showing 385 changed files with 61,934 additions and 274 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ build/**
**/vendor/**/*.js
**/coverage/**
docs/_build/*
dev-server/static/**/*.js
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
build/
/build/
node_modules/
coverage/
docs/_build/
Expand Down
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
build/
coverage/
docs/
dev-server/static/*
7 changes: 6 additions & 1 deletion dev-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ This directory contains the Hypothesis client's development server that is
started by `make dev`. It hosts the assets that make up the Hypothesis client
from the `build/` directory as well as content for testing the Hypothesis client.

Test documents in the `documents/` directory are available at
Test documents in the `documents/html` directory are available at
`localhost:<port>/document/<filename-without-extension>`,
e.g. `documents/foo.mustache` would be available at `localhost:<port>/document/foo`.

PDFs in the `documents/pdf` directory are available at
`localhost:port/pdf/<filename-without-extension>` and will be served with the
PDF JS viewer as well as the embedded client.

Mustache-templated HTML documents may use `{{{ hypothesisScript }}}` to inject
the client application as configured by `templates/client-config.js.mustache`.
PDFs may be dropped in as-is.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Binary file added dev-server/documents/pdf/budlong.pdf
Binary file not shown.
Binary file added dev-server/documents/pdf/widdershins.pdf
Binary file not shown.
113 changes: 0 additions & 113 deletions dev-server/index.js

This file was deleted.

114 changes: 114 additions & 0 deletions dev-server/serve-dev.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
'use strict';
/* eslint-env node */

const fs = require('fs');
const path = require('path');

const express = require('express');
const log = require('fancy-log');
const mustacheExpress = require('mustache-express');
const Mustache = require('mustache');

const { createServer, useSsl } = require('./create-server');

const HTML_PATH = `${__dirname}/documents/html/`;
const PDF_PATH = `${__dirname}/documents/pdf/`;
const TEMPLATE_PATH = `${__dirname}/templates/`;

/**
* @typedef Config
* @property {string} clientUrl - The URL of the client's boot script
*/

/**
* Generate `<script>` content for client configuration and injection
*
* @param {string} clientUrl
* @return {string}
*/
function renderConfig(clientUrl) {
const scriptTemplate = fs.readFileSync(
`${TEMPLATE_PATH}client-config.js.mustache`,
'utf-8'
);
return Mustache.render(scriptTemplate, { clientUrl });
}

/**
* Build context for rendering templates in the defined views directory. This
* is dead simple at present but could be extended as needs grow.
*
* @param {Config} config
*/
function templateContext(config) {
return {
hypothesisScript: renderConfig(config.clientUrl),
};
}

/**
* An HTTP server which serves test documents with the development client embedded.
*
* @param {number} port - The port that the test server should listen on.
* @param {Config} config - Config for the server
*/
function serveDev(port, config) {
const app = express();

app.engine('mustache', mustacheExpress());
app.set('view engine', 'mustache');
app.set('views', [HTML_PATH, path.join(__dirname, '/templates')]);

app.use(express.static(path.join(__dirname, 'static')));

// Serve static PDF files out of the PDF directory, but serve under
// `/pdf-source/` — these are needed by PDF JS viewer
app.use('/pdf-source', express.static(PDF_PATH));

// Enable CORS for assets so that cross-origin font loading works.
app.use(function (req, res, next) {
res.append('Access-Control-Allow-Origin', '*');
res.append('Access-Control-Allow-Methods', 'GET');
next();
});

// Landing page
app.get('/', (req, res) => {
res.render('index', templateContext(config));
});

// Serve HTML documents with injected client script
app.get('/document/:document', (req, res, next) => {
if (fs.existsSync(`${HTML_PATH}${req.params.document}.mustache`)) {
res.render(req.params.document, templateContext(config));
} else {
next();
}
});

// Serve PDF documents with PDFJS viewer and client script
app.get('/pdf/:pdf', (req, res, next) => {
if (fs.existsSync(`${PDF_PATH}${req.params.pdf}.pdf`)) {
const fullUrl = `${req.protocol}://${req.hostname}:${port}${req.originalUrl}`;
res.render('pdfjs-viewer', {
documentUrl: fullUrl, // The URL that annotations are associated with
url: `/pdf-source/${req.params.pdf}.pdf`, // The URL for the PDF source file
clientUrl: config.clientUrl,
});
} else {
next();
}
});

// Nothing else matches: this is a 404
app.use((req, res) => {
res.render('404', templateContext(config));
});

createServer(app).listen(port, () => {
const scheme = useSsl ? 'https' : 'http';
log(`Dev web server started at ${scheme}://localhost:${port}/`);
});
}

module.exports = serveDev;

This file was deleted.

This file was deleted.

1 change: 0 additions & 1 deletion dev-server/static/scripts/pdfjs-2/web/viewer.js.map

This file was deleted.

82 changes: 82 additions & 0 deletions dev-server/static/scripts/pdfjs-init.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
'use strict';

// Note: This file is not transpiled.
// Listen for `webviewerloaded` event to configure the viewer after its files
// have been loaded but before it is initialized.
document.addEventListener('webviewerloaded', () => {
const appOptions = window.PDFViewerApplicationOptions;
const app = window.PDFViewerApplication;

// Ensure that PDF.js viewer events such as "documentloaded" are dispatched
// to the DOM. The client relies on this.
appOptions.set('eventBusDispatchToDOM', true);

// Disable preferences support, as otherwise this will result in `eventBusDispatchToDOM`
// being overridden with the default value of `false`.
appOptions.set('disablePreferences', true);

// Prevent loading of default viewer PDF.
appOptions.set('defaultUrl', '');

// Read configuration rendered into template as global vars.
const documentUrl = window.DOCUMENT_URL;
const url = window.PDF_URL;
const clientEmbedUrl = window.CLIENT_URL;

// Wait for the PDF viewer to be fully initialized and then load the Hypothesis client.
//
// This is required because the client currently assumes that `PDFViewerApplication`
// is fully initialized when it loads. Note that "fully initialized" only means
// that the PDF viewer application's components have been initialized. The
// PDF itself will still be loading, and the client will wait for that to
// complete before fetching annotations.
//
const pdfjsInitialized = new Promise(resolve => {
// Poll `app.initialized` as there doesn't appear to be an event that
// we can listen to.
const timer = setInterval(function () {
if (app.initialized) {
clearTimeout(timer);
resolve();
}
}, 20);
});

pdfjsInitialized.then(() => {
// Prevent PDF.js' `Promise` polyfill, if it was used, from being
// overwritten by the one that ships with Hypothesis (both from core-js).
//
// See https://github.com/hypothesis/via/issues/81#issuecomment-531121534
if (
typeof Promise === 'function' &&
typeof PromiseRejectionEvent === 'undefined'
) {
window.PromiseRejectionEvent = function FakePromiseRejectionEvent() {
// core-js doesn't actually use this, it just tests for `typeof PromiseRejectionEvent`
console.warn('Tried to construct fake `PromiseRejectionEvent`');
};
}

// Load the Hypothesis client.
const embedScript = document.createElement('script');
embedScript.src = clientEmbedUrl;
document.body.appendChild(embedScript);

// Load the PDF specified in the URL.
//
// This is done after the viewer components are initialized to avoid some
// race conditions in `PDFViewerApplication` if the PDF finishes loading
// (eg. from the HTTP cache) before the viewer is fully initialized.
//
// See https://github.com/mozilla/pdf.js/wiki/Frequently-Asked-Questions#can-i-specify-a-different-pdf-in-the-default-viewer
// and https://github.com/mozilla/pdf.js/issues/10435#issuecomment-452706770
app.open({
// Load PDF through Via to work around CORS restrictions.
url: url,

// Make sure `PDFViewerApplication.url` returns the original URL, as this
// is the URL associated with annotations.
originalUrl: documentUrl,
});
});
});
File renamed without changes.
Loading

0 comments on commit da9c1d7

Please sign in to comment.