feat: update libraries

This commit is contained in:
2026-01-12 00:32:42 +08:00
parent de7d85fdc9
commit 3a74da85d2
3 changed files with 125 additions and 10 deletions

View File

@@ -12,6 +12,11 @@ Publish script:
hatter script pub --type deno-mod deno-commons-mod.ts hatter script pub --type deno-mod deno-commons-mod.ts
``` ```
Test script:
```shell
deno test --allow-all deno-commons-mod.ts
```
Published deno-mod at: Published deno-mod at:
[script deno mod](https://hatter.ink/script/list_script.jssp?type=deno-mod) [script deno mod](https://hatter.ink/script/list_script.jssp?type=deno-mod)

View File

@@ -3,8 +3,13 @@
import { assert } from "jsr:@std/assert/assert"; import { assert } from "jsr:@std/assert/assert";
import { assertEquals } from "jsr:@std/assert"; import { assertEquals } from "jsr:@std/assert";
import { decodeBase64, encodeBase64 } from "jsr:@std/encoding/base64";
import { dirname } from "https://deno.land/std@0.208.0/path/mod.ts"; import { dirname } from "https://deno.land/std@0.208.0/path/mod.ts";
// reference: https://docs.deno.com/examples/hex_base64_encoding/
// import { decodeBase64, encodeBase64 } from "jsr:@std/encoding/base64";
// import { decodeHex, encodeHex } from "jsr:@std/encoding/hex";
export async function sleep(timeoutMillis: number): Promise<void> { export async function sleep(timeoutMillis: number): Promise<void> {
await new Promise((resolve) => setTimeout(resolve, timeoutMillis)); await new Promise((resolve) => setTimeout(resolve, timeoutMillis));
} }
@@ -28,7 +33,7 @@ export function compareVersion(ver1: string, ver2: string): 0 | 1 | -1 {
return 0; return 0;
} }
export function isOn(val: string | undefined): boolean { export function isOn(val: string | undefined | null): boolean {
if ((val === null) || (val === undefined)) { if ((val === null) || (val === undefined)) {
return false; return false;
} }
@@ -37,7 +42,7 @@ export function isOn(val: string | undefined): boolean {
lowerVal === "true"; lowerVal === "true";
} }
export async function getEnv(key: string): string { export async function getEnv(key: string): Promise<string | null> {
const homeDir = getHomeDir(); const homeDir = getHomeDir();
if ((homeDir !== null) && key) { if ((homeDir !== null) && key) {
const envValue = await readFileToString( const envValue = await readFileToString(
@@ -47,11 +52,15 @@ export async function getEnv(key: string): string {
return envValue.trim(); return envValue.trim();
} }
} }
return Deno.env.get(key); return Deno.env.get(key) || null;
} }
export function isEnvOn(envKey: string): boolean { export function isEnvOn(envKey: string): boolean {
return isOn(getEnv(envKey)); return isOn(Deno.env.get(envKey));
}
export async function isEnvOnAsync(envKey: string): Promise<boolean> {
return isOn(await getEnv(envKey));
} }
export function formatHumanTime(timeMillis: number): string { export function formatHumanTime(timeMillis: number): string {
@@ -345,6 +354,22 @@ export function hexStringToUint8Array(hex: string): Uint8Array {
return uint8; return uint8;
} }
export function decodeBase64Url(base64UrlString: string): Uint8Array {
let standardBase64 = base64UrlString.replace(/-/g, "+").replace(/_/g, "/");
while (standardBase64.length % 4) {
standardBase64 += "=";
}
return decodeBase64(standardBase64);
}
export function encodeBase64Url(input: ArrayBufferLike): string {
let standardBased64 = encodeBase64(input);
return standardBased64.replace(/\+/g, "-").replace(/\//g, "_").replace(
/=/g,
"",
);
}
Deno.test("isOn", () => { Deno.test("isOn", () => {
assertEquals(false, isOn(undefined)); assertEquals(false, isOn(undefined));
assertEquals(false, isOn("")); assertEquals(false, isOn(""));
@@ -409,3 +434,18 @@ Deno.test("sleep", async () => {
const t2 = new Date().getTime(); const t2 = new Date().getTime();
assert(Math.abs(1000 - (t2 - t1)) < 20); assert(Math.abs(1000 - (t2 - t1)) < 20);
}); });
Deno.test("base64Url", () => {
assertEquals(
"_dxhVwI3qd9fMBlpEMmi6Q",
encodeBase64Url(decodeBase64Url("_dxhVwI3qd9fMBlpEMmi6Q")),
);
assertEquals(
"1dxJeD7erjAYUNEmdVNE8KdhpPZs0pAHtb-kbSqYIe5j039PkTHbrQYOEoeEWN4UsDERhnUg7mY",
encodeBase64Url(
decodeBase64Url(
"1dxJeD7erjAYUNEmdVNE8KdhpPZs0pAHtb-kbSqYIe5j039PkTHbrQYOEoeEWN4UsDERhnUg7mY",
),
),
);
});

View File

@@ -1,8 +1,11 @@
import { import {
decodeBase64Url,
encodeBase64Url,
getHomeDirOrDie, getHomeDirOrDie,
hexStringToUint8Array, hexStringToUint8Array,
uint8ArrayToHexString, } from "https://global.hatter.ink/script/get/@8/deno-commons-mod.ts";
} from "https://global.hatter.ink/script/get/@6/deno-commons-mod.ts"; import {getRandomValues} from "node:crypto";
import {assertEquals} from "jsr:@std/assert";
const COMMONS_LOCAL_ENCRYPT_TINY_ENCRYPT_MASTER_KEY_FILE = getHomeDirOrDie() + const COMMONS_LOCAL_ENCRYPT_TINY_ENCRYPT_MASTER_KEY_FILE = getHomeDirOrDie() +
"/.cache/commons-local-encrypt-tiny-encrypt-master-key"; "/.cache/commons-local-encrypt-tiny-encrypt-master-key";
@@ -12,6 +15,26 @@ interface TinyEncryptSimpleDecryptObject {
result: string; result: string;
} }
let cachedCryptoMasterKey: CryptoKey | null = null;
export async function lazyLoadCryptoMasterKey(): Promise<CryptoKey> {
if (cachedCryptoMasterKey == null) {
cachedCryptoMasterKey = await loadCryptoMasterKey();
}
return cachedCryptoMasterKey;
}
async function loadCryptoMasterKey(): Promise<CryptoKey> {
const key = await loadMasterKey();
return await crypto.subtle.importKey(
"raw",
key,
"AES-GCM",
false,
["encrypt", "decrypt"],
);
}
async function loadMasterKey(): Promise<Uint8Array> { async function loadMasterKey(): Promise<Uint8Array> {
const masterKeyContent = Deno.readTextFileSync( const masterKeyContent = Deno.readTextFileSync(
COMMONS_LOCAL_ENCRYPT_TINY_ENCRYPT_MASTER_KEY_FILE, COMMONS_LOCAL_ENCRYPT_TINY_ENCRYPT_MASTER_KEY_FILE,
@@ -38,8 +61,55 @@ stderr: ${new TextDecoder().decode(stderr)}`);
return hexStringToUint8Array(tinyEncryptSimpleDecryptObject.result); return hexStringToUint8Array(tinyEncryptSimpleDecryptObject.result);
} }
async function main() { export async function teDecryptToString(ciphertext: string): Promise<string> {
// TODO ... const decryptedValue = await teDecrypt(ciphertext);
console.log(uint8ArrayToHexString(await loadMasterKey())); return new TextDecoder().decode(decryptedValue);
} }
await main();
export async function teDecrypt(ciphertext: string): Promise<ArrayBufferLike> {
if (!ciphertext.startsWith("te:")) {
throw new Error(`Invalid ciphertext: ${ciphertext}`);
}
const ciphertextParts = ciphertext.split(":");
if (ciphertextParts.length !== 3) {
throw new Error(`Invalid ciphertext: ${ciphertext}`);
}
const nonce = decodeBase64Url(ciphertextParts[1]);
const ciphertextAndTag = decodeBase64Url(ciphertextParts[2]);
const cryptoKey = await lazyLoadCryptoMasterKey();
return await crypto.subtle.decrypt(
{ name: "AES-GCM", iv: nonce },
cryptoKey,
ciphertextAndTag,
);
}
export async function teEncrypt(plaintext: string): Promise<string> {
const nonce = randomNonce();
const plaintextBuffer = new TextEncoder().encode(plaintext);
const cryptoKey = await lazyLoadCryptoMasterKey();
const encryptedData = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv: nonce },
cryptoKey,
plaintextBuffer,
);
return `te:${encodeBase64Url(nonce)}:${encodeBase64Url(encryptedData)}`;
}
function randomNonce(): ArrayBufferLike {
const buffer = new Uint8Array(12);
getRandomValues(buffer);
return buffer;
}
Deno.test("teEncryptDecrypt", async () => {
assertEquals(
"hello world",
await teDecryptToString(
await teEncrypt("hello world"),
),
);
});