1010// In-memory client store (replace with persistent storage later)
1111const clients = new Map ( )
1212
13+ // Stable instance start time (used for created_at)
14+ const startedAt = new Date ( ) . toISOString ( )
15+
16+ /**
17+ * Parse request body — handles both JSON and form-urlencoded
18+ * (JSS uses raw buffer parser for all content types)
19+ */
20+ function parseBody ( request ) {
21+ if ( request . body && typeof request . body === 'object' && ! Buffer . isBuffer ( request . body ) ) {
22+ return request . body
23+ }
24+ const raw = Buffer . isBuffer ( request . body ) ? request . body . toString ( ) : String ( request . body || '' )
25+ const ct = request . headers [ 'content-type' ] || ''
26+ if ( ct . includes ( 'application/json' ) ) {
27+ try { return JSON . parse ( raw ) } catch { return { } }
28+ }
29+ // Default: parse as form-urlencoded
30+ return Object . fromEntries ( new URLSearchParams ( raw ) )
31+ }
32+
1333/**
1434 * POST /api/v1/apps — Dynamic client registration
1535 * Mastodon clients call this to register before OAuth
1636 */
1737export function createAppsHandler ( ) {
1838 return async ( request , reply ) => {
19- const { client_name, redirect_uris, scopes, website } = request . body || { }
39+ const body = parseBody ( request )
40+ const { client_name, redirect_uris, scopes, website } = body
2041
2142 if ( ! client_name || ! redirect_uris ) {
2243 return reply . code ( 422 ) . send ( { error : 'client_name and redirect_uris are required' } )
@@ -56,14 +77,14 @@ export function createVerifyCredentialsHandler (config) {
5677 username : config . username ,
5778 acct : config . username ,
5879 display_name : config . displayName ,
59- note : config . summary ? `<p>${ config . summary } </p>` : '' ,
80+ note : config . summary ? `<p>${ escapeHtml ( config . summary ) } </p>` : '' ,
6081 url : `${ baseUrl } /profile/card` ,
6182 uri : `${ baseUrl } /profile/card#me` ,
6283 avatar : `${ baseUrl } /profile/avatar.png` ,
6384 header : '' ,
6485 locked : false ,
6586 bot : false ,
66- created_at : new Date ( ) . toISOString ( ) ,
87+ created_at : startedAt ,
6788 followers_count : 0 ,
6889 following_count : 0 ,
6990 statuses_count : 0 ,
@@ -87,7 +108,7 @@ export function createInstanceHandler (config) {
87108 return async ( request , reply ) => {
88109 const protocol = request . headers [ 'x-forwarded-proto' ] || request . protocol
89110 const host = request . headers [ 'x-forwarded-host' ] || request . hostname
90- const baseUrl = ` ${ protocol } :// ${ host } `
111+ const wsProtocol = protocol === 'https' ? 'wss' : 'ws'
91112
92113 return reply . send ( {
93114 uri : host ,
@@ -96,7 +117,7 @@ export function createInstanceHandler (config) {
96117 short_description : 'Solid pod with Mastodon-compatible API' ,
97118 version : '4.0.0 (compatible; JSS 0.0.67)' ,
98119 urls : {
99- streaming_api : `wss ://${ host } `
120+ streaming_api : `${ wsProtocol } ://${ host } `
100121 } ,
101122 stats : {
102123 user_count : 1 ,
@@ -121,6 +142,10 @@ export function getClient (clientId) {
121142 return clients . get ( clientId ) || null
122143}
123144
145+ function escapeHtml ( str ) {
146+ return str . replace ( / & / g, '&' ) . replace ( / < / g, '<' ) . replace ( / > / g, '>' ) . replace ( / " / g, '"' )
147+ }
148+
124149export default {
125150 createAppsHandler,
126151 createVerifyCredentialsHandler,
0 commit comments