add support for v2 hashing algorithm.

update README.md and demo file to be more in sync.
update to v0.10.0.
This commit is contained in:
2026-03-29 22:34:43 -04:00
parent d5363c3960
commit c776b5df6a
33 changed files with 2255 additions and 1524 deletions
+10 -5
View File
@@ -9,17 +9,22 @@ formHash.addEventListener("submit", (e) => {
e.preventDefault();
const content = formHash.elements["content"].value;
const created = formHash.elements["created"].value;
const version = parseInt(formHash.elements["version"].value, 10);
const url = formHash.elements["url"].value;
const hash = hashTwt({
content,
created,
url,
});
const hash = hashTwt(
{
content,
created,
url,
},
version,
);
const result = [
`content: ${content}`,
`created: ${created}`,
`url: ${url}`,
`version: ${version}`,
`hash: ${hash}`,
].join("\n");
+10 -5
View File
@@ -15,18 +15,23 @@ formatSource(
const content = formHash.elements["content"].value;
const created = formHash.elements["created"].value;
const version = parseInt(formHash.elements["version"].value, 10);
const url = formHash.elements["url"].value;
const hash = hashTwt({
content,
created,
url,
});
const hash = hashTwt(
{
content,
created,
url,
},
version,
);
const result = [
\`content: \${content}\`,
\`created: \${created}\`,
\`url: \${url}\`,
\`version: \${version}\`,
\`hash: \${hash}\`,
].join("\\n");
+21 -15
View File
@@ -76,6 +76,18 @@ code {
white-space: pre-wrap;
}
details figure {
margin: 1rem .5rem;
}
details figure pre {
overflow: auto;
white-space: pre;
}
details[open] summary ~ * {
animation: riseInDetails .5s ease-in-out;
}
label {
display: inline-block;
@@ -110,6 +122,15 @@ pre {
white-space: pre-wrap;
}
select {
background-color: var(--fg-light);
font-size: 1rem;
}
select:open {
background-color: var(--link-active);
}
textarea {
background-color: var(--fg-light);
font-size: 1rem;
@@ -117,21 +138,6 @@ textarea {
width: 100%;
}
details figure {
margin: 1rem .5rem;
}
details figure pre {
overflow: auto;
white-space: pre;
}
details[open] summary ~ * {
animation: riseInDetails .5s ease-in-out;
}
summary {
cursor: pointer;
}
+1 -1
View File
@@ -1 +1 @@
export declare const __dirname: string;
export declare const HASH_V2_EPOCH = "2026-07-01T00:00:00Z";
+2 -1
View File
@@ -1,2 +1,3 @@
import { Twt } from './types.ts';
export default function hashTwt(twt: Twt): string;
export type HashableTwt = Pick<Twt, "content" | "created" | "url"> & Partial<Omit<Twt, "content" | "created" | "url">>;
export default function hashTwt(twt: HashableTwt, version?: number): string;
+7 -2
View File
@@ -2900,6 +2900,7 @@ const base32Encode = (payload) => {
return encoder.write(payload).finalize();
};
const getValueOrFirstEntry = (value) => Array.isArray(value) && value.length ? value[0] : value;
const HASH_V2_EPOCH = `2026-07-01T00:00:00Z`;
const dateRegex = /^(\d{4})-(\d{2})-(\d{2})([tT ])(\d{2}):(\d{2}):(\d{2})\.?(\d{3})?(?:(?:([+-]\d{2}):?(\d{2}))|Z)?$/;
const formatRFC3339 = (date) => {
const pad = (num = 0) => `${+num < 10 ? 0 : ""}${+num}`;
@@ -2928,10 +2929,14 @@ const formatRFC3339 = (date) => {
offset
].join("");
};
function hashTwt(twt) {
function hashTwt(twt, version = 0) {
const created = formatRFC3339(twt.created);
const payload = [twt.url, created, twt.content].join("\n");
return base32Encode(blakejsExports.blake2b(payload, void 0, 32)).toLowerCase().slice(-7);
const encoded = base32Encode(blakejsExports.blake2b(payload, void 0, 32)).toLowerCase();
const createdDate = new Date(twt.created), epochDate = new Date(HASH_V2_EPOCH);
if (version === 1 || !version && createdDate < epochDate)
return encoded.slice(-7);
return encoded.slice(0, 12);
}
var dayjs_min$1 = { exports: {} };
var dayjs_min = dayjs_min$1.exports;
File diff suppressed because one or more lines are too long
+400 -400
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+99 -27
View File
@@ -14,8 +14,10 @@
<ul class="tabs">
<li class="tab tabOverview" id="overview">
<a class="tab-link" href="#overview" id="tabOverview-link">Overview</a>
<div class="tab-panel" id="tabOverview-panel">
<h1>twtxt-lib</h2>
<p>
An isomorphic TypeScript library of
utility functions for parsing and interacting with
@@ -47,17 +49,28 @@
<ul>
<li>Isomorphic, available as an
(<a href="/dist-browser/twtxt-lib.min.js">optionally minified</a>)
<a href="/dist-browser/twtxt-lib.js">ES6+ library for the browser</a>,
with NPM and JSR versions coming soon.
<li>Fully typed and source-mapped.</li>
<li>Built as an <a href="https://caniuse.com/es6-module">ES6 module</a> (and <a href="https://antfu.me/posts/move-on-to-esm-only">ESM only</a>)</li>
<li>Includes an interactive demo <em>(you&apos;re looking at it)</em>.</li>
<a href="/dist-browser/twtxt-lib.js">ES6+ library for the browser</a>
or as a package (from
<a href="https://www.npmjs.com/package/twtxt-lib">NPM</a> or
<a href="https://jsr.io/@itsericwoodward/twtxt-lib">JSR</a>).
</li>
<li>Fully typed and source-mapped (I hope).</li>
<li>Built as an <a href="https://caniuse.com/es6-module">ES6 module</a>
(and <a href="https://antfu.me/posts/move-on-to-esm-only">ESM only</a>)
</li>
<li>
Includes sample files and an interactive demo <em>(you&apos;re looking
at it)</em>.
</li>
</ul>
<h2>Installation</h2>
<p>This library can be installed in several different ways:</p>
<p>This library can be installed several different ways:</p>
<h3>For the Browser</h3>
@@ -70,6 +83,7 @@
<a href="/dist-browser/twtxt-lib.js">website</a>, or doing a
<code>git clone https://git.itsericwoodward.com/eric/twtxt-lib.git</code>
and pulling it out of the <code>dist-browser</code> folder.
<ul>
<li>
Alternatively, you can grab the minified version from the
@@ -78,10 +92,12 @@
</li>
</ul>
</li>
<li>
Add the newly acquired file to your static site / progressive web app /
over-engineered blog.
</li>
<li>
Import the desired function(s) via ESM:
<code>import { hashTwt, loadAndParseTwtxtFile } from "./twtxt-lib.js";</code>
@@ -93,19 +109,22 @@
<ol>
<li>
Add the package to your project.
<ul>
<li>
Via <a href="https://www.npmjs.com/package/twtxt-lib">NPM</a>:
<code>yarn add twtxt-lib</code>
</li>
<li>
Via <a href="https://jsr.io/@itsericwoodward/twtxt-lib">JSR</a>:
<code>yarn add jsr:@itsericwoodward/twtxt-lib</code>
</li>
</ul>
</li>
<li>
Import the desired function(s) into your code:
Import the desired function(s) via ESM:
<code>import { hashTwt, loadAndParseTwtxtFile } from "twtxt-lib";</code>
</li>
</ol>
@@ -121,16 +140,27 @@
<code>/twtxt-demos/demo-hipster-twtxt.txt</code> </a
>:
</p>
<span class="dotLoader"></span>
</div>
<p class="copyright">
Copyright &copy; 2026 Eric Woodward, released under the <a href="https://www.itsericwoodward.com/licenses/mit/">MIT License</a>.
</p>
</div>
<p>
Example files generated with
<a href="https://hipsum.co/">Hipster Ipsum</a>,
<a href="https://pirateipsum.me/">Pirate Ipsum</a>,
a different <a href="https://lorem-ipsumm.com/pirate-ipsum/">Pirate Ipsum</a>, and
<a href="https://saganipsum.com/">Sagan Ipsum</a>.
</p>
<p class="copyright">
Copyright &copy; 2026 Eric Woodward, released under the <a href="https://www.itsericwoodward.com/licenses/mit/">MIT License</a>.
</p>
</div>
</li>
<li class="tab tabHashTwt" id="hashTwt">
<a class="tab-link" href="#hashTwt">hashTwt</a>
<div class="tab-panel" id="tabHashTwt-panel">
<p>
A function that takes the constituent parts of a &ldquo;twt&rdquo; and
@@ -138,15 +168,43 @@
<a href="https://twtxt.dev/exts/twt-hash.html">extension-compatible hash</a>
for it.
</p>
<p>
Version 0.10.0 and above includes support for
<a href="https://git.mills.io/yarnsocial/twtxt.dev/pulls/28">V2 of the
Hashing Spec</a>:
<ul>
<li>A specific hashing version can be provided as an optional argument.</li>
<li>
When no version argument is provided, it defaults to using version
1 for all twts with a created date before the epoch date
(<code>2026-07-01T00:00:00Z</code>), and version 2 for all twts
created on or after the epoch.
</li>
</ul>
</p>
<form id="formHash" name="formHash" method="post">
<div class="flexRow">
<div class="flexCol">
<label for="version">Version
<select id="version" name="version">
<option value="0">Default</option>
<option value="1">v1</option>
<option value="2">v2</option>
</select>
</label>
<label for="content">Content</label>
<textarea
id="content"
name="content"
>Prow scuttle parley provost Sail ho shrouds spirits boom mizzenmast yardarm.</textarea>
</div>
<div class="flexCol">
<label>
Created
@@ -156,6 +214,7 @@
value="2026-02-01T01:23:45Z"
/>
</label>
<label>
URL
<input
@@ -164,6 +223,7 @@
value="https://example.org/~pirate/twtxt.txt"
/>
</label>
<input type="submit" value="Go" />
</div>
</div>
@@ -173,12 +233,12 @@
<li class="tab tabParse" id="parseTwtxt">
<a class="tab-link" href="#parseTwtxt">parseTwtxt</a>
<div class="tab-panel" id="tabParse-panel">
<p>
A function that parses a twtxt file string,
returning an object with information about the file and its owner
(including <a href="https://twtxt.dev/exts/twt-hash.html">hashes</a>
for each twt and any
(including <a href="#hashTwt">hashes</a> for each twt and any
<a href="https://twtxt.dev/exts/metadata.html">metadata</a> in the
file).
</p>
@@ -187,6 +247,7 @@
<ul>
<li>
<code>/twtxt-demos/demo-hipster-twtxt.txt</code>
<button
data-url="/twtxt-demos/demo-hipster-twtxt.txt"
id="parseHipsterButton"
@@ -195,8 +256,10 @@
Load
</button>
</li>
<li>
<code>/twtxt-demos/demo-pirate-twtxt.txt</code>
<button
data-url="/twtxt-demos/demo-pirate-twtxt.txt"
id="parsePirateButton"
@@ -205,8 +268,10 @@
Load
</button>
</li>
<li>
<code>/twtxt-demos/demo-sagan-twtxt.txt</code>
<button
data-url="/twtxt-demos/demo-sagan-twtxt.txt"
id="parseSaganButton"
@@ -216,13 +281,6 @@
</button>
</li>
</ul>
<p>
<em
>Note that CORS restrictions may limit the
effectiveness of using this function from another
domain.</em
>
</p>
<form id="formParse" name="formParse" method="post">
<div class="flexRow">
@@ -236,7 +294,9 @@
value="/twtxt-demos/demo-hipster-twtxt.txt"
/>
</label>
<input type="submit" value="Go" />
<span class="dotLoader"></span>
</div>
</div>
@@ -246,15 +306,19 @@
<li class="tab tabLoadAndParse" id="loadAndParseTwtxtFile">
<a class="tab-link" href="#loadAndParseTwtxtFile">loadAndParseTwtxtFile</a>
<div class="tab-panel" id="tabLoadAndParse-panel">
<p>
An async function that fetches a twtxt.txt-compatible file
from a URL and parses it into an object
An async function that fetches a <code>twtxt.txt</code>-compatible file
from a URL and parses it, returning the extracted data as an object.
</p>
<p>Pre-included examples:</p>
<ul>
<li>
<code>/twtxt-demos/demo-hipster-twtxt.txt</code>
<button
data-url="/twtxt-demos/demo-hipster-twtxt.txt"
id="loadAndParseHipsterButton"
@@ -263,8 +327,10 @@
Load
</button>
</li>
<li>
<code>/twtxt-demos/demo-pirate-twtxt.txt</code>
<button
data-url="/twtxt-demos/demo-pirate-twtxt.txt"
id="loadAndParsePirateButton"
@@ -273,8 +339,10 @@
Load
</button>
</li>
<li>
<code>/twtxt-demos/demo-sagan-twtxt.txt</code>
<button
data-url="/twtxt-demos/demo-sagan-twtxt.txt"
id="loadAndParseSaganButton"
@@ -284,12 +352,14 @@
</button>
</li>
</ul>
<p>
<em
>Note that CORS restrictions may limit the
effectiveness of using this function from another
domain.</em
>
<em>
Note that
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS">Cross-Origin
Resource Sharing (CORS)</a> restrictions may limit the
effectiveness of using this function across domains.
</em>
</p>
<form
@@ -308,7 +378,9 @@
value="/twtxt-demos/demo-hipster-twtxt.txt"
/>
</label>
<input type="submit" value="Go" />
<span class="dotLoader"></span>
</div>
</div>