Migration to `thread-keeper`

pull/8/head
Matteo Cargnelutti 2022-11-22 14:08:28 -05:00
rodzic 5184207301
commit b0a98452d9
23 zmienionych plików z 522 dodań i 170 usunięć

Wyświetl plik

@ -1,6 +1,6 @@
# Save Your Threads 📚
# "Save Your Threads" - thread-keeper 📚
High-fidelity capture of Twitter threads as sealed PDFs: [archive.social](https://archive.social).
High-fidelity capture of Twitter threads as sealed PDFs: [social.perma.cc](https://social.perma.cc).
An experiment of the [Harvard Library Innovation Lab](https://lil.law.harvard.edu).
@ -45,6 +45,7 @@ A `brewfile` is available. Run `brew bundle` to install machine-level dependenci
## Local development
### Getting started
Run the following commands to initialize the project and start the development server.
```bash
@ -55,14 +56,15 @@ npm run generate-dev-cert # Will generate a certificate for self-signing PDFs. F
npm run dev # Starts the development server on port 3000
```
### Access keys
Create an access key to test with:
```
```bash
$ uuidgen
BB67BBC4-1F4B-4353-8E6D-9927A10F4509
```
and then add the key to `app/data/access-keys.json`:
And then add the key to `app/data/access-keys.json`:
```json
{
@ -70,6 +72,27 @@ and then add the key to `app/data/access-keys.json`:
}
```
### Certificates history
The _"Signatures Verification Page"_ page lists the certificates that were used for signing PDFs with the app. You may provide that history by creating two files under `/data`:
- `signing-certs-history.json`
- `timestamping-certs-history.json`
Expected format:
```json
[
{
"from": "2022-11-18 13:07:56 UTC",
"to": "present",
"domain": "domain.ext",
"info": "https://...",
"cert": "https://..."
},
...
]
```
[☝️ Back to summary](#summary)
---
@ -105,3 +128,4 @@ npm run docgen
Generates JSDoc-based code documentation under `/docs`.
[☝️ Back to summary](#summary)

Wyświetl plik

@ -1,5 +1,5 @@
/**
* archive.social
* thread-keeper
* @module const.js
* @author The Harvard Library Innovation Lab
* @license MIT

Wyświetl plik

@ -1,5 +1,5 @@
/**
* archive.social
* thread-keeper
* @module server.js
* @author The Harvard Library Innovation Lab
* @license MIT
@ -8,7 +8,7 @@ import assert from "assert";
import nunjucks from "nunjucks";
import { AccessKeys, SuccessLog, TwitterCapture } from "./utils/index.js";
import { AccessKeys, CertsHistory, SuccessLog, TwitterCapture } from "./utils/index.js";
import {
TEMPLATES_PATH,
STATIC_PATH,
@ -65,6 +65,27 @@ async function index(request, reply) {
.send(html);
}
/**
* [GET] /check
* Shows the "check" page /check form. Loads certificates history files in the process.
* Assumes `fastify` is in scope.
*
* @param {fastify.FastifyRequest} request
* @param {fastify.FastifyReply} reply
* @returns {Promise<fastify.FastifyReply>}
*/
async function check(request, reply) {
const html = nunjucks.render(`${TEMPLATES_PATH}check.njk`, {
signingCertsHistory: CertsHistory.load("signing"),
timestampsCertsHistory: CertsHistory.load("timestamping")
});
return reply
.code(200)
.header('Content-Type', 'text/html; charset=utf-8')
.send(html);
}
/**
* [POST] `/`
* Processes a request to capture a `twitter.com` url.
@ -230,6 +251,9 @@ export default async function (fastify, opts) {
// [GET] /
fastify.get('/', index);
// [GET] /check
fastify.get('/check', check);
// [POST] /
fastify.post('/', capture);

Wyświetl plik

@ -0,0 +1,83 @@
//------------------------------------------------------------------------------
// "check-pdf" dialog logic
//------------------------------------------------------------------------------
const checkPdfDialog = document.querySelector("dialog#check-pdf");
// Open / close based on hash
window.addEventListener('hashchange', (event) => {
const newURL = new URL(event.newURL);
const oldURL = new URL(event.oldURL);
if (newURL.hash === "#check-pdf") {
checkPdfDialog.showModal();
}
if (oldURL.hash === "#check-pdf" && oldURL !== newURL) {
checkPdfDialog.close();
}
});
// Open on load if hash already present
if (window.location.hash === "#check-pdf") {
checkPdfDialog.showModal();
}
// Clear output on file change
checkPdfDialog.querySelector("input").addEventListener("change", () => {
checkPdfDialog.querySelector("textarea").value = `Click on "Check" to proceed.\n`;
});
// Check file on click on "Check"
checkPdfDialog.querySelector("button").addEventListener("click", async(e) => {
let hash = "";
const output = checkPdfDialog.querySelector("textarea");
output.value = "";
try {
const data = await checkPdfDialog.querySelector("input[type='file']").files[0].arrayBuffer();
// Generate hash and convert it to hex and then base 64.
// Was of tremendous help: https://stackoverflow.com/questions/23190056/hex-to-base64-converter-for-javascript
hash = await (async() => {
const hash = await crypto.subtle.digest('SHA-512', data);
const walkable = Array.from(new Uint8Array(hash));
const toHex = walkable.map((b) => b.toString(16).padStart(2, "0")).join("");
return btoa(
toHex
.match(/\w{2}/g)
.map(function (a) {
return String.fromCharCode(parseInt(a, 16));
})
.join("")
);
})();
}
catch(err) {
output.value = `Could not calculate hash of the file selected, if any.\n`;
}
try {
const response = await fetch(`/api/v1/hashes/check/${encodeURIComponent(hash)}`);
output.value += `SHA-512 hash\n---${hash}\n---\n`;
switch (response.status) {
case 200:
output.value += `This hash is CONFIRMED to be present in archive.social's logs.\n`;
break;
case 404:
output.value += `This hash was NOT FOUND in archive.social's logs.\n`;
break;
default:
throw new Error(response.status);
break;
}
}
catch(err) {
console.log(`/api/v1/hashes/check/<hash> responded with HTTP ${err}`);
output.value += `An error occurred while trying to verify file.`;
}
});

