Skip to content

Commit b5db86f

Browse files
refactor: rollback SHA-256 with CRC32 for hash generation
1 parent cd0f459 commit b5db86f

2 files changed

Lines changed: 22 additions & 38 deletions

File tree

eslint.config.mjs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ export default [
5656
clearInterval: 'readonly',
5757

5858
// Browser APIs (client-side code)
59-
crypto: 'readonly',
6059
window: 'readonly',
6160
document: 'readonly',
6261
navigator: 'readonly',
@@ -69,7 +68,6 @@ export default [
6968
requestAnimationFrame: 'readonly',
7069
URLSearchParams: 'readonly',
7170
TextDecoder: 'readonly',
72-
TextEncoder: 'readonly',
7371
FileReader: 'readonly',
7472
DOMRect: 'readonly',
7573

packages/super-editor/src/core/super-converter/SuperConverter.js

Lines changed: 22 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as xmljs from 'xml-js';
22
import { v4 as uuidv4 } from 'uuid';
3+
import crc32 from 'buffer-crc32';
34
import { DocxExporter, exportSchemaToJson } from './exporter';
45
import { createDocumentJson, addDefaultStylesIfMissing } from './v2/importer/docxImporter.js';
56
import { deobfuscateFont, getArrayBufferFromUrl } from './helpers.js';
@@ -68,33 +69,6 @@ const collectRunDefaultProperties = (
6869
}
6970
};
7071

71-
/**
72-
* SHA-256 hash helpers using the Web Crypto API.
73-
* Works in all modern browsers and Node.js 20+.
74-
*/
75-
async function sha256Hex(bytes) {
76-
const hash = await crypto.subtle.digest('SHA-256', bytes);
77-
return Array.from(new Uint8Array(hash))
78-
.map((b) => b.toString(16).padStart(2, '0'))
79-
.join('')
80-
.toUpperCase();
81-
}
82-
83-
async function hashString(str) {
84-
return sha256Hex(new TextEncoder().encode(str));
85-
}
86-
87-
async function hashFile(fileSource) {
88-
if (fileSource instanceof ArrayBuffer) {
89-
return sha256Hex(fileSource);
90-
} else if (fileSource instanceof Blob || fileSource instanceof File) {
91-
return sha256Hex(await fileSource.arrayBuffer());
92-
} else if (fileSource instanceof Uint8Array) {
93-
return sha256Hex(fileSource);
94-
}
95-
return null;
96-
}
97-
9872
class SuperConverter {
9973
static allowedElements = Object.freeze({
10074
'w:document': 'doc',
@@ -778,32 +752,44 @@ class SuperConverter {
778752

779753
/**
780754
* Generate identifier hash from documentGuid and dcterms:created
781-
* Uses SHA-256 of the combined string for a compact identifier
755+
* Uses CRC32 of the combined string for a compact identifier
782756
* Only call when both documentGuid and timestamp exist
783757
* @returns {string} Hash identifier in format "HASH-XXXXXXXX"
784758
*/
785-
async #generateIdentifierHash() {
759+
#generateIdentifierHash() {
786760
const combined = `${this.documentGuid}|${this.getDocumentCreatedTimestamp()}`;
787-
const hash = await hashString(combined);
788-
return `HASH-${hash.substring(0, 8)}`;
761+
const buffer = Buffer.from(combined, 'utf8');
762+
const hash = crc32(buffer);
763+
return `HASH-${hash.toString('hex').toUpperCase()}`;
789764
}
790765

791766
/**
792767
* Generate content hash from file bytes
793-
* Uses SHA-256 of the raw file content for a stable identifier
768+
* Uses CRC32 of the raw file content for a stable identifier
794769
* @returns {Promise<string>} Hash identifier in format "HASH-XXXXXXXX"
795770
*/
796771
async #generateContentHash() {
797772
if (!this.fileSource) {
773+
// No file source available, generate a random hash (last resort)
798774
return `HASH-${uuidv4().replace(/-/g, '').substring(0, 8).toUpperCase()}`;
799775
}
800776

801777
try {
802-
const hash = await hashFile(this.fileSource);
803-
if (!hash) {
778+
let buffer;
779+
780+
if (Buffer.isBuffer(this.fileSource)) {
781+
buffer = this.fileSource;
782+
} else if (this.fileSource instanceof ArrayBuffer) {
783+
buffer = Buffer.from(this.fileSource);
784+
} else if (this.fileSource instanceof Blob || this.fileSource instanceof File) {
785+
const arrayBuffer = await this.fileSource.arrayBuffer();
786+
buffer = Buffer.from(arrayBuffer);
787+
} else {
804788
return `HASH-${uuidv4().replace(/-/g, '').substring(0, 8).toUpperCase()}`;
805789
}
806-
return `HASH-${hash.substring(0, 8)}`;
790+
791+
const hash = crc32(buffer);
792+
return `HASH-${hash.toString('hex').toUpperCase()}`;
807793
} catch (e) {
808794
console.warn('[super-converter] Could not generate content hash:', e);
809795
return `HASH-${uuidv4().replace(/-/g, '').substring(0, 8).toUpperCase()}`;
@@ -835,7 +821,7 @@ class SuperConverter {
835821

836822
if (hasGuid && hasTimestamp) {
837823
// Both exist: use identifierHash
838-
this.documentUniqueIdentifier = await this.#generateIdentifierHash();
824+
this.documentUniqueIdentifier = this.#generateIdentifierHash();
839825
} else {
840826
// Missing one or both: use contentHash for stability (same file = same hash)
841827
// But generate missing metadata so re-exported file will have complete metadata

0 commit comments

Comments
 (0)