age-encryption
Version:
<p align="center"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://github.com/FiloSottile/age/blob/main/logo/logo_white.svg"> <source media="(prefers-color-scheme: light)" srcset="https://github.com/FiloSottile/a
112 lines (111 loc) • 3.33 kB
JavaScript
import { randomBytes } from "@noble/hashes/utils";
export class LineReader {
s;
transcript = [];
buf = new Uint8Array(0);
constructor(stream) {
this.s = stream.getReader();
}
async readLine() {
const line = [];
while (true) {
const i = this.buf.indexOf("\n".charCodeAt(0));
if (i >= 0) {
line.push(this.buf.subarray(0, i));
this.transcript.push(this.buf.subarray(0, i + 1));
this.buf = this.buf.subarray(i + 1);
return asciiString(flatten(line));
}
if (this.buf.length > 0) {
line.push(this.buf);
this.transcript.push(this.buf);
}
const next = await this.s.read();
if (next.done) {
this.buf = flatten(line);
return null;
}
this.buf = next.value;
}
}
close() {
this.s.releaseLock();
return { rest: this.buf, transcript: flatten(this.transcript) };
}
}
function asciiString(bytes) {
bytes.forEach((b) => {
if (b < 32 || b > 126) {
throw Error("invalid non-ASCII byte in header");
}
});
return new TextDecoder().decode(bytes);
}
export function flatten(arr) {
const len = arr.reduce(((sum, line) => sum + line.length), 0);
const out = new Uint8Array(len);
let n = 0;
for (const a of arr) {
out.set(a, n);
n += a.length;
}
return out;
}
export function prepend(s, ...prefixes) {
return s.pipeThrough(new TransformStream({
start(controller) {
for (const p of prefixes) {
controller.enqueue(p);
}
}
}));
}
export function stream(a) {
// https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/from_static
return new ReadableStream({
start(controller) {
controller.enqueue(a);
controller.close();
}
});
}
export async function readAll(stream) {
if (!(stream instanceof ReadableStream)) {
throw new Error("readAll expects a ReadableStream<Uint8Array>");
}
return new Uint8Array(await new Response(stream).arrayBuffer());
}
export async function readAllString(stream) {
if (!(stream instanceof ReadableStream)) {
throw new Error("readAllString expects a ReadableStream<Uint8Array>");
}
return await new Response(stream).text();
}
export async function read(stream, n) {
const reader = stream.getReader();
const chunks = [];
let readBytes = 0;
while (readBytes < n) {
const { done, value } = await reader.read();
if (done) {
throw Error("stream ended before reading " + n + " bytes");
}
chunks.push(value);
readBytes += value.length;
}
reader.releaseLock();
const buf = flatten(chunks);
const data = buf.subarray(0, n);
const rest = prepend(stream, buf.subarray(n));
return { data, rest };
}
export function randomBytesStream(n, chunk) {
return new ReadableStream({
start(controller) {
for (let i = 0; i < n; i += chunk) {
controller.enqueue(randomBytes(Math.min(chunk, n - i)));
}
controller.close();
}
});
}