chore(core): fix references to Buffer

This commit is contained in:
Elian Doran 2026-01-06 12:16:38 +02:00
parent 8149460547
commit d717a89163
No known key found for this signature in database
12 changed files with 53 additions and 27 deletions

View File

@ -68,7 +68,7 @@ export interface EtapiTokenRow {
export interface BlobRow {
blobId: string;
content: string | Buffer;
content: string | Uint8Array;
contentLength: number;
dateModified: string;
utcDateModified: string;
@ -137,6 +137,6 @@ export interface NoteRow {
dateModified?: string;
utcDateCreated?: string;
utcDateModified?: string;
content?: string | Buffer;
content?: string | Uint8Array;
}

View File

@ -50,7 +50,7 @@ export interface RevisionPojo {
utcDateLastEdited?: string;
utcDateCreated?: string;
utcDateModified?: string;
content?: string | Buffer<ArrayBufferLike>;
content?: string | Uint8Array;
contentLength?: number;
}

View File

@ -18,7 +18,7 @@ export interface EntityChange {
export interface EntityRow {
isDeleted?: boolean;
content?: Buffer | string;
content?: Uint8Array | string;
}
export interface EntityChangeRecord {

View File

@ -10,6 +10,7 @@ import utils from "../../services/utils.js";
import becca from "../becca.js";
import type { ConstructorData,default as Becca } from "../becca-interface.js";
import { getSql } from "src/services/sql";
import { concat2, encodeUtf8, unwrapStringOrBuffer, wrapStringOrBuffer } from "src/services/utils/binary";
interface ContentOpts {
forceSave?: boolean;
@ -137,7 +138,7 @@ abstract class AbstractBeccaEntity<T extends AbstractBeccaEntity<T>> {
return this;
}
protected _setContent(content: string | Buffer, opts: ContentOpts = {}) {
protected _setContent(content: string | Uint8Array, opts: ContentOpts = {}) {
// client code asks to save entity even if blobId didn't change (something else was changed)
opts.forceSave = !!opts.forceSave;
opts.forceFrontendReload = !!opts.forceFrontendReload;
@ -148,9 +149,9 @@ abstract class AbstractBeccaEntity<T extends AbstractBeccaEntity<T>> {
}
if (this.hasStringContent()) {
content = content.toString();
content = unwrapStringOrBuffer(content);
} else {
content = Buffer.isBuffer(content) ? content : Buffer.from(content);
content = wrapStringOrBuffer(content);
}
const unencryptedContentForHashCalculation = this.getUnencryptedContentForHashCalculation(content);
@ -202,17 +203,21 @@ abstract class AbstractBeccaEntity<T extends AbstractBeccaEntity<T>> {
sql.execute("DELETE FROM entity_changes WHERE entityName = 'blobs' AND entityId = ?", [oldBlobId]);
}
private getUnencryptedContentForHashCalculation(unencryptedContent: Buffer | string) {
private getUnencryptedContentForHashCalculation(unencryptedContent: Uint8Array | string) {
if (this.isProtected) {
// a "random" prefix makes sure that the calculated hash/blobId is different for a decrypted/encrypted content
const encryptedPrefixSuffix = "t$[nvQg7q)&_ENCRYPTED_?M:Bf&j3jr_";
return Buffer.isBuffer(unencryptedContent) ? Buffer.concat([Buffer.from(encryptedPrefixSuffix), unencryptedContent]) : `${encryptedPrefixSuffix}${unencryptedContent}`;
if (typeof unencryptedContent === "string") {
return `${encryptedPrefixSuffix}${unencryptedContent}`;
} else {
return concat2(encodeUtf8(encryptedPrefixSuffix), unencryptedContent)
}
}
return unencryptedContent;
}
private saveBlob(content: string | Buffer, unencryptedContentForHashCalculation: string | Buffer, opts: ContentOpts = {}) {
private saveBlob(content: string | Uint8Array, unencryptedContentForHashCalculation: string | Uint8Array, opts: ContentOpts = {}) {
/*
* We're using the unencrypted blob for the hash calculation, because otherwise the random IV would
* cause every content blob to be unique which would balloon the database size (esp. with revisioning).
@ -260,16 +265,16 @@ abstract class AbstractBeccaEntity<T extends AbstractBeccaEntity<T>> {
return newBlobId;
}
protected _getContent(): string | Buffer {
protected _getContent(): string | Uint8Array {
const sql = getSql();
const row = sql.getRow<{ content: string | Buffer }>(/*sql*/`SELECT content FROM blobs WHERE blobId = ?`, [this.blobId]);
const row = sql.getRow<{ content: string | Uint8Array }>(/*sql*/`SELECT content FROM blobs WHERE blobId = ?`, [this.blobId]);
if (!row) {
const constructorData = this.constructor as unknown as ConstructorData<T>;
throw new Error(`Cannot find content for ${constructorData.primaryKeyName} '${(this as any)[constructorData.primaryKeyName]}', blobId '${this.blobId}'`);
}
return blobService.processContent(row.content, this.isProtected || false, this.hasStringContent()) as string | Buffer;
return blobService.processContent(row.content, this.isProtected || false, this.hasStringContent()) as string | Uint8Array;
}
/**

View File

@ -136,11 +136,11 @@ class BAttachment extends AbstractBeccaEntity<BAttachment> {
}
}
getContent(): Buffer {
return this._getContent() as Buffer;
getContent(): Uint8Array {
return this._getContent() as Uint8Array;
}
setContent(content: string | Buffer, opts?: ContentOpts) {
setContent(content: string | Uint8Array, opts?: ContentOpts) {
this._setContent(content, opts);
}

View File

@ -13,7 +13,7 @@ class BBlob extends AbstractBeccaEntity<BBlob> {
return ["blobId", "content"];
}
content!: string | Buffer;
content!: string | Uint8Array;
contentLength!: number;
constructor(row: BlobRow) {

View File

@ -251,7 +251,7 @@ class BNote extends AbstractBeccaEntity<BNote> {
}
}
setContent(content: Buffer | string, opts: ContentOpts = {}) {
setContent(content: Uint8Array | string, opts: ContentOpts = {}) {
this._setContent(content, opts);
eventService.emit(eventService.NOTE_CONTENT_CHANGE, { entity: this });

View File

@ -42,7 +42,7 @@ class BRevision extends AbstractBeccaEntity<BRevision> {
dateLastEdited?: string;
utcDateLastEdited?: string;
contentLength?: number;
content?: string | Buffer;
content?: string | Uint8Array;
constructor(row: RevisionRow, titleDecrypted = false) {
super();
@ -95,7 +95,7 @@ class BRevision extends AbstractBeccaEntity<BRevision> {
*
* This is the same approach as is used for Note's content.
*/
getContent(): string | Buffer {
getContent(): string | Uint8Array {
return this._getContent();
}
@ -120,7 +120,7 @@ class BRevision extends AbstractBeccaEntity<BRevision> {
}
}
setContent(content: string | Buffer, opts: ContentOpts = {}) {
setContent(content: string | Uint8Array, opts: ContentOpts = {}) {
this._setContent(content, opts);
}

View File

@ -23,13 +23,13 @@ function getBlobPojo(entityName: string, entityId: string, opts?: { preview: boo
if (!entity.hasStringContent()) {
pojo.content = null;
} else {
pojo.content = processContent(pojo.content, !!entity.isProtected, true) as string | Buffer;
pojo.content = processContent(pojo.content, !!entity.isProtected, true) as string | Uint8Array;
}
return pojo;
}
function processContent(content: Buffer | Uint8Array | string | null, isProtected: boolean, isStringContent: boolean) {
function processContent(content: Uint8Array | string | null, isProtected: boolean, isStringContent: boolean) {
if (isProtected) {
if (protectedSessionService.isProtectedSessionAvailable()) {
content = content === null ? null : protectedSessionService.decrypt(content as Uint8Array);
@ -47,7 +47,7 @@ function processContent(content: Buffer | Uint8Array | string | null, isProtecte
// IIRC a zero-sized buffer can be returned as null from the database
if (content === null) {
// this will force de/encryption
content = Buffer.alloc(0);
content = new Uint8Array(0);
}
return content;

View File

@ -1,5 +1,5 @@
import { getLog } from "../log.js";
import { concat2, decodeBase64, decodeUtf8, encodeBase64 } from "../utils/binary.js";
import { concat2, decodeBase64, decodeUtf8, encodeBase64, encodeUtf8 } from "../utils/binary.js";
import { getCrypto } from "./crypto.js";
function arraysIdentical(a: any[] | Uint8Array, b: any[] | Uint8Array) {
@ -55,7 +55,7 @@ function decrypt(key: Uint8Array, cipherText: string | Uint8Array): Uint8Array |
}
if (!key) {
return Uint8Array.from("[protected]");
return encodeUtf8("[protected]");
}
try {

View File

@ -21,7 +21,7 @@ export interface RunResult {
export interface DatabaseProvider {
loadFromFile(path: string, isReadOnly: boolean): void;
loadFromMemory(): void;
loadFromBuffer(buffer: NonSharedBuffer): void;
loadFromBuffer(buffer: Uint8Array): void;
backup(destinationFile: string): void;
prepare(query: string): Statement;
transaction<T>(func: (statement: Statement) => T): Transaction;

View File

@ -1,4 +1,5 @@
const utf8Decoder = new TextDecoder("utf-8");
const utf8Encoder = new TextEncoder();
export function concat2(a: Uint8Array, b: Uint8Array): Uint8Array {
const out = new Uint8Array(a.length + b.length);
@ -33,3 +34,23 @@ export function decodeBase64(base64: string): Uint8Array {
export function decodeUtf8(bytes: Uint8Array) {
return utf8Decoder.decode(bytes);
}
export function encodeUtf8(string: string) {
return utf8Encoder.encode(string);
}
export function unwrapStringOrBuffer(stringOrBuffer: string | Uint8Array) {
if (typeof stringOrBuffer === "string") {
return stringOrBuffer;
} else {
return decodeUtf8(stringOrBuffer);
}
}
export function wrapStringOrBuffer(stringOrBuffer: string | Uint8Array) {
if (typeof stringOrBuffer === "string") {
return encodeUtf8(stringOrBuffer);
} else {
return stringOrBuffer;
}
}