Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
{
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 100
"printWidth": 100,
"endOfLine": "auto"
}
]
},
Expand Down
3 changes: 3 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"endOfLine": "auto"
}
3 changes: 2 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
"build:all": "npm run build:node && npm run build:browser",
"build:watch": "tsc -p tsconfig.node.json --watch",
"clean": "rimraf dist coverage",
"copy:assets": "cp -r src/utilities/analytics/reference/data dist/utilities/analytics/reference/",
"copy:assets": "node scripts/copy-assets.js",
"lint": "eslint \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\"",
"lint:fix": "eslint \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\" --fix",
"format": "prettier --write \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\" \"*.{js,ts,json,md}\"",
Expand Down
24 changes: 24 additions & 0 deletions scripts/copy-assets.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const fs = require('fs');
const path = require('path');

const srcDir = path.join(__dirname, '..', 'src', 'utilities', 'analytics', 'reference', 'data');
const destDir = path.join(__dirname, '..', 'dist', 'utilities', 'analytics', 'reference', 'data');

function copyRecursiveSync(src, dest) {
if (!fs.existsSync(src)) {
return;
}
fs.mkdirSync(dest, { recursive: true });
const entries = fs.readdirSync(src, { withFileTypes: true });
for (const entry of entries) {
const from = path.join(src, entry.name);
const to = path.join(dest, entry.name);
if (entry.isDirectory()) {
copyRecursiveSync(from, to);
} else {
fs.copyFileSync(from, to);
}
}
}

