From d5309f03c38244131bbaece16ab9ee946f3b5445 Mon Sep 17 00:00:00 2001 From: Morax Date: Tue, 29 Jul 2025 12:19:42 +0800 Subject: [PATCH] feat: Refactor random number generation to use a secure method from utils --- src/tarot/card-manager.ts | 21 +++------------------ src/tarot/reading-manager.ts | 29 +++-------------------------- src/tarot/utils.ts | 15 +++++++++++++++ 3 files changed, 21 insertions(+), 44 deletions(-) create mode 100644 src/tarot/utils.ts diff --git a/src/tarot/card-manager.ts b/src/tarot/card-manager.ts index e3930ca..f0f3eb1 100644 --- a/src/tarot/card-manager.ts +++ b/src/tarot/card-manager.ts @@ -2,9 +2,7 @@ import fs from 'fs/promises'; import path from 'path'; import { fileURLToPath } from 'url'; import { TarotCard, CardOrientation, CardCategory } from './types.js'; - -// Use global crypto in Node.js >= 18 and browsers -declare const crypto: any; +import { getSecureRandom } from './utils.js'; // Helper to get __dirname in ES modules const __filename = fileURLToPath(import.meta.url); @@ -206,19 +204,6 @@ export class TarotCardManager { return undefined; } - /** - * Generate a cryptographically secure random number between 0 and 1. - */ - private getSecureRandom(): number { - if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') { - const array = new Uint32Array(1); - crypto.getRandomValues(array); - return array[0] / (0xffffffff + 1); - } - // Fallback for older Node.js or unexpected environments - console.warn('crypto.getRandomValues not available, falling back to Math.random().'); - return Math.random(); - } /** * Fisher-Yates shuffle algorithm for true randomness. @@ -226,7 +211,7 @@ export class TarotCardManager { private fisherYatesShuffle(array: readonly T[]): T[] { const shuffled = [...array]; for (let i = shuffled.length - 1; i > 0; i--) { - const j = Math.floor(this.getSecureRandom() * (i + 1)); + const j = Math.floor(getSecureRandom() * (i + 1)); [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; } return shuffled; @@ -236,7 +221,7 @@ export class TarotCardManager { * Get a random card from the deck. */ public getRandomCard(): TarotCard { - const randomIndex = Math.floor(this.getSecureRandom() * this.allCards.length); + const randomIndex = Math.floor(getSecureRandom() * this.allCards.length); return this.allCards[randomIndex]; } diff --git a/src/tarot/reading-manager.ts b/src/tarot/reading-manager.ts index 08bfa1f..0f11e2e 100644 --- a/src/tarot/reading-manager.ts +++ b/src/tarot/reading-manager.ts @@ -2,6 +2,7 @@ import { TarotCardManager } from "./card-manager.js"; import { TarotSessionManager } from "./session-manager.js"; import { TarotReading, DrawnCard, CardOrientation, TarotCard } from "./types.js"; import { TAROT_SPREADS, getAllSpreads, getSpread, isValidSpreadType } from "./spreads.js"; +import { getSecureRandom } from "./utils.js"; /** * Manages tarot readings and interpretations @@ -1115,41 +1116,17 @@ export class TarotReadingManager { */ private getSecureRandomOrientation(): CardOrientation { // Use the same secure random method as card manager - const random = this.getSecureRandom(); + const random = getSecureRandom(); return random < 0.5 ? "upright" : "reversed"; // 50% chance upright, 50% reversed } - /** - * Generate cryptographically secure random number - */ - private getSecureRandom(): number { - if (typeof crypto !== 'undefined' && crypto.getRandomValues) { - // Browser environment with Web Crypto API - const array = new Uint32Array(1); - crypto.getRandomValues(array); - return array[0] / (0xffffffff + 1); - } else if (typeof require !== 'undefined') { - // Node.js environment - try { - const crypto = require('crypto'); - return crypto.randomBytes(4).readUInt32BE(0) / (0xffffffff + 1); - } catch (e) { - // Fallback to Math.random if crypto is not available - console.warn('Crypto module not available, falling back to Math.random()'); - return Math.random(); - } - } else { - // Fallback to Math.random - return Math.random(); - } - } /** * Generate a unique reading ID with secure randomness */ private generateReadingId(): string { const timestamp = Date.now(); - const randomPart = Math.floor(this.getSecureRandom() * 1000000000).toString(36); + const randomPart = Math.floor(getSecureRandom() * 1000000000).toString(36); return `reading_${timestamp}_${randomPart}`; } } diff --git a/src/tarot/utils.ts b/src/tarot/utils.ts new file mode 100644 index 0000000..bd6791f --- /dev/null +++ b/src/tarot/utils.ts @@ -0,0 +1,15 @@ +// Use global crypto in Node.js >= 18 and browsers +declare const crypto: any; + +/** + * Generate a cryptographically secure random number between 0 and 1. + * @throws {Error} If crypto.getRandomValues is not available. + */ +export function getSecureRandom(): number { + if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') { + const array = new Uint32Array(1); + crypto.getRandomValues(array); + return array[0] / (0xffffffff + 1); + } + throw new Error('A secure random number generator (crypto.getRandomValues) is not available in this environment.'); +} \ No newline at end of file