Skip to content

Commit afd0de1

Browse files
committed
feat: add hex
1 parent f2d3469 commit afd0de1

File tree

4 files changed

+128
-0
lines changed

4 files changed

+128
-0
lines changed

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ utilities provided by `@better-auth/utils`:
2020
| [**RSA**](#rsa) | Perform encryption, decryption, signing, and verification with RSA keys. |
2121
| [**ECDSA**](#ecdsa) | Perform signing and verification with ECDSA keys. |
2222
| [**Base64**](#base64) | Encode and decode data in base64 format. |
23+
| [**Hex**](#hex) | Encode and decode data in hexadecimal format. |
2324
| [**OTP**](#otp) | Generate and verify one-time passwords. |
2425
| [**Cookies**](#cookies) | Parse, serialize, and manage HTTP cookies. |
2526

@@ -254,6 +255,28 @@ const decodedData = await base64.decode(encodedData);
254255
It automatically detects if the input is URL-safe and includes padding characters.
255256

256257

258+
## Hex
259+
260+
Hex utilities provide a simple interface to encode and decode data in hexadecimal format.
261+
262+
### Encoding
263+
264+
Encode data in hexadecimal format. Input can be a string, `ArrayBuffer`, or `TypedArray`.
265+
266+
```ts
267+
import { hex } from "@better-auth/utils/hex";
268+
269+
const encodedData = hex.encode("Data to encode");
270+
```
271+
272+
### Decoding
273+
274+
Decode hexadecimal-encoded data. Input can be a string or `ArrayBuffer`.
275+
276+
```ts
277+
const decodedData = hex.decode(encodedData);
278+
```
279+
257280
## OTP
258281

259282
The OTP utility provides a simple and secure way to generate and verify one-time passwords (OTPs), commonly used in multi-factor authentication (MFA) systems. It includes support for both HOTP (HMAC-based One-Time Password) and TOTP (Time-based One-Time Password) standards.

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@
5555
"import": "./dist/ecdsa.mjs",
5656
"require": "./dist/ecdsa.cjs"
5757
},
58+
"./hex": {
59+
"import": "./dist/hex.mjs",
60+
"require": "./dist/hex.cjs"
61+
},
5862
"./hmac": {
5963
"import": "./dist/hmac.mjs",
6064
"require": "./dist/hmac.cjs"

src/hex.test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { hex } from './hex';
3+
4+
describe('hex', () => {
5+
describe('encode', () => {
6+
it('should encode a string to hexadecimal', () => {
7+
const input = "Hello, World!";
8+
const expected = "48656c6c6f2c20576f726c6421";
9+
expect(hex.encode(input)).toBe(expected);
10+
});
11+
12+
it('should encode an ArrayBuffer to hexadecimal', () => {
13+
const input = new TextEncoder().encode("Hello").buffer;
14+
const expected = "48656c6c6f";
15+
expect(hex.encode(input)).toBe(expected);
16+
});
17+
18+
it('should encode a TypedArray to hexadecimal', () => {
19+
const input = new Uint8Array([72, 101, 108, 108, 111]);
20+
const expected = "48656c6c6f";
21+
expect(hex.encode(input)).toBe(expected);
22+
});
23+
});
24+
25+
describe('decode', () => {
26+
it('should decode a hexadecimal string to its original value', () => {
27+
const input = "48656c6c6f2c20576f726c6421";
28+
const expected = "Hello, World!";
29+
expect(hex.decode(input)).toBe(expected);
30+
});
31+
32+
it('should handle decoding of a hexadecimal string to binary data', () => {
33+
const input = "48656c6c6f";
34+
const expected = "Hello";
35+
expect(hex.decode(input)).toBe(expected);
36+
});
37+
38+
it('should throw an error for an odd-length string', () => {
39+
const input = "123";
40+
expect(() => hex.decode(input)).toThrow(Error);
41+
});
42+
43+
it('should throw an error for a non-hexadecimal string', () => {
44+
const input = "zzzz";
45+
expect(() => hex.decode(input)).toThrow(Error);
46+
});
47+
});
48+
49+
describe('round-trip tests', () => {
50+
it('should return the original string after encoding and decoding', () => {
51+
const input = "Hello, Hex!";
52+
const encoded = hex.encode(input);
53+
const decoded = hex.decode(encoded);
54+
expect(decoded).toBe(input);
55+
});
56+
57+
it('should handle empty strings', () => {
58+
const input = "";
59+
const encoded = hex.encode(input);
60+
const decoded = hex.decode(encoded);
61+
expect(decoded).toBe(input);
62+
});
63+
});
64+
});

src/hex.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import type { TypedArray } from "./type";
2+
3+
4+
const hexadecimal = "0123456789abcdef";
5+
export const hex = {
6+
encode: (data:
7+
string | ArrayBuffer | TypedArray) => {
8+
if (typeof data === "string") {
9+
data = new TextEncoder().encode(data);
10+
}
11+
if (data.byteLength === 0) {
12+
return "";
13+
}
14+
const buffer = new Uint8Array(data);
15+
let result = "";
16+
for (const byte of buffer) {
17+
result += byte.toString(16).padStart(2, "0");
18+
}
19+
return result;
20+
},
21+
decode: (data: string) => {
22+
if (!data) {
23+
return "";
24+
}
25+
if (data.length % 2 !== 0) {
26+
throw new Error("Invalid hexadecimal string");
27+
}
28+
if (!new RegExp(`^[${hexadecimal}]+$`).test(data)) {
29+
throw new Error("Invalid hexadecimal string");
30+
}
31+
const result = new Uint8Array(data.length / 2);
32+
for (let i = 0; i < data.length; i += 2) {
33+
result[i / 2] = parseInt(data.slice(i, i + 2), 16);
34+
}
35+
return new TextDecoder().decode(result);
36+
}
37+
}

0 commit comments

Comments
 (0)