@commonpub/layer 0.3.23 → 0.3.25

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.
package/README.md CHANGED
@@ -47,13 +47,13 @@ Create `server/utils/config.ts` to load the config on the server side. See `apps
47
47
 
48
48
  ## What's Included
49
49
 
50
- ### Pages (20+ routes)
50
+ ### Pages (70+ routes)
51
51
 
52
52
  Content CRUD, hub feeds, learning paths, docs sites, admin panel, federation management, user profiles, messaging, notifications, search, and more.
53
53
 
54
- ### Components (30+)
54
+ ### Components (100+)
55
55
 
56
- Content editor (`CpubEditor`), content cards, author rows, comment sections, engagement bars, federation UI, notification items, message threads, and more.
56
+ Content editor (`CpubEditor`), content cards, author rows, comment sections, engagement bars, federation UI, notification items, message threads, block renderers, and more.
57
57
 
58
58
  ### Composables (19)
59
59
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commonpub/layer",
3
- "version": "0.3.23",
3
+ "version": "0.3.25",
4
4
  "type": "module",
5
5
  "main": "./nuxt.config.ts",
6
6
  "files": [
@@ -44,15 +44,15 @@
44
44
  "vue": "^3.4.0",
45
45
  "vue-router": "^4.3.0",
46
46
  "zod": "^4.3.6",
47
- "@commonpub/config": "0.7.1",
48
47
  "@commonpub/auth": "0.5.0",
48
+ "@commonpub/config": "0.7.1",
49
49
  "@commonpub/editor": "0.5.0",
50
- "@commonpub/protocol": "0.9.5",
51
50
  "@commonpub/docs": "0.5.2",
52
- "@commonpub/schema": "0.8.12",
53
- "@commonpub/ui": "0.7.1",
51
+ "@commonpub/protocol": "0.9.5",
54
52
  "@commonpub/learning": "0.5.0",
55
- "@commonpub/server": "2.15.0"
53
+ "@commonpub/server": "2.15.0",
54
+ "@commonpub/ui": "0.7.1",
55
+ "@commonpub/schema": "0.8.12"
56
56
  },
57
57
  "scripts": {}
58
58
  }
@@ -10,17 +10,24 @@ export default defineEventHandler(async (event) => {
10
10
  const user = requireAuth(event);
11
11
  const { url, purpose } = await parseBody(event, schema);
12
12
 
13
- // SSRF protection — block private IPs
13
+ // SSRF protection — block private/internal IPs
14
14
  const parsed = new URL(url);
15
- const hostname = parsed.hostname;
15
+ const hostname = parsed.hostname.toLowerCase();
16
+ const h = hostname.replace(/^\[|\]$/g, '');
16
17
  if (
17
- hostname === 'localhost' ||
18
- hostname === '127.0.0.1' ||
19
- hostname === '::1' ||
20
- hostname.startsWith('10.') ||
21
- hostname.startsWith('192.168.') ||
22
- hostname.match(/^172\.(1[6-9]|2\d|3[01])\./) ||
23
- hostname === '169.254.169.254' // AWS metadata
18
+ h === 'localhost' ||
19
+ h === 'localhost.localdomain' ||
20
+ h === 'metadata.google.internal' ||
21
+ h.endsWith('.local') ||
22
+ /^127\./.test(h) ||
23
+ /^10\./.test(h) ||
24
+ /^172\.(1[6-9]|2\d|3[01])\./.test(h) ||
25
+ /^192\.168\./.test(h) ||
26
+ /^169\.254\./.test(h) ||
27
+ /^0\./.test(h) ||
28
+ h === '::1' ||
29
+ /^f[cd]/i.test(h) ||
30
+ /^fe80/i.test(h)
24
31
  ) {
25
32
  throw createError({ statusCode: 400, statusMessage: 'Cannot fetch from private/local addresses' });
26
33
  }
@@ -29,15 +29,22 @@ export default defineEventHandler(async (event) => {
29
29
  }
30
30
 
31
31
  // Block localhost/private IPs (SSRF prevention)
32
- const hostname = parsed.hostname;
32
+ const hostname = parsed.hostname.toLowerCase();
33
+ const h = hostname.replace(/^\[|\]$/g, ''); // strip IPv6 brackets
33
34
  if (
34
- hostname === 'localhost' ||
35
- hostname === '127.0.0.1' ||
36
- hostname === '::1' ||
37
- hostname.startsWith('10.') ||
38
- hostname.startsWith('172.') ||
39
- hostname.startsWith('192.168.') ||
40
- hostname.endsWith('.local')
35
+ h === 'localhost' ||
36
+ h === 'localhost.localdomain' ||
37
+ h === 'metadata.google.internal' ||
38
+ h.endsWith('.local') ||
39
+ /^127\./.test(h) ||
40
+ /^10\./.test(h) ||
41
+ /^172\.(1[6-9]|2\d|3[01])\./.test(h) ||
42
+ /^192\.168\./.test(h) ||
43
+ /^169\.254\./.test(h) ||
44
+ /^0\./.test(h) ||
45
+ h === '::1' ||
46
+ /^f[cd]/i.test(h) ||
47
+ /^fe80/i.test(h)
41
48
  ) {
42
49
  throw createError({ statusCode: 403, statusMessage: 'Private addresses not allowed' });
43
50
  }
@@ -30,8 +30,12 @@ export default defineEventHandler(async (event) => {
30
30
  controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'counts', notifications, messages })}\n\n`));
