@rmdes/indiekit-endpoint-microsub 1.0.64 → 1.0.66

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,109 +0,0 @@
1
- /**
2
- * Read state tracking utilities
3
- * @module storage/read-state
4
- */
5
-
6
- import { markItemsRead, markItemsUnread, getUnreadCount } from "./items-read-state.js";
7
-
8
- /**
9
- * Mark entries as read for a user
10
- * @param {object} application - Indiekit application
11
- * @param {string} channelUid - Channel UID
12
- * @param {Array} entries - Entry IDs to mark as read
13
- * @param {string} userId - User ID
14
- * @returns {Promise<number>} Number of entries marked
15
- */
16
- export async function markRead(application, channelUid, entries, userId) {
17
- const channelsCollection = application.collections.get("microsub_channels");
18
- const channel = await channelsCollection.findOne({ uid: channelUid });
19
-
20
- if (!channel) {
21
- return 0;
22
- }
23
-
24
- return markItemsRead(application, channel._id, entries, userId);
25
- }
26
-
27
- /**
28
- * Mark entries as unread for a user
29
- * @param {object} application - Indiekit application
30
- * @param {string} channelUid - Channel UID
31
- * @param {Array} entries - Entry IDs to mark as unread
32
- * @param {string} userId - User ID
33
- * @returns {Promise<number>} Number of entries marked
34
- */
35
- export async function markUnread(application, channelUid, entries, userId) {
36
- const channelsCollection = application.collections.get("microsub_channels");
37
- const channel = await channelsCollection.findOne({ uid: channelUid });
38
-
39
- if (!channel) {
40
- return 0;
41
- }
42
-
43
- return markItemsUnread(application, channel._id, entries, userId);
44
- }
45
-
46
- /**
47
- * Get unread count for a channel
48
- * @param {object} application - Indiekit application
49
- * @param {string} channelUid - Channel UID
50
- * @param {string} userId - User ID
51
- * @returns {Promise<number>} Unread count
52
- */
53
- export async function getChannelUnreadCount(application, channelUid, userId) {
54
- const channelsCollection = application.collections.get("microsub_channels");
55
- const channel = await channelsCollection.findOne({ uid: channelUid });
56
-
57
- if (!channel) {
58
- return 0;
59
- }
60
-
61
- return getUnreadCount(application, channel._id, userId);
62
- }
63
-
64
- /**
65
- * Get unread counts for all channels
66
- * @param {object} application - Indiekit application
67
- * @param {string} userId - User ID
68
- * @returns {Promise<Map>} Map of channel UID to unread count
69
- */
70
- export async function getAllUnreadCounts(application, userId) {
71
- const channelsCollection = application.collections.get("microsub_channels");
72
- const itemsCollection = application.collections.get("microsub_items");
73
-
74
- // Aggregate unread counts per channel
75
- const pipeline = [
76
- {
77
- $match: {
78
- readBy: { $ne: userId },
79
- },
80
- },
81
- {
82
- $group: {
83
- _id: "$channelId",
84
- count: { $sum: 1 },
85
- },
86
- },
87
- ];
88
-
89
- const results = await itemsCollection.aggregate(pipeline).toArray();
90
-
91
- // Get channel UIDs
92
- const channelIds = results.map((r) => r._id);
93
- const channels = await channelsCollection
94
- .find({ _id: { $in: channelIds } })
95
- .toArray();
96
-
97
- const channelMap = new Map(channels.map((c) => [c._id.toString(), c.uid]));
98
-
99
- // Build result map
100
- const unreadCounts = new Map();
101
- for (const result of results) {
102
- const uid = channelMap.get(result._id.toString());
103
- if (uid) {
104
- unreadCounts.set(uid, result.count);
105
- }
106
- }
107
-
108
- return unreadCounts;
109
- }
@@ -1,129 +0,0 @@
1
- /**
2
- * WebSub hub discovery
3
- * @module websub/discovery
4
- */
5
-
6
- /**
7
- * Discover WebSub hub from HTTP response headers and content
8
- * @param {object} response - Fetch response object
9
- * @param {string} content - Response body content
10
- * @returns {object|undefined} WebSub info { hub, self }
11
- */
12
- export function discoverWebsub(response, content) {
13
- // Try to find hub and self URLs from Link headers first
14
- const linkHeader = response.headers.get("link");
15
- const fromHeaders = linkHeader ? parseLinkHeader(linkHeader) : {};
16
-
17
- // Fall back to content parsing
18
- const fromContent = parseContentForLinks(content);
19
-
20
- const hub = fromHeaders.hub || fromContent.hub;
21
- const self = fromHeaders.self || fromContent.self;
22
-
23
- if (hub) {
24
- return { hub, self };
25
- }
26
-
27
- return;
28
- }
29
-
30
- /**
31
- * Parse Link header for hub and self URLs
32
- * @param {string} linkHeader - Link header value
33
- * @returns {object} { hub, self }
34
- */
35
- function parseLinkHeader(linkHeader) {
36
- const result = {};
37
- const links = linkHeader.split(",");
38
-
39
- for (const link of links) {
40
- const parts = link.trim().split(";");
41
- if (parts.length < 2) continue;
42
-
43
- const urlMatch = parts[0].match(/<([^>]+)>/);
44
- if (!urlMatch) continue;
45
-
46
- const url = urlMatch[1];
47
- const relationship = parts
48
- .slice(1)
49
- .find((p) => p.trim().startsWith("rel="))
50
- ?.match(/rel=["']?([^"'\s;]+)["']?/)?.[1];
51
-
52
- if (relationship === "hub") {
53
- result.hub = url;
54
- } else if (relationship === "self") {
55
- result.self = url;
56
- }
57
- }
58
-
59
- return result;
60
- }
61
-
62
- /**
63
- * Parse content for hub and self URLs (Atom, RSS, HTML)
64
- * @param {string} content - Response body
65
- * @returns {object} { hub, self }
66
- */
67
- function parseContentForLinks(content) {
68
- const result = {};
69
-
70
- // Try HTML <link> elements
71
- const htmlHubMatch = content.match(
72
- /<link[^>]+rel=["']?hub["']?[^>]+href=["']([^"']+)["']/i,
73
- );
74
- if (htmlHubMatch) {
75
- result.hub = htmlHubMatch[1];
76
- }
77
-
78
- const htmlSelfMatch = content.match(
79
- /<link[^>]+rel=["']?self["']?[^>]+href=["']([^"']+)["']/i,
80
- );
81
- if (htmlSelfMatch) {
82
- result.self = htmlSelfMatch[1];
83
- }
84
-
85
- // Also try the reverse order (href before rel)
86
- if (!result.hub) {
87
- const htmlHubMatch2 = content.match(
88
- /<link[^>]+href=["']([^"']+)["'][^>]+rel=["']?hub["']?/i,
89
- );
90
- if (htmlHubMatch2) {
91
- result.hub = htmlHubMatch2[1];
92
- }
93
- }
94
-
95
- if (!result.self) {
96
- const htmlSelfMatch2 = content.match(
97
- /<link[^>]+href=["']([^"']+)["'][^>]+rel=["']?self["']?/i,
98
- );
99
- if (htmlSelfMatch2) {
100
- result.self = htmlSelfMatch2[1];
101
- }
102
- }
103
-
104
- // Try Atom <link> elements
105
- if (!result.hub) {
106
- const atomHubMatch = content.match(
107
- /<atom:link[^>]+rel=["']?hub["']?[^>]+href=["']([^"']+)["']/i,
108
- );
109
- if (atomHubMatch) {
110
- result.hub = atomHubMatch[1];
111
- }
112
- }
113
-
114
- return result;
115
- }
116
-
117
- /**
118
- * Check if a hub URL is valid
119
- * @param {string} hubUrl - Hub URL to validate
120
- * @returns {boolean} Whether the URL is valid
121
- */
122
- export function isValidHubUrl(hubUrl) {
123
- try {
124
- const url = new URL(hubUrl);
125
- return url.protocol === "https:" || url.protocol === "http:";
126
- } catch {
127
- return false;
128
- }
129
- }