Wyświetl plik

@ -70,6 +70,7 @@ body > main h1 {
body > main h2 {
font-size: 1.65rem;
letter-spacing: -0.05rem;
margin-top: 0.25rem;
}
@media (max-width: 769px) {
@ -87,12 +88,12 @@ body > main p {
line-height: 1.45rem;
}
/* INDEX - GENERAL*/
body#index > main {
/* GENERAL LAYOUT */
body > main {
min-height: 100vh;
}
body#index > main button {
body > main button {
font-family: Garamond, serif;
font-size: 0.85rem;
border: 0px;
@ -106,12 +107,22 @@ body#index > main button {
transition: background-color 0.35s ease-in-out;
}
body#index > main button:hover {
body > main button:hover {
background-color: var(--main-color-);
}
/* INDEX - HEADER */
body#index > main > img:first-of-type {
body > main hr {
margin: auto;
width: 45ch;
border: 0px;
border-top: 1px solid var(--main-color-----);
margin-top: 1.5rem;
margin-bottom: 1.5rem;
}
/* GENERAL - HEADER */
body > main > img:first-of-type {
display: block;
margin: auto;
margin-bottom: 1rem;
@ -119,10 +130,109 @@ body#index > main > img:first-of-type {
max-height: 62.5vh;
}
body#index > main header {
body > main header {
text-align: center;
}
/* GENERAL - DIALOGS */
body > main dialog {
display: none;
}
body > main dialog[open] {
display: unset;
margin: auto;
border: 0px;
width: 50ch;
max-width: 90%;
padding: 2rem;
border: 0.5rem solid var(--main-color);
}
body > main dialog[open] p {
font-size: 0.9rem;
line-height: 1.45rem;
margin-bottom: 1rem;
}
body > main dialog[open]::backdrop {
background-color: rgba(0,0,0,0.65);
overflow: hidden;
}
body > main dialog#check-pdf button {
margin-right: 1rem;
}
body > main dialog#check-pdf input {
display: block;
width: 100%;
margin-bottom: 1rem;
cursor: pointer;
}
body > main dialog#check-pdf textarea {
display: block;
width: 100%;
margin-bottom: 1rem;
cursor: pointer;
height: 6rem;
padding: 0.5rem;
font-size: 0.65rem;
}
/* GENERAL - TEXT CONTENT */
body > main section {
max-width: 55ch;
margin: auto;
margin-bottom: 1.5rem;
}
body > main section p {
margin-bottom: 0.5rem;
font-size: 0.95rem;
line-height: 1.45rem;
}
body > main section p.spaced {
margin-top: 1rem;
margin-bottom: 1rem;
}
@media (max-width: 769px) {
body > main section {
max-width: unset;
}
}
/* GENERAL - FOOTER */
body > main footer {
max-width: 45ch;
margin: auto;
text-align: center;
padding-top: 2rem;
margin-bottom: 1.5rem;
border-top: 1px solid var(--main-color-----);
}
body > main footer p {
margin-bottom: 0.5rem;
font-size: 0.95rem;
line-height: 1.3rem;
}
@media (max-width: 769px) {
body > main footer {
max-width: unset;
}
}
body > main footer img {
width: 10rem;
display: inline-block;
max-width: 50%;
}
/* INDEX - FORM */
body#index > main form {
max-width: 45ch;
@ -191,98 +301,3 @@ body#index > main form fieldset.submit span label {
cursor: pointer;
padding-left: 0.25rem;
}
/* INDEX - DIALOG */
body#index > main dialog {
display: none;
}
body#index > main dialog[open] {
display: unset;
margin: auto;
border: 0px;
width: 50ch;
max-width: 90%;
padding: 2rem;
border: 0.5rem solid var(--main-color);
}
body#index > main dialog[open] p {
font-size: 0.9rem;
line-height: 1.45rem;
margin-bottom: 1rem;
}
body#index > main dialog[open]::backdrop {
background-color: rgba(0,0,0,0.65);
overflow: hidden;
}
body#index > main dialog#check-pdf button {
margin-right: 1rem;
}
body#index > main dialog#check-pdf input {
display: block;
width: 100%;
margin-bottom: 1rem;
cursor: pointer;
}
body#index > main dialog#check-pdf textarea {
display: block;
width: 100%;
margin-bottom: 1rem;
cursor: pointer;
height: 6rem;
padding: 0.5rem;
font-size: 0.65rem;
}
/* INDEX - EXPLAINER */
body#index > main section {
max-width: 55ch;
margin: auto;
margin-bottom: 1.5rem;
/*border-top: 1px solid var(--main-color-----);*/
}
body#index > main section p {
margin-bottom: 0.5rem;
font-size: 0.95rem;
line-height: 1.45rem;
}
@media (max-width: 769px) {
body#index > main section {
max-width: unset;
}
}
/* INDEX - FOOTER */
body#index > main footer {
max-width: 45ch;
margin: auto;
text-align: center;
padding-top: 2rem;
margin-bottom: 1.5rem;
border-top: 1px solid var(--main-color-----);
}
body#index > main footer p {
margin-bottom: 0.5rem;
font-size: 0.95rem;
line-height: 1.3rem;
}
@media (max-width: 769px) {
body#index > main footer {
max-width: unset;
}
}
body#index > main footer img {
width: 10rem;
display: inline-block;
max-width: 50%;
}