copyRecursiveSync(srcDir, destDir);
24 changes: 16 additions & 8 deletions src/processors/gridset/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,16 @@
* @returns Decrypted and inflated buffer
*/
export function decryptGridsetEntry(buffer: Buffer, password?: string): Buffer {
const nodeRequire =
typeof require === 'function' ? require : (undefined as undefined | ((id: string) => any));
if (!nodeRequire) {
throw new Error('Crypto utilities are not available in this environment.');
}
// Dynamic require to avoid breaking in browser environments
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-return
const crypto = require('crypto');
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-return
const zlib = require('zlib');
const cryptoModule = 'crypto';
const zlibModule = 'zlib';
const crypto = nodeRequire(cryptoModule);
const zlib = nodeRequire(zlibModule);

const pwd = (password || 'Chocolate').padEnd(32, ' ');
const key = Buffer.from(pwd.slice(0, 32), 'utf8');
Expand All @@ -43,10 +48,13 @@ export function decryptGridsetEntry(buffer: Buffer, password?: string): Buffer {
*/
export function isCryptoAvailable(): boolean {
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('crypto');
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('zlib');
const nodeRequire =
typeof require === 'function' ? require : (undefined as undefined | ((id: string) => any));
if (!nodeRequire) return false;
const cryptoModule = 'crypto';
const zlibModule = 'zlib';
nodeRequire(cryptoModule);
nodeRequire(zlibModule);
return true;
} catch {
return false;
Expand Down
16 changes: 10 additions & 6 deletions src/processors/obfProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,12 +227,16 @@ class ObfProcessor extends BaseProcessor {
const sourceButtons = boardData.buttons || [];

// Calculate page ID first (used to make button IDs unique)
const pageId =
_boardPath && _boardPath.endsWith('.obf') && !_boardPath.includes('/')
? _boardPath // Zip entry - use filename to match navigation paths
: boardData?.id
? String(boardData.id)
: _boardPath?.split('/').pop() || '';
const isZipEntry =
_boardPath &&
_boardPath.endsWith('.obf') &&
!_boardPath.includes('/') &&
!_boardPath.includes('\\');
const pageId = isZipEntry
? _boardPath // Zip entry - use filename to match navigation paths
: boardData?.id
? String(boardData.id)
: _boardPath?.split(/[/\\]/).pop() || '';

const buttons: AACButton[] = await Promise.all(
sourceButtons.map(async (btn: ObfButton): Promise<AACButton> => {
Expand Down
24 changes: 16 additions & 8 deletions src/processors/snapProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import Database from 'better-sqlite3';
import path from 'path';
import fs from 'fs';
import crypto from 'crypto';
import os from 'os';
import { SnapValidator } from '../validation/snapValidator';
import { ValidationResult } from '../validation/validationTypes';
import { ProcessorInput } from '../utils/io';
Expand Down Expand Up @@ -104,13 +105,20 @@ class SnapProcessor extends BaseProcessor {
async loadIntoTree(filePathOrBuffer: ProcessorInput): Promise<AACTree> {
await Promise.resolve();
const tree = new AACTree();
let tempDir: string | null = null;
const filePath =
typeof filePathOrBuffer === 'string'
typeof filePathOrBuffer !== 'string'
? (() => {
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'snap-'));
return path.join(tempDir, 'input.spb');
})()
: filePathOrBuffer;

if (typeof filePathOrBuffer !== 'string') {
const buffer = Buffer.isBuffer(filePathOrBuffer)
? filePathOrBuffer
: path.join(process.cwd(), 'temp.spb');

if (Buffer.isBuffer(filePathOrBuffer)) {
fs.writeFileSync(filePath, filePathOrBuffer);
: Buffer.from(filePathOrBuffer);
fs.writeFileSync(filePath, buffer);
}

let db: any = null;
Expand Down Expand Up @@ -722,11 +730,11 @@ class SnapProcessor extends BaseProcessor {
}

// Clean up temporary file if created from buffer
if (Buffer.isBuffer(filePathOrBuffer) && fs.existsSync(filePath)) {
if (tempDir && fs.existsSync(tempDir)) {
try {
fs.unlinkSync(filePath);
fs.rmSync(tempDir, { recursive: true, force: true });
} catch (e) {
console.warn('Failed to clean up temporary file:', e);
console.warn('Failed to clean up temporary files:', e);
}
}
}
Expand Down
30 changes: 26 additions & 4 deletions src/utils/io.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,33 @@ export type BinaryOutput = Buffer | Uint8Array;

let cachedFs: typeof import('fs') | null = null;
let cachedPath: typeof import('path') | null = null;
let cachedRequire: NodeRequire | null | undefined = undefined;

type NodeRequire = (id: string) => any;

function getNodeRequire(): NodeRequire {
if (cachedRequire === undefined) {
if (typeof require === 'function') {
cachedRequire = require;
} else if (typeof globalThis !== 'undefined') {
const maybeRequire = (globalThis as { require?: unknown }).require;
cachedRequire = typeof maybeRequire === 'function' ? (maybeRequire as NodeRequire) : null;
} else {
cachedRequire = null;
}
}
if (!cachedRequire) {
throw new Error('File system access is not available in this environment.');
}
return cachedRequire;
}

export function getFs(): typeof import('fs') {
if (!cachedFs) {
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
cachedFs = require('fs');
const nodeRequire = getNodeRequire();
const fsModule = 'fs';
cachedFs = nodeRequire(fsModule);
} catch {
throw new Error('File system access is not available in this environment.');
}
Expand All @@ -23,8 +44,9 @@ export function getFs(): typeof import('fs') {
export function getPath(): typeof import('path') {
if (!cachedPath) {
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
cachedPath = require('path');
const nodeRequire = getNodeRequire();
const pathModule = 'path';
cachedPath = nodeRequire(pathModule);
} catch {
throw new Error('Path utilities are not available in this environment.');
}
Expand Down
2 changes: 1 addition & 1 deletion test/obfProcessor.roundtrip.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import path from 'path';
import { ObfProcessor } from '../src/processors/obfProcessor';
import { AACTree, AACPage, AACButton } from '../src/core/treeStructure';

jest.setTimeout(30000);
jest.setTimeout(process.platform === 'win32' ? 60000 : 30000);

describe('OBFProcessor round-trip', () => {
const obfPath: string = path.join(__dirname, 'assets/obf/example.obf');
Expand Down
2 changes: 1 addition & 1 deletion test/processTexts.realworld.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { GridsetProcessor } from '../src/processors/gridsetProcessor';
import { SnapProcessor } from '../src/processors/snapProcessor';
import { TouchChatProcessor } from '../src/processors/touchchatProcessor';

jest.setTimeout(30000);
jest.setTimeout(process.platform === 'win32' ? 60000 : 30000);

describe('ProcessTexts with Real-World Data', () => {
const examplesDir = path.join(__dirname, '../examples');
Expand Down