Skip to content

Commit b827aa2

Browse files
committed
fix: replace Node.js Buffer APIs with browser-native alternatives
Buffer.from, Buffer.isBuffer, and buffer-crc32 crash in browser/CDN environments. Use Uint8Array, ArrayBuffer.isView, TextEncoder, and a custom CRC32 table instead.
1 parent f08c936 commit b827aa2

3 files changed

Lines changed: 48 additions & 29 deletions

File tree

packages/super-editor/src/core/DocxZipper.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as xmljs from 'xml-js';
22
import JSZip from 'jszip';
3-
import { getContentTypesFromXml } from './super-converter/helpers.js';
3+
import { getContentTypesFromXml, base64ToUint8Array } from './super-converter/helpers.js';
44
import { ensureXmlString, isXmlLike } from './encoding-helpers.js';
55

66
/**
@@ -303,7 +303,7 @@ class DocxZipper {
303303
});
304304

305305
Object.keys(media).forEach((path) => {
306-
const binaryData = Buffer.from(media[path], 'base64');
306+
const binaryData = base64ToUint8Array(media[path]);
307307
zip.file(path, binaryData);
308308
});
309309

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

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
/* global TextEncoder */
12
import * as xmljs from 'xml-js';
23
import { v4 as uuidv4 } from 'uuid';
3-
import crc32 from 'buffer-crc32';
44
import { DocxExporter, exportSchemaToJson } from './exporter';
55
import { createDocumentJson, addDefaultStylesIfMissing } from './v2/importer/docxImporter.js';
6-
import { deobfuscateFont, getArrayBufferFromUrl } from './helpers.js';
6+
import { deobfuscateFont, getArrayBufferFromUrl, computeCrc32Hex } from './helpers.js';
77
import { baseNumbering } from './v2/exporter/helpers/base-list.definitions.js';
88
import { DEFAULT_CUSTOM_XML, DEFAULT_DOCX_DEFS } from './exporter-docx-defs.js';
99
import {
@@ -758,9 +758,8 @@ class SuperConverter {
758758
*/
759759
#generateIdentifierHash() {
760760
const combined = `${this.documentGuid}|${this.getDocumentCreatedTimestamp()}`;
761-
const buffer = Buffer.from(combined, 'utf8');
762-
const hash = crc32(buffer);
763-
return `HASH-${hash.toString('hex').toUpperCase()}`;
761+
const data = new TextEncoder().encode(combined);
762+
return `HASH-${computeCrc32Hex(data).toUpperCase()}`;
764763
}
765764

766765
/**
@@ -775,21 +774,21 @@ class SuperConverter {
775774
}
776775

777776
try {
778-
let buffer;
777+
let data;
779778

780-
if (Buffer.isBuffer(this.fileSource)) {
781-
buffer = this.fileSource;
779+
if (ArrayBuffer.isView(this.fileSource)) {
780+
const view = this.fileSource;
781+
data = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
782782
} else if (this.fileSource instanceof ArrayBuffer) {
783-
buffer = Buffer.from(this.fileSource);
783+
data = new Uint8Array(this.fileSource);
784784
} else if (this.fileSource instanceof Blob || this.fileSource instanceof File) {
785785
const arrayBuffer = await this.fileSource.arrayBuffer();
786-
buffer = Buffer.from(arrayBuffer);
786+
data = new Uint8Array(arrayBuffer);
787787
} else {
788788
return `HASH-${uuidv4().replace(/-/g, '').substring(0, 8).toUpperCase()}`;
789789
}
790790

791-
const hash = crc32(buffer);
792-
return `HASH-${hash.toString('hex').toUpperCase()}`;
791+
return `HASH-${computeCrc32Hex(data).toUpperCase()}`;
793792
} catch (e) {
794793
console.warn('[super-converter] Could not generate content hash:', e);
795794
return `HASH-${uuidv4().replace(/-/g, '').substring(0, 8).toUpperCase()}`;

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

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,38 @@
11
import { parseSizeUnit } from '../utilities/index.js';
22
import { xml2js } from 'xml-js';
33

4+
// --- Browser-compatible CRC32 (replaces buffer-crc32 to avoid Node.js Buffer dependency) ---
5+
const CRC32_TABLE = new Uint32Array(256);
6+
for (let i = 0; i < 256; i++) {
7+
let c = i;
8+
for (let j = 0; j < 8; j++) {
9+
c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
10+
}
11+
CRC32_TABLE[i] = c;
12+
}
13+
14+
/**
15+
* Compute CRC32 of a Uint8Array and return as 8-char lowercase hex string.
16+
* Drop-in replacement for `buffer-crc32(buf).toString('hex')`.
17+
*/
18+
function computeCrc32Hex(data) {
19+
let crc = 0xffffffff;
20+
for (let i = 0; i < data.length; i++) {
21+
crc = CRC32_TABLE[(crc ^ data[i]) & 0xff] ^ (crc >>> 8);
22+
}
23+
return ((crc ^ 0xffffffff) >>> 0).toString(16).padStart(8, '0');
24+
}
25+
26+
/** Decode a base64 string to Uint8Array (works in both Node 16+ and browsers). */
27+
function base64ToUint8Array(base64) {
28+
const binary = atob(base64);
29+
const bytes = new Uint8Array(binary.length);
30+
for (let i = 0; i < binary.length; i++) {
31+
bytes[i] = binary.charCodeAt(i);
32+
}
33+
return bytes;
34+
}
35+
436
// CSS pixels per inch; used to convert between Word's inch-based measurements and DOM pixels.
537
const PIXELS_PER_INCH = 96;
638

@@ -276,21 +308,7 @@ const getArrayBufferFromUrl = async (input) => {
276308
// If this is a data URI we need only the payload portion
277309
const base64Payload = isDataUri ? trimmed.split(',', 2)[1] : trimmed.replace(/\s/g, '');
278310

279-
try {
280-
if (typeof globalThis.atob === 'function') {
281-
const binary = globalThis.atob(base64Payload);
282-
const bytes = new Uint8Array(binary.length);
283-
for (let i = 0; i < binary.length; i++) {
284-
bytes[i] = binary.charCodeAt(i);
285-
}
286-
return bytes.buffer;
287-
}
288-
} catch (err) {
289-
console.warn('atob failed, falling back to Buffer:', err);
290-
}
291-
292-
const buf = Buffer.from(base64Payload, 'base64');
293-
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
311+
return base64ToUint8Array(base64Payload).buffer;
294312
};
295313

296314
const getContentTypesFromXml = (contentTypesXml) => {
@@ -620,4 +638,6 @@ export {
620638
convertSizeToCSS,
621639
resolveShadingFillColor,
622640
resolveOpcTargetPath,
641+
computeCrc32Hex,
642+
base64ToUint8Array,
623643
};

0 commit comments

Comments
 (0)