Wyświetl plik

@ -0,0 +1,80 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Signature Checking Page</title>
<meta name="description" content="How do I check that a PDF came from you?">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/static/index.css">
<script type="module" src="/static/check.js"></script>
</head>
<body id="check">
<main>
{% include "./modules/header-pic.njk" %}
<header>
<h1>Signature Verification Page</h1>
<p>How do I check that a PDF came from you?</p>
</header>
<hr>
<section>
<h2>Fingerprint check</h2>
<p>You may use <a href="#check-pdf">this form</a> to confirm whether a PDF file really came from us by checking its fingerprint against our logs.</p>
<h2>Signing Certificates History</h2>
<p>Below is the list of signing certificates this website has used to sign PDFs. You may download the associated PEM file to allow Acrobat Reader to verify signatures.
{% for entry in signingCertsHistory %}
<p class="spaced">
From <strong>{{ entry.from }}</strong> to <strong>{{ entry.to }}</strong>, we've signed documents using a certificate for: <strong>{{ entry.domain }}</strong>.<br/>
See: <a href="{{ entry.info }}">certificate transparency records for this certificate</a>, <a href="{{ entry.cert }}">certificate file</a>.
</p>
{% endfor %}
<h2>Timestamping Certificates History</h2>
<p>Below is the list of timestamping certificates this website has used to sign PDFs. You may download the associated CER file to allow Acrobat Reader to verify signatures.
{% for entry in timestampsCertsHistory %}
<p class="spaced">
From <strong>{{ entry.from }}</strong> to <strong>{{ entry.to }}</strong>, we've signed documents using the following timestamping server: <strong>{{ entry.domain }}</strong>.<br/>
See: <a href="{{ entry.info }}">certificate transparency records for this certificate</a>, <a href="{{ entry.cert }}">certificate file</a>.
</p>
{% endfor %}
<p><a href="/">Go back to the homepage</a></p>
</section>
<dialog id="check-pdf">
<h2>Check a PDF</h2>
<p>This tool verifies that a given .pdf file was created by archive.social by matching its fingerprint against our logs.</p>
<input
aria-label="File input for the PDF file to check"
type="file"
accept=".pdf"
name="check-pdf-file"/>
<textarea disabled>Results will be displayed here.</textarea>
<button>Check PDF</button>
<a href="#">Close</a>
</dialog>
{% include "./modules/footer.njk" %}
</main>
</body>
</html>

