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
18 changes: 17 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
# Changelog

Last updated: 2026-07-20 12:31 PM CDT
Last updated: 2026-07-20 12:42 PM CDT

All notable changes to this project are documented here.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Changed

- **Replaced `axios` with the built-in `fetch`** for calendar downloads and removed
the `axios` dependency. This eliminates axios from the plugin's fetch/parse path —
the surface behind axios' SSRF and prototype-pollution advisories (audit finding
**H3**). Calendar data is fetched with `fetch` and parsed from a string via
`node-ical`'s synchronous `parseICS`, so no HTTP client touches the response body.
- Error handling is now correct: `fetch` does not reject on HTTP error statuses,
so the response status is checked explicitly (the old `axios` code string-matched
a 404 error message that `fetch` never produces, and silently swallowed other
failures). Non-404 failures now surface a message to the user.
- Note: `node-ical` still pulls `axios@0.24` transitively, but it is not on the
plugin's code path (only its URL-fetch helpers use it, which the plugin doesn't
call). Fully removing it requires a `node-ical` major bump — deferred until the
parser test suite exists (audit H2); Dependabot will surface it.

### Added

- **CI workflow** (`.github/workflows/ci.yml`) on every PR and push to `main`:
Expand Down
34 changes: 19 additions & 15 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import "@logseq/libs";
import { BlockEntity, PageEntity, SettingSchemaDesc } from "@logseq/libs/dist/LSPlugin.user";
import ical from "node-ical";
import axios from "axios";
import {
getDateForPage,
getDateForPageWithoutBrackets,
} from "logseq-dateutils";
import moment from "moment-timezone";
import urlRegexSafe from 'url-regex-safe';

Check failure on line 9 in index.ts

View workflow job for this annotation

GitHub Actions / typecheck

Could not find a declaration file for module 'url-regex-safe'. '/home/runner/work/logseq-calendars-plugin/logseq-calendars-plugin/node_modules/url-regex-safe/lib/index.js' implicitly has an 'any' type.

let mainBlockUUID = ""
// const md = require('markdown-it')().use(require('markdown-it-mark'));
Expand Down Expand Up @@ -161,9 +160,9 @@
];
logseq.useSettingsSchema(settingsTemplate);

function sortDate(data) {

Check failure on line 163 in index.ts

View workflow job for this annotation

GitHub Actions / typecheck

Parameter 'data' implicitly has an 'any' type.
// Filter out events with invalid dates
const validEvents = data.filter(event => {

Check failure on line 165 in index.ts

View workflow job for this annotation

GitHub Actions / typecheck

Parameter 'event' implicitly has an 'any' type.
if (!event.start) {
return false;
}
Expand All @@ -174,7 +173,7 @@
return true;
});

const sorted = validEvents.sort(function (a, b) {

Check failure on line 176 in index.ts

View workflow job for this annotation

GitHub Actions / typecheck

Parameter 'b' implicitly has an 'any' type.

Check failure on line 176 in index.ts

View workflow job for this annotation

GitHub Actions / typecheck

Parameter 'a' implicitly has an 'any' type.
// Sort by absolute time (UTC), which corresponds to the displayed local time
// Since we display times in the user's local timezone, sorting by UTC
// gives us the correct chronological order
Expand All @@ -187,7 +186,7 @@
return sorted;
}

function shouldFilterDeclinedEvent(event, userEmail) {

Check failure on line 189 in index.ts

View workflow job for this annotation

GitHub Actions / typecheck

Parameter 'userEmail' implicitly has an 'any' type.

Check failure on line 189 in index.ts

View workflow job for this annotation

GitHub Actions / typecheck

Parameter 'event' implicitly has an 'any' type.
// Only filter if email is configured and feature is enabled
if (!userEmail || userEmail.trim() === "" || logseq.settings?.hideDeclinedEvents === false) {
return false;
Expand All @@ -203,7 +202,7 @@
const normalizedUserEmail = userEmail.trim().toLowerCase();

// Find user in attendee list
const userAttendee = attendees.find(attendee => {

Check failure on line 205 in index.ts

View workflow job for this annotation

GitHub Actions / typecheck

Parameter 'attendee' implicitly has an 'any' type.
if (!attendee.val) return false;
const attendeeEmail = attendee.val.replace('mailto:', '').toLowerCase();
return attendeeEmail === normalizedUserEmail;
Expand All @@ -218,7 +217,7 @@
return false;
}

function getAttendeeName(attendee) {

Check failure on line 220 in index.ts

View workflow job for this annotation

GitHub Actions / typecheck

Parameter 'attendee' implicitly has an 'any' type.
const cn = attendee?.params?.CN;
if (!cn) return null;
const trimmed = String(cn).trim();
Expand All @@ -238,7 +237,7 @@
// back to their email (shown plain, no link) unless the participantEmailFallback
// setting is disabled, in which case they are omitted.
// Excludes the user themselves (matched against userEmail). Empty list -> "".
function formatParticipants(event, statuses, userEmail) {

Check failure on line 240 in index.ts

View workflow job for this annotation

GitHub Actions / typecheck

Parameter 'event' implicitly has an 'any' type.
if (!event.attendee) return "";
const attendees = Array.isArray(event.attendee) ? event.attendee : [event.attendee];
const normalizedUserEmail = userEmail ? userEmail.trim().toLowerCase() : "";
Expand Down Expand Up @@ -717,23 +716,28 @@
logseq.App.showMsg("Fetching Calendar Items");

// Add cache-busting parameter to force fresh calendar data
const cacheBuster = `?nocache=${new Date().getTime()}`;
const urlWithCacheBuster = url.includes('?') ? `${url}&nocache=${new Date().getTime()}` : url + cacheBuster;
const nocache = `nocache=${new Date().getTime()}`;
const urlWithCacheBuster = url.includes("?") ? `${url}&${nocache}` : `${url}?${nocache}`;

// fetch (unlike the old axios call) does NOT reject on HTTP error statuses —
// only on network failure — so check response.ok explicitly.
const response = await fetch(urlWithCacheBuster);
if (!response.ok) {
if (response.status === 404) {
logseq.App.showMsg("Calendar not found: Check your URL");
} else {
logseq.App.showMsg(`Failed to fetch "${calendarName}" (HTTP ${response.status})`);
}
console.log(`Calendar fetch failed for ${calendarName}: ${response.status} ${response.statusText}`);
return;
}

let response2 = await axios.get(urlWithCacheBuster);
console.log(response2);
var hello = await rawParser(response2.data);
const rawData = await response.text();
const hello = await rawParser(rawData);
const date = await findDate(preferredDateFormat);
insertJournalBlocks(
hello,
preferredDateFormat,
calendarName,
date
);
insertJournalBlocks(hello, preferredDateFormat, calendarName, date);
} catch (err) {
if (`${err}` == `Error: Request failed with status code 404`) {
logseq.App.showMsg("Calendar not found: Check your URL");
}
logseq.App.showMsg(`Error fetching "${calendarName}". Check the URL and your connection.`);
console.log(err);
}
}
Expand Down
10 changes: 0 additions & 10 deletions package-lock.json

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

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
"license": "ISC",
"dependencies": {
"@logseq/libs": "0.0.6",
"axios": "^0.25.0",
"logseq-dateutils": "latest",
"moment": "^2.29.3",
"moment-timezone": "^0.5.34",
Expand Down
Loading