Skip to content
This repository was archived by the owner on Jun 24, 2025. It is now read-only.

chore: fix lint issues in src/share #1384

Merged
merged 9 commits into from
Mar 23, 2025
2 changes: 1 addition & 1 deletion src/services/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ export function processStringOrBuffer(data: string | Buffer | null) {
}
}

export function safeExtractMessageAndStackFromError(err: unknown) {
export function safeExtractMessageAndStackFromError(err: unknown): [errMessage: string, errStack: string | undefined] {
return (err instanceof Error) ? [err.message, err.stack] as const : ["Unknown Error", undefined] as const;
}

Expand Down
6 changes: 4 additions & 2 deletions src/share/routes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { beforeAll, beforeEach, describe, expect, it } from "vitest";
import supertest from "supertest";
import { initializeTranslations } from "../services/i18n.js";
import type { Application, Request, Response, NextFunction } from "express";
import { safeExtractMessageAndStackFromError } from "../services/utils.js";

let app: Application;

Expand All @@ -11,8 +12,9 @@ describe("Share API test", () => {
beforeAll(async () => {
initializeTranslations();
app = (await import("../app.js")).default;
app.use((err: any, req: Request, res: Response, next: NextFunction) => {
if (err.message.includes("Cannot set headers after they are sent to the client")) {
app.use((err: unknown, req: Request, res: Response, next: NextFunction) => {
const [errMessage] = safeExtractMessageAndStackFromError(err)
if (errMessage.includes("Cannot set headers after they are sent to the client")) {
cannotSetHeadersCount++;
}

Expand Down
37 changes: 22 additions & 15 deletions src/share/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import log from "../services/log.js";
import type SNote from "./shaca/entities/snote.js";
import type SBranch from "./shaca/entities/sbranch.js";
import type SAttachment from "./shaca/entities/sattachment.js";
import utils from "../services/utils.js";
import utils, { safeExtractMessageAndStackFromError } from "../services/utils.js";
import options from "../services/options.js";

function getSharedSubTreeRoot(note: SNote): { note?: SNote; branch?: SBranch } {
Expand Down Expand Up @@ -115,7 +115,14 @@ function renderImageAttachment(image: SNote, res: Response, attachmentName: stri
svgString = content;
} else {
// backwards compatibility, before attachments, the SVG was stored in the main note content as a separate key
const contentSvg = image.getJsonContentSafely()?.svg;
const possibleSvgContent = image.getJsonContentSafely();

const contentSvg = (typeof possibleSvgContent === "object"
&& possibleSvgContent !== null
&& "svg" in possibleSvgContent
&& typeof possibleSvgContent.svg === "string")
? possibleSvgContent.svg
: null;
Comment on lines +120 to +125
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is quite hard to read, I would prefer extracting it in a method and using if statements and writing a test for it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume you mean moving this as method into SNote class?

something like this?
It still looks ugly, but not much we can do about it I guess.
As I've noted in my commit message, using a schema validator like zod would make life a lot easier for these types of situation (but that would be something for the future, not this PR I'd say).

snote.ts

        getSvgContent() {
            const possibleSvgContent = this.getJsonContentSafely();

            return (typeof possibleSvgContent === "object"
                && possibleSvgContent !== null
                && "svg" in possibleSvgContent
                && typeof possibleSvgContent.svg === "string")
                    ? possibleSvgContent.svg
                    : null;
        }

routes.ts

        const contentSvg = image.getSvgContent();

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pano9000 , seems fine, but I would still break it into multiple statements, something along the lines of:

getSvgContent() {
    const possibleSvgContent = this.getJsonContentSafely();
    if (!possibleSvgContent || typeof possibleSvgContent !== "object") {
        return null;
    }

    if (!("svg" in possibleSvgContent) || typeof possibleSvgContent.svg !== "string") {
        return;
    }


    return possibleSvgContent.svg;
}


if (contentSvg) {
svgString = contentSvg;
Expand Down Expand Up @@ -184,8 +191,9 @@ function register(router: Router) {
res.send(ejsResult);
useDefaultView = false; // Rendering went okay, don't use default view
}
} catch (e: any) {
log.error(`Rendering user provided share template (${templateId}) threw exception ${e.message} with stacktrace: ${e.stack}`);
} catch (e: unknown) {
const [errMessage, errStack] = safeExtractMessageAndStackFromError(e);
log.error(`Rendering user provided share template (${templateId}) threw exception ${errMessage} with stacktrace: ${errStack}`);
}
}
}
Expand All @@ -195,7 +203,7 @@ function register(router: Router) {
}
}

router.get("/share/", (req, res, next) => {
router.get("/share/", (req, res) => {
if (req.path.substr(-1) !== "/") {
res.redirect("../share/");
return;
Expand All @@ -211,7 +219,7 @@ function register(router: Router) {
renderNote(shaca.shareRootNote, req, res);
});

router.get("/share/:shareId", (req, res, next) => {
router.get("/share/:shareId", (req, res) => {
shacaLoader.ensureLoad();

const { shareId } = req.params;
Expand All @@ -221,7 +229,7 @@ function register(router: Router) {
renderNote(note, req, res);
});

router.get("/share/api/notes/:noteId", (req, res, next) => {
router.get("/share/api/notes/:noteId", (req, res) => {
shacaLoader.ensureLoad();
let note: SNote | boolean;

Expand All @@ -234,7 +242,7 @@ function register(router: Router) {
res.json(note.getPojo());
});

router.get("/share/api/notes/:noteId/download", (req, res, next) => {
router.get("/share/api/notes/:noteId/download", (req, res) => {
shacaLoader.ensureLoad();

let note: SNote | boolean;
Expand All @@ -256,7 +264,7 @@ function register(router: Router) {
});

// :filename is not used by trilium, but instead used for "save as" to assign a human-readable filename
router.get("/share/api/images/:noteId/:filename", (req, res, next) => {
router.get("/share/api/images/:noteId/:filename", (req, res) => {
shacaLoader.ensureLoad();

let image: SNote | boolean;
Expand All @@ -282,7 +290,7 @@ function register(router: Router) {
});

// :filename is not used by trilium, but instead used for "save as" to assign a human-readable filename
router.get("/share/api/attachments/:attachmentId/image/:filename", (req, res, next) => {
router.get("/share/api/attachments/:attachmentId/image/:filename", (req, res) => {
shacaLoader.ensureLoad();

let attachment: SAttachment | boolean;
Expand All @@ -300,7 +308,7 @@ function register(router: Router) {
}
});

router.get("/share/api/attachments/:attachmentId/download", (req, res, next) => {
router.get("/share/api/attachments/:attachmentId/download", (req, res) => {
shacaLoader.ensureLoad();

let attachment: SAttachment | boolean;
Expand All @@ -322,7 +330,7 @@ function register(router: Router) {
});

// used for PDF viewing
router.get("/share/api/notes/:noteId/view", (req, res, next) => {
router.get("/share/api/notes/:noteId/view", (req, res) => {
shacaLoader.ensureLoad();

let note: SNote | boolean;
Expand All @@ -340,19 +348,18 @@ function register(router: Router) {
});

// Used for searching, require noteId so we know the subTreeRoot
router.get("/share/api/notes", (req, res, next) => {
router.get("/share/api/notes", (req, res) => {
shacaLoader.ensureLoad();

const ancestorNoteId = req.query.ancestorNoteId ?? "_share";
let note;

if (typeof ancestorNoteId !== "string") {
res.status(400).json({ message: "'ancestorNoteId' parameter is mandatory." });
return;
}

// This will automatically return if no ancestorNoteId is provided and there is no shareIndex
if (!(note = checkNoteAccess(ancestorNoteId, req, res))) {
if (!checkNoteAccess(ancestorNoteId, req, res)) {
return;
}

Expand Down
2 changes: 1 addition & 1 deletion src/share/shaca/entities/sattachment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class SAttachment extends AbstractShacaEntity {
}
}

let content = row.content;
const content = row.content;

if (this.hasStringContent()) {
return content === null ? "" : content.toString("utf-8");
Expand Down
4 changes: 2 additions & 2 deletions src/share/shaca/entities/snote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ class SNote extends AbstractShacaEntity {
}
}

let content = row.content;
const content = row.content;

if (this.hasStringContent()) {
return content === null ? "" : content.toString("utf-8");
Expand Down Expand Up @@ -212,7 +212,7 @@ class SNote extends AbstractShacaEntity {
/**
* @throws Error in case of invalid JSON
*/
getJsonContent(): any | null {
getJsonContent(): unknown | null {
const content = this.getContent();

if (typeof content !== "string" || !content || !content.trim()) {
Expand Down
2 changes: 1 addition & 1 deletion src/share/shaca/shaca_loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ function ensureLoad() {

eventService.subscribe(
[eventService.ENTITY_CREATED, eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED, eventService.ENTITY_CHANGE_SYNCED, eventService.ENTITY_DELETE_SYNCED],
({ entityName, entity }) => {
() => {
shaca.reset();
}
);
Expand Down