Wyświetl plik

@ -2,7 +2,7 @@
<html lang="en">
<head>
<title>Save Your Threads</title>
<meta name="description" content="High-fidelity capture of Twitter threads as sealed PDFs. An experiment of the Harvard Library Innovation Lab.">
<meta name="description" content="High-fidelity capture of Twitter threads as sealed PDFs on social.perma.cc. An experiment of the Harvard Library Innovation Lab.">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@ -14,17 +14,13 @@
<body id="index">
<main>
<!-- HEADER -->
<img
src="/static/ocean-of-books.svg"
alt="Illustration of a person falling in an ocean of books.">
{% include "./modules/header-pic.njk" %}
<header>
<h1>Save Your Threads</h1>
<p>High-fidelity capture of Twitter threads as sealed PDFs.</p>
</header>
<!-- MAIN FORM -->
<form action="/" target="_blank" method="POST" enctype="application/x-www-form-urlencoded">
<fieldset>
@ -79,7 +75,7 @@
{% endif %}
{% if errorReason and errorReason == "TOO-MANY-CAPTURES-TOTAL" %}
<p>archive.social has received too many requests. Please retry in a minute.</p>
<p>Too many requests. Please retry in a minute.</p>
{% endif %}
{% if errorReason and errorReason == "TOO-MANY-CAPTURES-USER" %}
@ -87,31 +83,13 @@
{% endif %}
{% if errorReason and errorReason == "CAPTURE-ISSUE" %}
<p>archive.social encountered an issue during the capture process itself. Please try again later.</p>
<p>An issue occured during the capture process itself. Please try again later.</p>
{% endif %}
<button>Ok</button>
</dialog>
{% endif %}
<dialog id="check-pdf">
<h2>Check a PDF</h2>
<p>This tool verifies that a given .pdf file was created by archive.social by matching its fingerprint against our logs.</p>
<input
aria-label="File input for the PDF file to check"
type="file"
accept=".pdf"
name="check-pdf-file"/>
<textarea disabled>Results will be displayed here.</textarea>
<button>Check PDF</button>
<a href="#">Close</a>
</dialog>
<section>
<h2>What is this?</h2>
@ -119,13 +97,13 @@
<h2>Who can use it?</h2>
<p>To use our website <a href="https://ocb.to/archive-social-form">you'll need to contact us</a> for an API key. We're currently only able to share a limited number with people like journalists, internet scholars, and archivists. But you can also use <a href="https://github.com/harvard-lil/archive.social">our open source software</a> to stand up an archive server of your own, and share it with your friends.</p>
<p>To use our website <a href="https://ocb.to/archive-social-form">you'll need to contact us</a> for an API key. We're currently only able to share a limited number with people like journalists, internet scholars, and archivists. But you can also use <a href="https://github.com/harvard-lil/thread-keeper">our open source software</a> to stand up an archive server of your own, and share it with your friends.</p>
<h2>Why make a PDF archiving tool for Twitter?</h2>
<p>There are lots of screenshots of Twitter threads going around. Some are real, some are fake. You can't tell who made them, or when they were made.</p>
<p>PDFs let us apply document signatures and timestamps so anyone can check, in the future, that a PDF you download with "archive.social" really came from the Harvard Library Innovation Lab and hasn't been edited. PDFs also let us bundle in additional media as attachments. Each signed PDF currently includes all images in the page (so you can see full size images that are cropped in the PDF view) and the primary video on the page if any.</p>
<p>PDFs let us apply document signatures and timestamps so anyone can check, in the future, that a PDF you download with this site really came from the Harvard Library Innovation Lab and hasn't been edited. PDFs also let us bundle in additional media as attachments. Each signed PDF currently includes all images in the page (so you can see full size images that are cropped in the PDF view) and the primary video on the page if any.</p>
<h2>Why <em>not</em> make a PDF archiving tool for Twitter?</h2>
@ -147,15 +125,13 @@
<h2>How do I check that a PDF came from you?</h2>
<p>You can download <a href="https://crt.sh/?id=8004113167">this public key file (.PEM)</a> and add it to Adobe Acrobat. You can also use <a href="#check-pdf">this form</a> to confirm whether it really came from us by checking its fingerprint against our logs.</p>
<p>Tech nerd note: As an extra check, the key you're downloading here happens to be one that we also verified via LetsEncrypt as belonging to our domain. You can see the same key in <a href="https://crt.sh/?id=8004113167">the certificate transparency logs</a>.</p>
<p>See our <a href="/check">Signature Verification Page</a>.</p>
<h2>Does a signature on a PDF web archive mean it's real?</h2>
<p>Well … no. Library folks like to talk about "authenticity" and "provenance". A signature on a PDF tells you its <em>provenance</em>: you can prove that you really got the PDF from us, and that we couldn't have created it after a certain date. You'll then have to decide whether you trust our claim that the PDF we gave you represents a real page we saw on Twitter (and that no one else has messed with our servers). If someone else gives you a signed PDF, they're giving you a different provenance chain, and you can trace that back to decide who you're being asked to trust.</p>
<p>Tech nerd note: This whole trust step is needed because of something called <em>repudiability</em>: https web transactions are deliberately designed to be repudiable, meaning there's no way to tell as a third party after the fact whether they ever really happened. Signed HTTP exchanges are one proposal that may eventually let websites choose to publish verifiable content instead, but they aren't here yet. So for now, you're left deciding whether "archive.social" is an intermediary you want to choose to trust.</p>
<p>Tech nerd note: This whole trust step is needed because of something called <em>repudiability</em>: https web transactions are deliberately designed to be repudiable, meaning there's no way to tell as a third party after the fact whether they ever really happened. Signed HTTP exchanges are one proposal that may eventually let websites choose to publish verifiable content instead, but they aren't here yet. So for now, you're left deciding whether "social.perma.cc" is an intermediary you want to choose to trust.</p>
<h2>What is your privacy policy?</h2>
@ -163,19 +139,7 @@
</section>
<footer>
<p>
<a href="https://lil.law.harvard.edu">
<img src="/static/lil.svg" alt="Harvard Library Innovation Lab - Logo">
</a>
</p>
<p>This site is an experiment of the <a href="https://lil.law.harvard.edu">Harvard Library Innovation Lab</a>.</p>
<p><a href="https://twitter.com/harvardlil">@harvardlil</a> on Twitter - <a href="https://law.us3.list-manage.com/subscribe?u=4290964398813d739f2398db0&amp;id=e097736c6f">Subscribe to our Newsletter</a>.</p>
<p>contact: <a href="mailto:info@archive.social">info@archive.social</a></p>
</footer>
{% include "./modules/footer.njk" %}
</main>
</body>

