crypto.ts 5.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. import { getUnsafeWindow } from "./dom.js";
  2. import { mapRange } from "./math.js";
  3. /** Compresses a string or an ArrayBuffer using the provided {@linkcode compressionFormat} and returns it as a base64 string */
  4. export async function compress(input: string | ArrayBuffer, compressionFormat: CompressionFormat, outputType?: "string"): Promise<string>
  5. /** Compresses a string or an ArrayBuffer using the provided {@linkcode compressionFormat} and returns it as an ArrayBuffer */
  6. export async function compress(input: string | ArrayBuffer, compressionFormat: CompressionFormat, outputType: "arrayBuffer"): Promise<ArrayBuffer>
  7. /** Compresses a string or an ArrayBuffer using the provided {@linkcode compressionFormat} and returns it as a base64 string or ArrayBuffer, depending on what {@linkcode outputType} is set to */
  8. export async function compress(input: string | ArrayBuffer, compressionFormat: CompressionFormat, outputType: "string" | "arrayBuffer" = "string"): Promise<ArrayBuffer | string> {
  9. const byteArray = typeof input === "string" ? new TextEncoder().encode(input) : input;
  10. const comp = new CompressionStream(compressionFormat);
  11. const writer = comp.writable.getWriter();
  12. writer.write(byteArray);
  13. writer.close();
  14. const buf = await (new Response(comp.readable).arrayBuffer());
  15. return outputType === "arrayBuffer" ? buf : ab2str(buf);
  16. }
  17. /** Decompresses a previously compressed base64 string or ArrayBuffer, with the format passed by {@linkcode compressionFormat}, converted to a string */
  18. export async function decompress(input: string | ArrayBuffer, compressionFormat: CompressionFormat, outputType?: "string"): Promise<string>
  19. /** Decompresses a previously compressed base64 string or ArrayBuffer, with the format passed by {@linkcode compressionFormat}, converted to an ArrayBuffer */
  20. export async function decompress(input: string | ArrayBuffer, compressionFormat: CompressionFormat, outputType: "arrayBuffer"): Promise<ArrayBuffer>
  21. /** Decompresses a previously compressed base64 string or ArrayBuffer, with the format passed by {@linkcode compressionFormat}, converted to a string or ArrayBuffer, depending on what {@linkcode outputType} is set to */
  22. export async function decompress(input: string | ArrayBuffer, compressionFormat: CompressionFormat, outputType: "string" | "arrayBuffer" = "string"): Promise<ArrayBuffer | string> {
  23. const byteArray = typeof input === "string" ? str2ab(input) : input;
  24. const decomp = new DecompressionStream(compressionFormat);
  25. const writer = decomp.writable.getWriter();
  26. writer.write(byteArray);
  27. writer.close();
  28. const buf = await (new Response(decomp.readable).arrayBuffer());
  29. return outputType === "arrayBuffer" ? buf : new TextDecoder().decode(buf);
  30. }
  31. /** Converts an ArrayBuffer to a base64-encoded string */
  32. function ab2str(buf: ArrayBuffer): string {
  33. return getUnsafeWindow().btoa(
  34. new Uint8Array(buf)
  35. .reduce((data, byte) => data + String.fromCharCode(byte), "")
  36. );
  37. }
  38. /** Converts a base64-encoded string to an ArrayBuffer representation of its bytes */
  39. function str2ab(str: string): ArrayBuffer {
  40. return Uint8Array.from(getUnsafeWindow().atob(str), c => c.charCodeAt(0));
  41. }
  42. /**
  43. * Creates a hash / checksum of the given {@linkcode input} string or ArrayBuffer using the specified {@linkcode algorithm} ("SHA-256" by default).
  44. *
  45. * ⚠️ Uses the SubtleCrypto API so it needs to run in a secure context (HTTPS).
  46. * ⚠️ If you use this for cryptography, make sure to use a secure algorithm (under no circumstances use SHA-1) and to [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) your input data.
  47. */
  48. export async function computeHash(input: string | ArrayBuffer, algorithm = "SHA-256"): Promise<string> {
  49. let data: ArrayBuffer;
  50. if(typeof input === "string") {
  51. const encoder = new TextEncoder();
  52. data = encoder.encode(input);
  53. }
  54. else
  55. data = input;
  56. const hashBuffer = await crypto.subtle.digest(algorithm, data);
  57. const hashArray = Array.from(new Uint8Array(hashBuffer));
  58. const hashHex = hashArray.map(byte => byte.toString(16).padStart(2, "0")).join("");
  59. return hashHex;
  60. }
  61. /**
  62. * Generates a random ID with the specified length and radix (16 characters and hexadecimal by default)
  63. *
  64. * ⚠️ Not suitable for generating anything related to cryptography! Use [SubtleCrypto's `generateKey()`](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/generateKey) for that instead.
  65. * @param length The length of the ID to generate (defaults to 16)
  66. * @param radix The [radix](https://en.wikipedia.org/wiki/Radix) of each digit (defaults to 16 which is hexadecimal. Use 2 for binary, 10 for decimal, 36 for alphanumeric, etc.)
  67. * @param enhancedEntropy If set to true, uses [`crypto.getRandomValues()`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues) for better cryptographic randomness (this also makes it take MUCH longer to generate)
  68. */
  69. export function randomId(length = 16, radix = 16, enhancedEntropy = false): string {
  70. if(enhancedEntropy) {
  71. const arr = new Uint8Array(length);
  72. crypto.getRandomValues(arr);
  73. return Array.from(
  74. arr,
  75. (v) => mapRange(v, 0, 255, 0, radix).toString(radix).substring(0, 1),
  76. ).join("");
  77. }
  78. return Array.from(
  79. { length },
  80. () => Math.floor(Math.random() * radix).toString(radix),
  81. ).join("");
  82. }