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

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");

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");

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;
}

View File

@@ -1 +1 @@
export declare const __dirname: string;
export declare const HASH_V2_EPOCH = "2026-07-01T00:00:00Z";

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;

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

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

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>