31
31
  }
32
32
 
33
- // Send initial counts
34
- await sendCounts();
33
+ // Send initial counts — if DB is unavailable, send zeros and let polling retry
34
+ try {
35
+ await sendCounts();
36
+ } catch {
37
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'counts', notifications: 0, messages: 0 })}\n\n`));
38
+ }
35
39
 
36
40
  // Poll every 10 seconds
37
41
  const interval = setInterval(async () => {
@@ -39,7 +39,11 @@ export default defineNitroPlugin((nitro) => {
39
39
  // Run first sync after a brief delay to avoid startup contention
40
40
  runSync(domain, intervalMs, backfillOnSync);
41
41
 
42
- interval = setInterval(() => runSync(domain, intervalMs, backfillOnSync), intervalMs);
42
+ interval = setInterval(() => {
43
+ runSync(domain, intervalMs, backfillOnSync).catch((err) => {
44
+ console.error('[hub-sync] Sync worker unexpected error:', err instanceof Error ? err.message : err);
45
+ });
46
+ }, intervalMs);
43
47
  } catch (err) {
44
48
  console.error('[hub-sync] Failed to start:', err instanceof Error ? err.message : err);
45
49
  }
@@ -64,7 +64,11 @@ export default defineNitroPlugin((nitro) => {
64
64
 
65
65
  // Digest scheduler — runs every hour, sends digests for users whose digest window has elapsed
66
66
  const DIGEST_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
67
- digestInterval = setInterval(() => runDigest(siteUrl, siteName), DIGEST_INTERVAL_MS);
67
+ digestInterval = setInterval(() => {
68
+ runDigest(siteUrl, siteName).catch((err) => {
69
+ console.error('[notification-email] Digest scheduler unexpected error:', err instanceof Error ? err.message : err);
70
+ });
71
+ }, DIGEST_INTERVAL_MS);
68
72
 
69
73
  console.log('[notification-email] Digest scheduler started (interval: 1h)');
70
74
  } catch (err) {
@@ -36,8 +36,8 @@ export default defineEventHandler(async (event) => {
36
36
  // CORS — WebFinger must be accessible from browser-based AP clients
37
37
  setResponseHeader(event, 'access-control-allow-origin', '*');
38
38
 
39
- // Instance actor lookup: acct:domain@domain → /actor Service
40
- if (parsed.username === instanceDomain || parsed.username === config.instance.domain) {
39
+ // Instance actor lookup: acct:domain@domain or acct:instance@domain → /actor Service
40
+ if (parsed.username === instanceDomain || parsed.username === config.instance.domain || parsed.username === 'instance') {
41
41
  const actorUri = `https://${instanceDomain}/actor`;
42
42
  setResponseHeader(event, 'content-type', 'application/jrd+json');
43
43
  return buildWebFingerResponse({