Wyświetl plik

@ -0,0 +1,13 @@
<footer>
<p>
<a href="https://lil.law.harvard.edu">
<img src="/static/lil.svg" alt="Harvard Library Innovation Lab - Logo">
</a>
</p>
<p>This site is an experiment of the <a href="https://lil.law.harvard.edu">Harvard Library Innovation Lab</a>.</p>
<p><a href="https://twitter.com/harvardlil">@harvardlil</a> on Twitter - <a href="https://law.us3.list-manage.com/subscribe?u=4290964398813d739f2398db0&amp;id=e097736c6f">Subscribe to our Newsletter</a>.</p>
<p>contact: <a href="mailto:info@perma.cc">info@perma.cc</a></p>
</footer>

Wyświetl plik

@ -0,0 +1 @@
<img src="/static/ocean-of-books.svg" alt="Illustration of a person falling in an ocean of books.">

Wyświetl plik

@ -1,5 +1,5 @@
/**
* archive.social
* thread-keeper
* @module utils.AccessKeys
* @author The Harvard Library Innovation Lab
* @license MIT

Wyświetl plik

@ -0,0 +1,60 @@
/**
* thread-keeper
* @module utils.CertsHistory
* @author The Harvard Library Innovation Lab
* @license MIT
*/
import fs from "fs";
import { DATA_PATH } from "../const.js";
/**
* Utility class for handling the "certificates history" files.
* Expected structure:
* ```
* [
* {
* "from": "2022-11-18 13:07:56 UTC",
* "to": "2022-11-22 19:00:00 UTC",
* "domain": "domain.ext",
* "info": "...",
* "cert": "..."
* },
* ...
* ]
* ```
*/
export class CertsHistory {
/**
* Complete path to `signing-certs-history.json`.
* @type {string}
*/
static signingFilepath = `${DATA_PATH}signing-certs-history.json`;
/**
* Complete path to `timestamping-certs-history.json`.
* @type {string}
*/
static timestampingFilepath = `${DATA_PATH}timestamping-certs-history.json`;
/**
* Returns the parsed contents of a certificates history file.
* Creates said file if it doesn't exist.
*
* @param {string} [type="signing"] - Can be "signing" or "timestamping".
*/
static load(type = "signing") {
if (!["signing", "timestamping"].includes(type)) {
throw new Error(`${type} is not a valid certificate history file type.`);
}
const filepath = type === "signing" ? CertsHistory.signingFilepath : CertsHistory.timestampingFilepath;
if (!fs.readFileSync(filepath)) {
fs.writeFileSync(filepath, "{}");
}
return JSON.parse(fs.readFileSync(filepath));
}
}

