javascript-solid-server 0.0.65 → 0.0.67

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "javascript-solid-server",
3
- "version": "0.0.65",
3
+ "version": "0.0.67",
4
4
  "description": "A minimal, fast Solid server",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/ap/index.js CHANGED
@@ -7,7 +7,7 @@ import { webfinger } from 'microfed'
7
7
  import { loadOrCreateKeypair, getKeyId } from './keys.js'
8
8
  import { initStore } from './store.js'
9
9
  import { createInboxHandler } from './routes/inbox.js'
10
- import { createOutboxHandler } from './routes/outbox.js'
10
+ import { createOutboxHandler, createOutboxPostHandler } from './routes/outbox.js'
11
11
  import { createCollectionsHandler } from './routes/collections.js'
12
12
  import { createActorHandler } from './routes/actor.js'
13
13
 
@@ -135,7 +135,7 @@ export async function activityPubPlugin(fastify, options = {}) {
135
135
  version: '2.1',
136
136
  software: {
137
137
  name: 'jss',
138
- version: '0.0.65',
138
+ version: '0.0.67',
139
139
  repository: 'https://github.com/JavaScriptSolidServer/JavaScriptSolidServer'
140
140
  },
141
141
  protocols: ['activitypub', 'solid'],
@@ -165,7 +165,9 @@ export async function activityPubPlugin(fastify, options = {}) {
165
165
 
166
166
  // Outbox endpoint
167
167
  const outboxHandler = createOutboxHandler(config, keypair)
168
+ const outboxPostHandler = createOutboxPostHandler(config, keypair)
168
169
  fastify.get('/profile/card/outbox', outboxHandler)
170
+ fastify.post('/profile/card/outbox', outboxPostHandler)
169
171
 
170
172
  // Followers/Following collections
171
173
  const collectionsHandler = createCollectionsHandler(config)
@@ -1,9 +1,12 @@
1
1
  /**
2
2
  * Outbox endpoint handler
3
3
  * Returns user's activities as OrderedCollection
4
+ * Accepts POST to create new posts and deliver to followers
4
5
  */
5
6
 
6
- import { getPosts } from '../store.js'
7
+ import { outbox } from 'microfed'
8
+ import { getPosts, savePost, getFollowerInboxes } from '../store.js'
9
+ import { randomUUID } from 'crypto'
7
10
 
8
11
  /**
9
12
  * Create outbox handler
@@ -49,4 +52,98 @@ export function createOutboxHandler(config, keypair) {
49
52
  }
50
53
  }
51
54
 
52
- export default { createOutboxHandler }
55
+ /**
56
+ * Create outbox POST handler for creating new posts
57
+ * @param {object} config - AP configuration
58
+ * @param {object} keypair - RSA keypair
59
+ * @returns {Function} Fastify handler
60
+ */
61
+ export function createOutboxPostHandler(config, keypair) {
62
+ return async (request, reply) => {
63
+ const protocol = request.headers['x-forwarded-proto'] || request.protocol
64
+ const host = request.headers['x-forwarded-host'] || request.hostname
65
+ const baseUrl = `${protocol}://${host}`
66
+ const profileUrl = `${baseUrl}/profile/card`
67
+ const actorId = `${profileUrl}#me`
68
+
69
+ // Parse body
70
+ let activity
71
+ try {
72
+ activity = typeof request.body === 'string'
73
+ ? JSON.parse(request.body)
74
+ : request.body
75
+ } catch {
76
+ return reply.code(400).send({ error: 'Invalid JSON' })
77
+ }
78
+
79
+ // Handle direct Note posting (convenience)
80
+ if (activity.type === 'Note' || (!activity.type && activity.content)) {
81
+ const noteId = `${baseUrl}/posts/${randomUUID()}`
82
+ const now = new Date().toISOString()
83
+
84
+ const note = {
85
+ '@context': 'https://www.w3.org/ns/activitystreams',
86
+ type: 'Note',
87
+ id: noteId,
88
+ content: activity.content,
89
+ published: now,
90
+ attributedTo: actorId,
91
+ to: ['https://www.w3.org/ns/activitystreams#Public'],
92
+ cc: [`${profileUrl}/followers`],
93
+ ...(activity.inReplyTo ? { inReplyTo: activity.inReplyTo } : {})
94
+ }
95
+
96
+ activity = {
97
+ '@context': 'https://www.w3.org/ns/activitystreams',
98
+ type: 'Create',
99
+ id: `${noteId}/activity`,
100
+ actor: actorId,
101
+ published: now,
102
+ object: note,
103
+ to: note.to,
104
+ cc: note.cc
105
+ }
106
+ }
107
+
108
+ // Save post
109
+ if (activity.type === 'Create' && activity.object?.type === 'Note') {
110
+ savePost(
111
+ activity.object.id,
112
+ activity.object.content,
113
+ activity.object.inReplyTo || null
114
+ )
115
+ }
116
+
117
+ // Deliver to followers
118
+ const inboxes = getFollowerInboxes()
119
+ request.log.info(`Delivering to ${inboxes.length} follower(s)`)
120
+
121
+ const keyId = `${profileUrl}#main-key`
122
+ const deliveryResults = await Promise.allSettled(
123
+ inboxes.map(inbox =>
124
+ outbox.send({
125
+ activity,
126
+ inbox,
127
+ privateKey: keypair.privateKey,
128
+ keyId
129
+ })
130
+ )
131
+ )
132
+
133
+ const succeeded = deliveryResults.filter(r => r.status === 'fulfilled').length
134
+ const failed = deliveryResults.filter(r => r.status === 'rejected').length
135
+
136
+ if (failed > 0) {
137
+ request.log.warn(`Delivery: ${succeeded} succeeded, ${failed} failed`)
138
+ } else {
139
+ request.log.info(`Delivered to ${succeeded} inbox(es)`)
140
+ }
141
+
142
+ return reply
143
+ .code(201)
144
+ .header('Location', activity.object?.id || activity.id)
145
+ .send(activity)
146
+ }
147
+ }
148
+
149
+ export default { createOutboxHandler, createOutboxPostHandler }
package/src/ap/store.js CHANGED
@@ -267,7 +267,7 @@ export function getPostCount() {
267
267
 
268
268
  export function cacheActor(actor) {
269
269
  runStmt(
270
- 'INSERT OR REPLACE INTO actors (id, data, fetched_at) VALUES (?, ?, datetime("now"))',
270
+ "INSERT OR REPLACE INTO actors (id, data, fetched_at) VALUES (?, ?, datetime('now'))",
271
271
  [actor.id, JSON.stringify(actor)]
272
272
  )
273
273
  }