Wyświetl plik

@ -1,5 +1,5 @@
/**
* archive.social
* thread-keeper
* @module utils.SuccessLog
* @author The Harvard Library Innovation Lab
* @license MIT
@ -10,7 +10,9 @@ import crypto from "crypto";
import { DATA_PATH } from "../const.js";
/**
* Utility class for handling success logs. Keeps trace of the hashes of the PDFs that were generated.
*/
export class SuccessLog {
/**
* Complete path to `success-log.json`.

Wyświetl plik

@ -1,5 +1,5 @@
/**
* archive.social
* thread-keeper
* @module utils.TwitterCapture
* @author The Harvard Library Innovation Lab
* @license MIT

Wyświetl plik

@ -1,5 +1,5 @@
/**
* archive.social
* thread-keeper
* @module utils
* @author The Harvard Library Innovation Lab
* @license MIT
@ -7,5 +7,6 @@
import { AccessKeys } from "./AccessKeys.js";
import { TwitterCapture } from "./TwitterCapture.js";
import { SuccessLog } from "./SuccessLog.js";
import { CertsHistory } from "./CertsHistory.js";
export { AccessKeys, SuccessLog, TwitterCapture };
export { AccessKeys, SuccessLog, CertsHistory, TwitterCapture };

Wyświetl plik

@ -1,7 +1,7 @@
<a name="const.module_js"></a>
## js
archive.social
thread-keeper
**Author**: The Harvard Library Innovation Lab
**License**: MIT

Wyświetl plik

@ -1,7 +1,7 @@
<a name="server.module_js"></a>
## js
archive.social
thread-keeper
**Author**: The Harvard Library Innovation Lab
**License**: MIT
@ -11,6 +11,7 @@ archive.social
* [~accessKeys](#server.module_js..accessKeys) : <code>AccessKeys</code>
* [~CAPTURES_WATCH](#server.module_js..CAPTURES_WATCH) : <code>Object</code>
* [~index(request, reply)](#server.module_js..index) ⇒ <code>Promise.&lt;fastify.FastifyReply&gt;</code>
* [~check(request, reply)](#server.module_js..check) ⇒ <code>Promise.&lt;fastify.FastifyReply&gt;</code>
* [~capture(request, reply)](#server.module_js..capture) ⇒ <code>Promise.&lt;fastify.FastifyReply&gt;</code>
* [~checkHash(request, reply)](#server.module_js..checkHash) ⇒ <code>Promise.&lt;fastify.FastifyReply&gt;</code>
@ -45,6 +46,20 @@ Assumes `fastify` is in scope.
| request | <code>fastify.FastifyRequest</code> |
| reply | <code>fastify.FastifyReply</code> |
<a name="server.module_js..check"></a>
### js~check(request, reply) ⇒ <code>Promise.&lt;fastify.FastifyReply&gt;</code>
[GET] /check
Shows the "check" page /check form. Loads certificates history files in the process.
Assumes `fastify` is in scope.
**Kind**: inner method of [<code>js</code>](#server.module_js)
| Param | Type |
| --- | --- |
| request | <code>fastify.FastifyRequest</code> |
| reply | <code>fastify.FastifyReply</code> |
<a name="server.module_js..capture"></a>
### js~capture(request, reply) ⇒ <code>Promise.&lt;fastify.FastifyReply&gt;</code>

Wyświetl plik

@ -1,7 +1,7 @@
<a name="utils.module_AccessKeys"></a>
## AccessKeys
archive.social
thread-keeper
**Author**: The Harvard Library Innovation Lab
**License**: MIT

Wyświetl plik

@ -0,0 +1,67 @@
<a name="utils.module_CertsHistory"></a>
## CertsHistory
thread-keeper
**Author**: The Harvard Library Innovation Lab
**License**: MIT
* [CertsHistory](#utils.module_CertsHistory)
* [.CertsHistory](#utils.module_CertsHistory.CertsHistory)
* _instance_
* [.signingFilepath](#utils.module_CertsHistory.CertsHistory+signingFilepath) : <code>string</code>
* [.timestampingFilepath](#utils.module_CertsHistory.CertsHistory+timestampingFilepath) : <code>string</code>
* _static_
* [.load([type])](#utils.module_CertsHistory.CertsHistory.load)
<a name="utils.module_CertsHistory.CertsHistory"></a>
### CertsHistory.CertsHistory
Utility class for handling the "certificates history" files.
Expected structure:
```
[
{
"from": "2022-11-18 13:07:56 UTC",
"to": "2022-11-22 19:00:00 UTC",
"domain": "domain.ext",
"info": "...",
"cert": "..."
},
...
]
```
**Kind**: static class of [<code>CertsHistory</code>](#utils.module_CertsHistory)
* [.CertsHistory](#utils.module_CertsHistory.CertsHistory)
* _instance_
* [.signingFilepath](#utils.module_CertsHistory.CertsHistory+signingFilepath) : <code>string</code>
* [.timestampingFilepath](#utils.module_CertsHistory.CertsHistory+timestampingFilepath) : <code>string</code>
* _static_
* [.load([type])](#utils.module_CertsHistory.CertsHistory.load)
<a name="utils.module_CertsHistory.CertsHistory+signingFilepath"></a>
#### certsHistory.signingFilepath : <code>string</code>
Complete path to `signing-certs-history.json`.
**Kind**: instance property of [<code>CertsHistory</code>](#utils.module_CertsHistory.CertsHistory)
<a name="utils.module_CertsHistory.CertsHistory+timestampingFilepath"></a>
#### certsHistory.timestampingFilepath : <code>string</code>
Complete path to `timestamping-certs-history.json`.
**Kind**: instance property of [<code>CertsHistory</code>](#utils.module_CertsHistory.CertsHistory)
<a name="utils.module_CertsHistory.CertsHistory.load"></a>
#### CertsHistory.load([type])
Returns the parsed contents of a certificates history file.
Creates said file if it doesn't exist.
**Kind**: static method of [<code>CertsHistory</code>](#utils.module_CertsHistory.CertsHistory)
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [type] | <code>string</code> | <code>&quot;\&quot;signing\&quot;&quot;</code> | Can be "signing" or "timestamping". |

Wyświetl plik

@ -1,7 +1,7 @@
<a name="utils.module_SuccessLog"></a>
## SuccessLog
archive.social
thread-keeper
**Author**: The Harvard Library Innovation Lab
**License**: MIT
@ -17,6 +17,8 @@ archive.social
<a name="utils.module_SuccessLog.SuccessLog"></a>
### SuccessLog.SuccessLog
Utility class for handling success logs. Keeps trace of the hashes of the PDFs that were generated.
**Kind**: static class of [<code>SuccessLog</code>](#utils.module_SuccessLog)
* [.SuccessLog](#utils.module_SuccessLog.SuccessLog)

Wyświetl plik

@ -1,7 +1,7 @@
<a name="utils.module_TwitterCapture"></a>
## TwitterCapture
archive.social
thread-keeper
**Author**: The Harvard Library Innovation Lab
**License**: MIT

Wyświetl plik

@ -1,7 +1,7 @@
<a name="module_utils"></a>
## utils
archive.social
thread-keeper
**Author**: The Harvard Library Innovation Lab
**License**: MIT

Wyświetl plik

@ -1,5 +1,5 @@
{
"name": "archive.social",
"name": "thread-keeper",
"version": "0.0.1",
"description": "",
"main": "app.js",
@ -84,7 +84,7 @@
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/harvard-lil/archive.social/issues"
"url": "https://github.com/harvard-lil/thread-keeper/issues"
},
"homepage": "https://github.com/harvard-lil/archive.social#readme"
"homepage": "https://github.com/harvard-lil/thread-keeper#readme"
}

Wyświetl plik

@ -5,4 +5,5 @@ jsdoc2md ../app/const.js > ../docs/const.md;
jsdoc2md ../app/utils/index.js > ../docs/utils/index.md;
jsdoc2md ../app/utils/AccessKeys.js > ../docs/utils/AccessKeys.md;
jsdoc2md ../app/utils/SuccessLog.js > ../docs/utils/SuccessLog.md;
jsdoc2md ../app/utils/TwitterCapture.js > ../docs/utils/TwitterCapture.md;
jsdoc2md ../app/utils/TwitterCapture.js > ../docs/utils/TwitterCapture.md;
jsdoc2md ../app/utils/CertsHistory.js > ../docs/utils/CertsHistory.md;