fedipod-server 0.28.1 → 0.30.0

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.
Files changed (438) hide show
  1. package/README.md +38 -34
  2. package/config/server.json +22 -0
  3. package/dist/claims.js +3 -2
  4. package/dist/directory.d.ts +2 -0
  5. package/dist/door.d.ts +4 -0
  6. package/dist/door.js +31 -0
  7. package/dist/fedipod.d.ts +16 -0
  8. package/dist/fedipod.js +13 -0
  9. package/dist/front.d.ts +3 -0
  10. package/dist/front.js +94 -0
  11. package/dist/handler.d.ts +39 -101
  12. package/dist/handler.js +86 -617
  13. package/dist/handler.jsonld +6 -30
  14. package/dist/identities.d.ts +62 -0
  15. package/dist/identities.js +236 -0
  16. package/dist/mounts.d.ts +43 -0
  17. package/dist/mounts.js +123 -0
  18. package/dist/opt-in.d.ts +46 -0
  19. package/dist/opt-in.js +176 -0
  20. package/dist/store-pod.js +37 -0
  21. package/package.json +10 -18
  22. package/lib/client/c2s.mjs +0 -582
  23. package/lib/client/localapi.mjs +0 -87
  24. package/lib/client/masto/accounts.mjs +0 -309
  25. package/lib/client/masto/body.mjs +0 -156
  26. package/lib/client/masto/index.mjs +0 -206
  27. package/lib/client/masto/instance.mjs +0 -112
  28. package/lib/client/masto/media.mjs +0 -79
  29. package/lib/client/masto/oauth.mjs +0 -656
  30. package/lib/client/masto/render.mjs +0 -518
  31. package/lib/client/masto/statuses.mjs +0 -372
  32. package/lib/client/masto/timelines.mjs +0 -438
  33. package/lib/client/oidc-auth.mjs +0 -62
  34. package/lib/client/streaming.mjs +0 -154
  35. package/lib/client/webpush.mjs +0 -98
  36. package/lib/connections/acctfeed.mjs +0 -257
  37. package/lib/connections/atproto.mjs +0 -309
  38. package/lib/connections/bskyfeed.mjs +0 -243
  39. package/lib/connections/bskygroup.mjs +0 -162
  40. package/lib/connections/fediacct.mjs +0 -253
  41. package/lib/connections/import.mjs +0 -357
  42. package/lib/connections/tagfeed.mjs +0 -212
  43. package/lib/connections/vault.mjs +0 -114
  44. package/lib/core/as2.mjs +0 -206
  45. package/lib/core/contexts/activitystreams.json +0 -379
  46. package/lib/core/contexts/anno.json +0 -126
  47. package/lib/core/contexts/did-v1.json +0 -57
  48. package/lib/core/contexts/fep-5711.json +0 -36
  49. package/lib/core/contexts/gotosocial.json +0 -86
  50. package/lib/core/contexts/identity-v1.json +0 -152
  51. package/lib/core/contexts/index.mjs +0 -63
  52. package/lib/core/contexts/join-lemmy.json +0 -33
  53. package/lib/core/contexts/joinmastodon.json +0 -28
  54. package/lib/core/contexts/litepub-0.1.json +0 -60
  55. package/lib/core/contexts/map.json +0 -19
  56. package/lib/core/contexts/miscellany.json +0 -19
  57. package/lib/core/contexts/quotes.json +0 -18
  58. package/lib/core/contexts/schemaorg.json +0 -8845
  59. package/lib/core/contexts/security-data-integrity-v1.json +0 -78
  60. package/lib/core/contexts/security-data-integrity-v2.json +0 -81
  61. package/lib/core/contexts/security-multikey-v1.json +0 -35
  62. package/lib/core/contexts/security-v1.json +0 -74
  63. package/lib/core/contexts/webfinger.json +0 -10
  64. package/lib/core/deliver.mjs +0 -371
  65. package/lib/core/graphview.mjs +0 -327
  66. package/lib/core/intake/activities.mjs +0 -633
  67. package/lib/core/intake/activity.mjs +0 -318
  68. package/lib/core/intake/channel.mjs +0 -144
  69. package/lib/core/intake/group.mjs +0 -256
  70. package/lib/core/intake/index.mjs +0 -701
  71. package/lib/core/intake/notes.mjs +0 -360
  72. package/lib/core/intake/verify.mjs +0 -142
  73. package/lib/core/keys.mjs +0 -153
  74. package/lib/core/lease.mjs +0 -206
  75. package/lib/core/polls.mjs +0 -105
  76. package/lib/core/profile-page.mjs +0 -104
  77. package/lib/core/proof.mjs +0 -79
  78. package/lib/core/publisher/collections.mjs +0 -237
  79. package/lib/core/publisher/index.mjs +0 -500
  80. package/lib/core/publisher/notes.mjs +0 -453
  81. package/lib/core/publisher/questions.mjs +0 -239
  82. package/lib/core/publisher/restore.mjs +0 -224
  83. package/lib/core/shapes/activitystreams.ttl +0 -157
  84. package/lib/core/shapes/index.mjs +0 -132
  85. package/lib/core/shapes/shapes-text.mjs +0 -13
  86. package/lib/core/social.mjs +0 -531
  87. package/lib/core/storage.mjs +0 -176
  88. package/lib/core/store.mjs +0 -568
  89. package/lib/core/wire-quotes.mjs +0 -71
  90. package/lib/core/wire.mjs +0 -889
  91. package/lib/device/account.mjs +0 -87
  92. package/lib/device/admin/index.mjs +0 -13
  93. package/lib/device/admin/origins.mjs +0 -35
  94. package/lib/device/admin/routes/connections.mjs +0 -144
  95. package/lib/device/admin/routes/gateway.mjs +0 -199
  96. package/lib/device/admin/routes/lifecycle.mjs +0 -200
  97. package/lib/device/admin/routes/owner.mjs +0 -322
  98. package/lib/device/admin/routes/setup.mjs +0 -412
  99. package/lib/device/admin/routes/social.mjs +0 -188
  100. package/lib/device/admin/server.mjs +0 -95
  101. package/lib/device/admin/static.mjs +0 -247
  102. package/lib/device/admin/surface.mjs +0 -315
  103. package/lib/device/certs.mjs +0 -281
  104. package/lib/device/cli/commands/account.mjs +0 -586
  105. package/lib/device/cli/commands/run.mjs +0 -278
  106. package/lib/device/cli/commands/service.mjs +0 -221
  107. package/lib/device/cli/commands/setup.mjs +0 -447
  108. package/lib/device/cli/commands/state.mjs +0 -561
  109. package/lib/device/cli/context.mjs +0 -288
  110. package/lib/device/export-collections.mjs +0 -161
  111. package/lib/device/gateway-move.mjs +0 -58
  112. package/lib/device/home.mjs +0 -161
  113. package/lib/device/migrate.mjs +0 -115
  114. package/lib/device/ports.mjs +0 -27
  115. package/lib/device/remote.mjs +0 -54
  116. package/lib/device/setup.mjs +0 -459
  117. package/lib/device/update.mjs +0 -96
  118. package/lib/gateway/caches.mjs +0 -36
  119. package/lib/gateway/directory.mjs +0 -90
  120. package/lib/gateway/front-core.mjs +0 -970
  121. package/lib/gateway/gateway-core.mjs +0 -165
  122. package/lib/gateway/headers.mjs +0 -49
  123. package/lib/gateway/httpsig.mjs +0 -159
  124. package/lib/gateway/notices.mjs +0 -84
  125. package/lib/gateway/quiet.mjs +0 -189
  126. package/lib/pod/README.md +0 -61
  127. package/lib/pod/actor.mjs +0 -96
  128. package/lib/pod/collection.mjs +0 -77
  129. package/lib/pod/containers.mjs +0 -140
  130. package/lib/pod/discovery.mjs +0 -46
  131. package/lib/pod/featured.mjs +0 -18
  132. package/lib/pod/followers.mjs +0 -24
  133. package/lib/pod/following.mjs +0 -10
  134. package/lib/pod/http.mjs +0 -74
  135. package/lib/pod/inbox.mjs +0 -166
  136. package/lib/pod/links.mjs +0 -35
  137. package/lib/pod/media.mjs +0 -27
  138. package/lib/pod/notes.mjs +0 -80
  139. package/lib/pod/notifications.mjs +0 -89
  140. package/lib/pod/outbox.mjs +0 -18
  141. package/lib/pod/package.json +0 -19
  142. package/lib/pod/policy.mjs +0 -58
  143. package/lib/pod/private.mjs +0 -19
  144. package/lib/pod/root.mjs +0 -104
  145. package/lib/pod/state.mjs +0 -40
  146. package/lib/pod/transport.mjs +0 -525
  147. package/lib/pod/urls.mjs +0 -85
  148. package/lib/server/embed.mjs +0 -423
  149. package/lib/session/README.md +0 -137
  150. package/lib/session/demo.html +0 -94
  151. package/lib/session/fedi-account.mjs +0 -408
  152. package/lib/session/fedi-login.mjs +0 -123
  153. package/lib/session/oidc-session.mjs +0 -233
  154. package/lib/session/package.json +0 -49
  155. package/lib/shared/guard.mjs +0 -213
  156. package/lib/shared/links.mjs +0 -9
  157. package/lib/shared/safefetch.mjs +0 -235
  158. package/lib/shared/ua.mjs +0 -29
  159. package/phanpy/dist/404.html +0 -40
  160. package/phanpy/dist/_headers +0 -2
  161. package/phanpy/dist/apple-touch-icon.png +0 -0
  162. package/phanpy/dist/assets/ICONS-DIPJeU0C.js +0 -2
  163. package/phanpy/dist/assets/ICONS-DIPJeU0C.js.map +0 -1
  164. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js +0 -183
  165. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js.map +0 -1
  166. package/phanpy/dist/assets/Temml-CCjoacWt.woff2 +0 -0
  167. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js +0 -2
  168. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js.map +0 -1
  169. package/phanpy/dist/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
  170. package/phanpy/dist/assets/catch-up-O5q5C75Z.png +0 -0
  171. package/phanpy/dist/assets/chunk-aKtaBQYM.js +0 -1
  172. package/phanpy/dist/assets/compose-CFEx7wb4.js +0 -2
  173. package/phanpy/dist/assets/compose-CFEx7wb4.js.map +0 -1
  174. package/phanpy/dist/assets/compose-ZJN5xSb4.js +0 -35
  175. package/phanpy/dist/assets/compose-ZJN5xSb4.js.map +0 -1
  176. package/phanpy/dist/assets/debug.module-YFQgwnIr.js +0 -52
  177. package/phanpy/dist/assets/debug.module-YFQgwnIr.js.map +0 -1
  178. package/phanpy/dist/assets/dom-p1YcJ5Rw.js +0 -2
  179. package/phanpy/dist/assets/dom-p1YcJ5Rw.js.map +0 -1
  180. package/phanpy/dist/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
  181. package/phanpy/dist/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
  182. package/phanpy/dist/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
  183. package/phanpy/dist/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
  184. package/phanpy/dist/assets/icons/add-circle-line-WD0mwORD.js +0 -1
  185. package/phanpy/dist/assets/icons/alert-line-CpFUMf9s.js +0 -1
  186. package/phanpy/dist/assets/icons/android-2-line-Bzxnrd3S.js +0 -1
  187. package/phanpy/dist/assets/icons/announcement-line-5nwaJFJm.js +0 -1
  188. package/phanpy/dist/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +0 -1
  189. package/phanpy/dist/assets/icons/arrow-down-line-B8uzXzq6.js +0 -1
  190. package/phanpy/dist/assets/icons/arrow-left-line-CGrGT9C-.js +0 -1
  191. package/phanpy/dist/assets/icons/arrow-right-line-CE-HERfX.js +0 -1
  192. package/phanpy/dist/assets/icons/arrow-to-up-line-CvdQjmcy.js +0 -1
  193. package/phanpy/dist/assets/icons/arrow-up-circle-line-DEparSNF.js +0 -1
  194. package/phanpy/dist/assets/icons/arrow-up-line-C7yBeugc.js +0 -1
  195. package/phanpy/dist/assets/icons/arrows-right-line-BtbXmJGP.js +0 -1
  196. package/phanpy/dist/assets/icons/at-line-CF66Bznk.js +0 -1
  197. package/phanpy/dist/assets/icons/attachment-line-BY8It-l9.js +0 -1
  198. package/phanpy/dist/assets/icons/blockquote-line-BS6pU_hm.js +0 -1
  199. package/phanpy/dist/assets/icons/board-line-C0UWXyA6.js +0 -1
  200. package/phanpy/dist/assets/icons/bookmark-line-1CtX5JLF.js +0 -1
  201. package/phanpy/dist/assets/icons/building-5-line-Dd59MzbO.js +0 -1
  202. package/phanpy/dist/assets/icons/bus-2-line-6_dvA0GG.js +0 -1
  203. package/phanpy/dist/assets/icons/calendar-day-line-Crpl_fmu.js +0 -1
  204. package/phanpy/dist/assets/icons/calendar-month-line-vommts2C.js +0 -1
  205. package/phanpy/dist/assets/icons/calendar-time-add-line-BU_DTlQk.js +0 -1
  206. package/phanpy/dist/assets/icons/camera-line-Cv69MEei.js +0 -1
  207. package/phanpy/dist/assets/icons/celebrate-line-B8DvIoi0.js +0 -1
  208. package/phanpy/dist/assets/icons/chart-bar-line-FK-QOmtF.js +0 -1
  209. package/phanpy/dist/assets/icons/chart-line-line-BGcJAFWw.js +0 -1
  210. package/phanpy/dist/assets/icons/chat-3-line-YFK2-hwz.js +0 -1
  211. package/phanpy/dist/assets/icons/check-circle-line-DsAxFr5-.js +0 -1
  212. package/phanpy/dist/assets/icons/clipboard-line-BBp364gJ.js +0 -1
  213. package/phanpy/dist/assets/icons/close-circle-line-CICdiuGz.js +0 -1
  214. package/phanpy/dist/assets/icons/close-line-DYrFWcjv.js +0 -1
  215. package/phanpy/dist/assets/icons/cloud-line-tiQAbah8.js +0 -1
  216. package/phanpy/dist/assets/icons/code-line-fAl8iiV3.js +0 -1
  217. package/phanpy/dist/assets/icons/comment-2-line-CR3g7WeB.js +0 -1
  218. package/phanpy/dist/assets/icons/copy-2-line-CLW69yF5.js +0 -1
  219. package/phanpy/dist/assets/icons/delete-2-line-B8xmbr4n.js +0 -1
  220. package/phanpy/dist/assets/icons/document-3-line-DBGNhAg2.js +0 -1
  221. package/phanpy/dist/assets/icons/document-line-C7mb9sb7.js +0 -1
  222. package/phanpy/dist/assets/icons/down-line-BGqSSThd.js +0 -1
  223. package/phanpy/dist/assets/icons/edit-4-line-DcgGaEhx.js +0 -1
  224. package/phanpy/dist/assets/icons/emoji-2-line-D8YgnM-y.js +0 -1
  225. package/phanpy/dist/assets/icons/exit-line-Dc07BS-x.js +0 -1
  226. package/phanpy/dist/assets/icons/external-link-line-BvpSkmjO.js +0 -1
  227. package/phanpy/dist/assets/icons/eye-2-line-CiWXx-f_.js +0 -1
  228. package/phanpy/dist/assets/icons/eye-close-line-BgTaSJ4P.js +0 -1
  229. package/phanpy/dist/assets/icons/filter-2-line-B_mR2usy.js +0 -1
  230. package/phanpy/dist/assets/icons/filter-line-C_nm-3cU.js +0 -1
  231. package/phanpy/dist/assets/icons/flag-1-line-C2HB05I0.js +0 -1
  232. package/phanpy/dist/assets/icons/forbid-circle-line-D3LTT9yd.js +0 -1
  233. package/phanpy/dist/assets/icons/formula-line-DLrt2d6j.js +0 -1
  234. package/phanpy/dist/assets/icons/grid-line-CgUmEikf.js +0 -1
  235. package/phanpy/dist/assets/icons/group-line-C5fnX0wU.js +0 -1
  236. package/phanpy/dist/assets/icons/hand-finger-2-line-BGgxbuL9.js +0 -1
  237. package/phanpy/dist/assets/icons/hashtag-line-CyTEEXBb.js +0 -1
  238. package/phanpy/dist/assets/icons/heart-crack-line-BZddbO2C.js +0 -1
  239. package/phanpy/dist/assets/icons/heart-line-CJ7K47J4.js +0 -1
  240. package/phanpy/dist/assets/icons/history-2-line-kWWvFzqv.js +0 -1
  241. package/phanpy/dist/assets/icons/history-line-bGQiwbTH.js +0 -1
  242. package/phanpy/dist/assets/icons/home-3-line-fRU1zeLG.js +0 -1
  243. package/phanpy/dist/assets/icons/information-line-iNk6-sOY.js +0 -1
  244. package/phanpy/dist/assets/icons/keyboard-line-DsCyuQNz.js +0 -1
  245. package/phanpy/dist/assets/icons/layout-4-line-CZkOX52Z.js +0 -1
  246. package/phanpy/dist/assets/icons/layout-5-line-B_znt5wO.js +0 -1
  247. package/phanpy/dist/assets/icons/left-line-eUw0tya4.js +0 -1
  248. package/phanpy/dist/assets/icons/lightning-line-DaKoDdix.js +0 -1
  249. package/phanpy/dist/assets/icons/link-2-line-DCqLmMA7.js +0 -1
  250. package/phanpy/dist/assets/icons/list-check-line-BAxosBh9.js +0 -1
  251. package/phanpy/dist/assets/icons/lock-line-CPoaXeUW.js +0 -1
  252. package/phanpy/dist/assets/icons/mail-line-B0P8aa1L.js +0 -1
  253. package/phanpy/dist/assets/icons/moon-line-CEqNAToP.js +0 -1
  254. package/phanpy/dist/assets/icons/more-1-fill-Dw2e5xab.js +0 -1
  255. package/phanpy/dist/assets/icons/more-3-line-BIiVjYTz.js +0 -1
  256. package/phanpy/dist/assets/icons/notification-line-BZZq2Gtu.js +0 -1
  257. package/phanpy/dist/assets/icons/pencil-line-CKh8-A1v.js +0 -1
  258. package/phanpy/dist/assets/icons/photo-album-line-Bnfg2u40.js +0 -1
  259. package/phanpy/dist/assets/icons/pin-line-DpTolyhs.js +0 -1
  260. package/phanpy/dist/assets/icons/play-fill-B0-G_8lX.js +0 -1
  261. package/phanpy/dist/assets/icons/qrcode-2-line-B6SPCaHn.js +0 -1
  262. package/phanpy/dist/assets/icons/quill-pen-line-B6LUrnCP.js +0 -1
  263. package/phanpy/dist/assets/icons/quote-left-fill-hQZ1cj_6.js +0 -1
  264. package/phanpy/dist/assets/icons/radar-line-CKyroTth.js +0 -1
  265. package/phanpy/dist/assets/icons/react-line-CoIKhhtK.js +0 -1
  266. package/phanpy/dist/assets/icons/refresh-2-line-D0mTF97S.js +0 -1
  267. package/phanpy/dist/assets/icons/right-line-B3KWJaqc.js +0 -1
  268. package/phanpy/dist/assets/icons/rocket-line-4KZl537D.js +0 -1
  269. package/phanpy/dist/assets/icons/round-fill-C69NTrZ0.js +0 -1
  270. package/phanpy/dist/assets/icons/round-line-B9k_1uo-.js +0 -1
  271. package/phanpy/dist/assets/icons/route-line-B28ReHcE.js +0 -1
  272. package/phanpy/dist/assets/icons/rows-4-line-CrBniagM.js +0 -1
  273. package/phanpy/dist/assets/icons/scan-line-DSf74YdP.js +0 -1
  274. package/phanpy/dist/assets/icons/search-2-line-B6geIU1z.js +0 -1
  275. package/phanpy/dist/assets/icons/settings-3-line-azJwK47w.js +0 -1
  276. package/phanpy/dist/assets/icons/settings-6-line-C_5zc5YR.js +0 -1
  277. package/phanpy/dist/assets/icons/share-2-line-D5MxWjrm.js +0 -1
  278. package/phanpy/dist/assets/icons/share-forward-line-Cp61fZzZ.js +0 -1
  279. package/phanpy/dist/assets/icons/sparkles-2-line-DYYyXTaW.js +0 -1
  280. package/phanpy/dist/assets/icons/sparkles-line-C2hY1s6I.js +0 -1
  281. package/phanpy/dist/assets/icons/time-line-BF3eet1v.js +0 -1
  282. package/phanpy/dist/assets/icons/transfer-4-line-oisfvTBw.js +0 -1
  283. package/phanpy/dist/assets/icons/translate-line-BFWol2Ce.js +0 -1
  284. package/phanpy/dist/assets/icons/unlock-line-X_x5vwv3.js +0 -1
  285. package/phanpy/dist/assets/icons/upload-3-line-DgzwUJeW.js +0 -1
  286. package/phanpy/dist/assets/icons/user-4-line-BVSynZCp.js +0 -1
  287. package/phanpy/dist/assets/icons/user-add-2-line-BlGS5Q8q.js +0 -1
  288. package/phanpy/dist/assets/icons/user-add-line-BbXdHG4z.js +0 -1
  289. package/phanpy/dist/assets/icons/user-edit-line-BFAHOV0X.js +0 -1
  290. package/phanpy/dist/assets/icons/user-follow-line-BXux0wSd.js +0 -1
  291. package/phanpy/dist/assets/icons/user-star-line-DdYlpHLU.js +0 -1
  292. package/phanpy/dist/assets/icons/user-warning-line-Cj43j0_E.js +0 -1
  293. package/phanpy/dist/assets/icons/user-x-line-DIDgNlPH.js +0 -1
  294. package/phanpy/dist/assets/icons/volume-line-Bs390KQt.js +0 -1
  295. package/phanpy/dist/assets/icons/volume-mute-line-DzlzIqFZ.js +0 -1
  296. package/phanpy/dist/assets/icons/walk-line-yNXfB-md.js +0 -1
  297. package/phanpy/dist/assets/icons/world-2-line-CenkGIwj.js +0 -1
  298. package/phanpy/dist/assets/icons/zoom-in-line-CaENUphf.js +0 -1
  299. package/phanpy/dist/assets/icons/zoom-out-line-BDNk0EWo.js +0 -1
  300. package/phanpy/dist/assets/instances-BNPptnn-.json +0 -333
  301. package/phanpy/dist/assets/locales/ar-SA-CfheRbCu.js +0 -1
  302. package/phanpy/dist/assets/locales/ca-ES-DHRW86Tz.js +0 -1
  303. package/phanpy/dist/assets/locales/cs-CZ-CKoqEuA1.js +0 -1
  304. package/phanpy/dist/assets/locales/de-DE-ByRPMRFy.js +0 -1
  305. package/phanpy/dist/assets/locales/eo-UY-CPC7wLCS.js +0 -1
  306. package/phanpy/dist/assets/locales/es-ES-dJa0Fd9t.js +0 -1
  307. package/phanpy/dist/assets/locales/eu-ES-C3djlPR2.js +0 -1
  308. package/phanpy/dist/assets/locales/fa-IR-DTT490Ck.js +0 -1
  309. package/phanpy/dist/assets/locales/fi-FI-B23MCPuz.js +0 -1
  310. package/phanpy/dist/assets/locales/fr-FR-BcaQsO5C.js +0 -1
  311. package/phanpy/dist/assets/locales/gl-ES-BR-CVKBN.js +0 -1
  312. package/phanpy/dist/assets/locales/he-IL-CfheRbCu.js +0 -1
  313. package/phanpy/dist/assets/locales/hu-HU-CfheRbCu.js +0 -1
  314. package/phanpy/dist/assets/locales/it-IT-C1136UFV.js +0 -1
  315. package/phanpy/dist/assets/locales/ja-JP-CRZmRAhD.js +0 -1
  316. package/phanpy/dist/assets/locales/kab-Cz8xH_TT.js +0 -1
  317. package/phanpy/dist/assets/locales/ko-KR-CG9UiY4o.js +0 -1
  318. package/phanpy/dist/assets/locales/lt-LT-CJcI6sHZ.js +0 -1
  319. package/phanpy/dist/assets/locales/nb-NO-CfheRbCu.js +0 -1
  320. package/phanpy/dist/assets/locales/nl-NL-DxN4Yc1t.js +0 -1
  321. package/phanpy/dist/assets/locales/oc-FR-CfheRbCu.js +0 -1
  322. package/phanpy/dist/assets/locales/pl-PL-DkwGXf6P.js +0 -1
  323. package/phanpy/dist/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +0 -1
  324. package/phanpy/dist/assets/locales/pt-BR-nGL0HdFS.js +0 -1
  325. package/phanpy/dist/assets/locales/pt-PT-Cp17aj2z.js +0 -1
  326. package/phanpy/dist/assets/locales/ru-RU-0Y8cIoaK.js +0 -1
  327. package/phanpy/dist/assets/locales/th-TH-CfheRbCu.js +0 -1
  328. package/phanpy/dist/assets/locales/tok-CfheRbCu.js +0 -1
  329. package/phanpy/dist/assets/locales/tr-TR-CfheRbCu.js +0 -1
  330. package/phanpy/dist/assets/locales/uk-UA-C5E3dFBf.js +0 -1
  331. package/phanpy/dist/assets/locales/zh-CN-BHJi7_et.js +0 -1
  332. package/phanpy/dist/assets/locales/zh-TW-CfheRbCu.js +0 -1
  333. package/phanpy/dist/assets/main-BdqNbG-a.js +0 -26
  334. package/phanpy/dist/assets/main-BdqNbG-a.js.map +0 -1
  335. package/phanpy/dist/assets/mock-home-DGhR__He.js +0 -2
  336. package/phanpy/dist/assets/mock-home-DGhR__He.js.map +0 -1
  337. package/phanpy/dist/assets/multi-column-ROck0NVt.jpg +0 -0
  338. package/phanpy/dist/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
  339. package/phanpy/dist/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
  340. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js +0 -2
  341. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js.map +0 -1
  342. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js +0 -20
  343. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js.map +0 -1
  344. package/phanpy/dist/assets/style-B1iLPTPR.css +0 -2
  345. package/phanpy/dist/assets/temml-DuyIQMei.js +0 -97
  346. package/phanpy/dist/assets/temml-DuyIQMei.js.map +0 -1
  347. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js +0 -2
  348. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js.map +0 -1
  349. package/phanpy/dist/assets/year-in-posts-B2lywqdx.png +0 -0
  350. package/phanpy/dist/assets/year-in-posts-DJv_star.js +0 -3
  351. package/phanpy/dist/assets/year-in-posts-DJv_star.js.map +0 -1
  352. package/phanpy/dist/compose/index.html +0 -20
  353. package/phanpy/dist/favicon.ico +0 -0
  354. package/phanpy/dist/index.html +0 -73
  355. package/phanpy/dist/logo-192.png +0 -0
  356. package/phanpy/dist/logo-512.png +0 -0
  357. package/phanpy/dist/logo-badge-72.png +0 -0
  358. package/phanpy/dist/logo-maskable-512.png +0 -0
  359. package/phanpy/dist/logo-monochrome-512.png +0 -0
  360. package/phanpy/dist/logo-monochrome-maskable-512.png +0 -0
  361. package/phanpy/dist/manifest.webmanifest +0 -1
  362. package/phanpy/dist/og-image-2.jpg +0 -0
  363. package/phanpy/dist/og-image.png +0 -0
  364. package/phanpy/dist/robots.txt +0 -2
  365. package/phanpy/dist/sw.js +0 -2
  366. package/phanpy/dist/sw.js.map +0 -1
  367. package/phanpy/dist/version.json +0 -1
  368. package/run-agent.mjs +0 -897
  369. package/vendor/gate.cjs +0 -138
  370. package/vendor/idp-grant.cjs +0 -394
  371. package/vendor/jfetch.cjs +0 -16
  372. package/web/admin/actors.js +0 -145
  373. package/web/admin/bar.css +0 -126
  374. package/web/admin/bar.js +0 -99
  375. package/web/admin/client/client.js +0 -66
  376. package/web/admin/client/index.html +0 -69
  377. package/web/admin/client-pick.js +0 -79
  378. package/web/admin/common.js +0 -23
  379. package/web/admin/connections.js +0 -112
  380. package/web/admin/gateway.js +0 -133
  381. package/web/admin/group.js +0 -258
  382. package/web/admin/index.html +0 -603
  383. package/web/admin/notices-bar.js +0 -90
  384. package/web/admin/oauth-signin.mjs +0 -37
  385. package/web/admin/record.js +0 -390
  386. package/web/admin/setup/index.html +0 -241
  387. package/web/admin/setup/setup.js +0 -326
  388. package/web/admin/tokens.css +0 -68
  389. package/web/admin/upkeep.js +0 -177
  390. package/web/admin/window.css +0 -55
  391. package/web/admin/window.js +0 -136
  392. package/web/app/README.md +0 -81
  393. package/web/app/admin-facade.mjs +0 -588
  394. package/web/app/agent.mjs +0 -663
  395. package/web/app/atproto-browser.mjs +0 -86
  396. package/web/app/authorize_interaction.html +0 -56
  397. package/web/app/authorize_interaction.js +0 -69
  398. package/web/app/boot.mjs +0 -576
  399. package/web/app/deliver-relay.mjs +0 -129
  400. package/web/app/dist/boot.js +0 -34218
  401. package/web/app/dist/boot.js.map +0 -7
  402. package/web/app/dist/sw.js +0 -72810
  403. package/web/app/dist/sw.js.map +0 -7
  404. package/web/app/fediacct-browser.mjs +0 -146
  405. package/web/app/gateway-move.mjs +0 -60
  406. package/web/app/idb-kv.mjs +0 -35
  407. package/web/app/index.html +0 -242
  408. package/web/app/keys-browser.mjs +0 -71
  409. package/web/app/keystore.mjs +0 -82
  410. package/web/app/oidc-session.mjs +0 -6
  411. package/web/app/pod-auth.mjs +0 -248
  412. package/web/app/pod-remote.mjs +0 -84
  413. package/web/app/shims/_globals.mjs +0 -2
  414. package/web/app/shims/fedify-sig.mjs +0 -77
  415. package/web/app/shims/node-crypto.mjs +0 -113
  416. package/web/app/shims/node-fs.mjs +0 -17
  417. package/web/app/shims/node-path.mjs +0 -19
  418. package/web/app/shims/node-url.mjs +0 -7
  419. package/web/app/shims/prelude.js +0 -58
  420. package/web/app/shims/safefetch.mjs +0 -58
  421. package/web/app/shims/shapes-text.mjs +0 -8
  422. package/web/app/shims/web-push.mjs +0 -14
  423. package/web/app/signup.mjs +0 -318
  424. package/web/app/sw-src.mjs +0 -220
  425. package/web/app/update.js +0 -70
  426. package/web/front/admin.html +0 -55
  427. package/web/front/admin.js +0 -89
  428. package/web/front/install.sh +0 -69
  429. package/web/front/new-account.html +0 -35
  430. package/web/front/new-account.js +0 -13
  431. package/web/front/notices.html +0 -69
  432. package/web/front/notices.js +0 -107
  433. package/web/front/run.html +0 -58
  434. package/web/front/run.js +0 -195
  435. package/web/front/solid-oidc-client.js +0 -6
  436. package/web/icons/forum.svg +0 -11
  437. package/web/icons/gateway.svg +0 -14
  438. package/web/icons/local.svg +0 -11
@@ -1,318 +0,0 @@
1
- // activity.mjs — what an inbound activity IS, before anything is done with
2
- // it: the AS2 types the fediverse actually posts, the caps that bound what a
3
- // stranger can make the agent hold, and the pure checks on ids, origins and
4
- // authorship that every other intake module shares.
5
-
6
- // A note's replies collection is rewritten WHOLE every time one is added, so
7
- // without a cap the bytes are quadratic in a number a stranger chooses.
8
- export const MAX_REPLIES_RECORDED = 500;
9
- // Ids of activities already forwarded to our followers (§7.1.2), so a re-drain
10
- // never re-broadcasts one. A ceiling on the record, not on forwarding.
11
- export const MAX_FORWARDED = 2000;
12
- // How many posts may wait for a group operator's decision. A ceiling, not a
13
- // window: full refuses the newest rather than dropping the oldest.
14
- export const MAX_PENDING_REVIEW = 500;
15
- // A group's membership, cached: it changes when someone joins or leaves, and
16
- // re-reading it on every arriving post would spend one stranger's fetch per
17
- // message on a list that moves in days.
18
- export const CO_MEMBER_TTL_MS = 24 * 60 * 60_000;
19
- export const CO_MEMBER_MAX = 5000;
20
- // An activity is a few kB. This is generous by two orders of magnitude and
21
- // still bounds what one Append can make us hold in memory.
22
- export const MAX_ITEM_BYTES = 512 * 1024;
23
- // The AS2 actor types. A group is as much an actor as a person is.
24
- export const ACTOR_TYPES = new Set(['Person', 'Group', 'Service', 'Application', 'Organization']);
25
- // The AS2 types the fediverse actually posts. `Note` alone is Mastodon's world
26
- // and not the fediverse's: an Article is a Plume or WriteFreely post, a
27
- // Question is a poll, a Video is PeerTube, a Page is Lemmy, an Audio is
28
- // Funkwhale. Insisting on Note dead-lettered every one of them as "not a
29
- // verifiable Note" — from people the owner had chosen to follow, silently.
30
- //
31
- // They share the shape this code reads: attributedTo, content, published,
32
- // inReplyTo, tag, attachment. A poll's options are dropped, which is a
33
- // degraded rendering rather than a lost post.
34
- export const CONTENT_TYPES = new Set(['Note', 'Article', 'Question', 'Page', 'Video', 'Audio', 'Image', 'Event']);
35
-
36
- // A Question is a poll: its options live in oneOf (pick one) or anyOf (pick
37
- // several), each carrying the tally its author's server maintains.
38
- // Custom emojis ride the tag list; the images live at the author's server and
39
- // the client fetches them from there.
40
- // Content, name and summary were capped; nothing else was. One remote Question
41
- // with thousands of options, or thousands of emoji, mention or attachment
42
- // entries, put megabytes into a single statuses.json row — a document rewritten
43
- // whole on every change. These are display lists: past a few dozen, nothing can
44
- // render them and nobody meant them to be rendered.
45
- // http(s) only. An emoji, attachment or mention URL is written straight into
46
- // the client's markup, so `javascript:` and `data:` have no business in one.
47
- // The store has guarded avatars this way all along (safeUrl, lib/store.mjs);
48
- // these three lists were simply never put through it.
49
- export const httpOnly = (u) => {
50
- if (!u) return null;
51
- try {
52
- const p = new URL(String(u));
53
- return (p.protocol === 'https:' || p.protocol === 'http:') ? String(u) : null;
54
- } catch { return null; }
55
- };
56
-
57
- export const MAX_MODQUEUE = 200;
58
- export const MAX_EMOJIS = 60;
59
- export const MAX_POLL_OPTIONS = 50;
60
- export const MAX_OPTION_CHARS = 200;
61
- export const MAX_MENTIONS = 60;
62
- export const MAX_URL_CHARS = 2048;
63
-
64
- export function emojisOf(note) {
65
- return [].concat(note?.tag || [])
66
- .filter(t => t?.type === 'Emoji' && t.icon?.url && t.name)
67
- .slice(0, MAX_EMOJIS)
68
- .map(t => ({
69
- shortcode: String(t.name).replace(/^:|:$/g, '').slice(0, 64),
70
- url: httpOnly(String(t.icon.url).slice(0, MAX_URL_CHARS)),
71
- }))
72
- .filter(e => e.url);
73
- }
74
-
75
- export function pollOf(note) {
76
- const opts = note?.oneOf || note?.anyOf;
77
- if (!Array.isArray(opts) || !opts.length) return null;
78
- return {
79
- multiple: !!note.anyOf,
80
- expiresAt: note.endTime || null,
81
- closed: !!note.closed,
82
- options: opts.slice(0, MAX_POLL_OPTIONS).map(o => ({
83
- title: String(o?.name ?? '').slice(0, MAX_OPTION_CHARS),
84
- votes: Number(o?.replies?.totalItems) || 0,
85
- })),
86
- };
87
- }
88
- // AS2 lets `type` be one string or a list, and implementations use both —
89
- // `["Person","Service"]` is an ordinary actor. Read either form.
90
- export const typesOf = (t) => (Array.isArray(t) ? t : [t]).filter(x => typeof x === 'string');
91
- export const isContentType = (t) => typesOf(t).some(x => CONTENT_TYPES.has(x));
92
- export const isActorType = (t) => typesOf(t).some(x => ACTOR_TYPES.has(x));
93
-
94
- // What we will carry to our followers on someone else's behalf (§7.1.2): the
95
- // activities a conversation is made of, and nothing else. A type this file
96
- // does not handle falls out of handle() with no rejection, and "no rejection"
97
- // is what qualifies an activity for forwarding — so without this gate a
98
- // stranger could have anything at all, of a type nothing here reads,
99
- // re-delivered to every follower over our signature.
100
- export const FORWARDABLE = new Set(['Create', 'Update', 'Delete', 'Like', 'Announce', 'Undo']);
101
- // Of those, the ones we may re-deliver to our own followers over our own
102
- // signature (§7.1.2). Deliberately narrower than FORWARDABLE: these three are
103
- // the ones whose object this drain fetched from the author's origin and checked
104
- // before accepting. A Like, an Announce or an Undo is taken on the envelope's
105
- // word alone — relaying one is signing for a claim nothing corroborated.
106
- export const FORWARD_TYPES = new Set(['Create', 'Update', 'Delete']);
107
- // How many forwards one drain may send. A reply into a busy thread of ours is a
108
- // handful; anything near this is a flood using us as an amplifier.
109
- export const MAX_FORWARDS_PER_DRAIN = 20;
110
- export const ACCEPT_AP = 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
111
-
112
- // What is worth keeping of an activity we are filing rather than acting on.
113
- //
114
- // A dead letter, a moderation entry and a waiting follow request each used to
115
- // hold the WHOLE delivered activity — up to the 512 KB item cap — in documents
116
- // that are serialized and PUT whole on every change. A stranger could inflate
117
- // deadletter.json to ~100 MB and requests.json to ~250 MB and make us rewrite
118
- // them on every batch.
119
- //
120
- // Everything the consumers need survives: `acceptActivity` wraps this as the
121
- // Accept's object (and {id, type, actor, object} IS the standard Follow shape),
122
- // `applyModeration` reads only `object`, and a dead letter is read by a human
123
- // who wants to know what arrived, not to replay it.
124
- export function trimActivity(a) {
125
- if (!a || typeof a !== 'object') return a ?? null;
126
- const idOf = (v) => (typeof v === 'string' ? v : v?.id ?? null);
127
- const out = {};
128
- for (const k of ['id', 'type', 'actor', 'target']) {
129
- const v = idOf(a[k]);
130
- if (v) out[k] = String(v).slice(0, 2048);
131
- }
132
- const obj = idOf(a.object);
133
- if (obj) {
134
- // An Update says what to change INSIDE its object — a topic's new name, or
135
- // whether it is closed — so flattening the object to its id alone threw the
136
- // instruction away and left a bare topic id that meant nothing. Close,
137
- // reopen and rename all arrived at the queue saying only which topic, and
138
- // the moderator watched them do nothing. Kept now, bounded, and only the
139
- // two fields a moderation queue acts on: everything else is still dropped.
140
- const asked = a.object && typeof a.object === 'object' ? a.object : null;
141
- const said = {};
142
- if (asked) {
143
- if (typeof asked.name === 'string') said.name = asked.name.slice(0, 300);
144
- // `false` is the whole of a reopen, so presence is what counts, not truth.
145
- if ('closed' in asked) {
146
- // A date stays a date; a boolean stays a boolean; and the STRINGS
147
- // "true"/"false" — which is what a boolean looks like after a form, a
148
- // template or an RDF round trip — become the booleans they mean,
149
- // because `!!"false"` is true and would turn a reopen into a close.
150
- const v = asked.closed;
151
- said.closed = v === 'false' ? false : v === 'true' ? true
152
- : typeof v === 'string' ? v.slice(0, 64) : !!v;
153
- }
154
- }
155
- out.object = Object.keys(said).length
156
- ? { id: String(obj).slice(0, 2048), ...said }
157
- : String(obj).slice(0, 2048);
158
- }
159
- // A typed object with no id still says what it was — a Block of nobody, an
160
- // Undo of a Follow — and that is the whole of what the queue reads.
161
- else if (a.object && typeof a.object === 'object' && a.object.type) {
162
- out.object = { type: String(a.object.type).slice(0, 64) };
163
- }
164
- return out;
165
- }
166
-
167
- // Same origin AND, where the origin holds more than one identity, the same
168
- // identity within it.
169
- //
170
- // Origin alone is the right test when an origin is one person — the ordinary
171
- // fediverse server, and a subdomain pod. It is the WRONG test on a path-based
172
- // host, and above all on a multi-tenant front: every tenant of fedipod.net has
173
- // ids under `https://fedipod.net/u/<name>/`, so origin-equality made every
174
- // tenant able to vouch for every other. One tenant could publish posts
175
- // attributed to another, Update a neighbour's post, or Delete it — and a
176
- // receiving Mastodon would believe it for the same reason we did.
177
- //
178
- // So when both ids carry an identity prefix, the prefixes must match too. The
179
- // two shapes that exist here are the front's `/u/<name>/` and a pod's own AP
180
- // root (`…/<root>/ap/…`); anything else has no prefix and falls back to origin,
181
- // which is what a plain remote server should be judged by.
182
- function identityPrefix(u) {
183
- const m = /^(https?:\/\/[^/]+\/u\/[^/]+\/)/u.exec(u);
184
- if (m) return m[1];
185
- const ap = /^(https?:\/\/[^/]+\/(?:[^/]+\/)*?)ap\//u.exec(u);
186
- return ap ? ap[1] : null;
187
- }
188
-
189
- export function sameIdentity(a, b) {
190
- if (!sameOrigin(a, b)) return false;
191
- const pa = identityPrefix(String(a));
192
- const pb = identityPrefix(String(b));
193
- if (!pa || !pb) return true; // no prefix to compare: origin is the answer
194
- return pa === pb;
195
- }
196
-
197
- export function sameOrigin(a, b) {
198
- try { return new URL(a).origin === new URL(b).origin; } catch { return false; }
199
- }
200
-
201
- // Is this socket URL the pod's own? The scheme has to be the socket form of the
202
- // pod's — wss for https, ws for http — so a downgrade to plaintext from an https
203
- // pod is somewhere else, not the same place unencrypted.
204
- //
205
- // The host may be the pod's, or a PARENT of it. Not a loosening for
206
- // convenience: a CSS server that gives every pod a subdomain answers
207
- // notifications from the server root, so jeff-zucker.teamid.live is served by
208
- // wss://teamid.live/.notifications/… — which is the deployment this project
209
- // actually runs on. Requiring an exact match dropped it to polling, and the
210
- // live agents are how that was found rather than the suite.
211
- //
212
- // A sibling subdomain is still refused: only a suffix of our own host passes,
213
- // and two labels minimum so `.live` cannot pose as everyone's parent. Not a
214
- // public-suffix list — that is a dependency and a data file to keep current,
215
- // and the party this guards against is the pod you already chose to trust.
216
- //
217
- // `localhost` is the one single-label parent allowed, because it is the one that
218
- // cannot be anybody else: it is reserved to the loopback interface (RFC 6761),
219
- // so `alice.localhost` and `localhost` are the same machine by definition and
220
- // there is no stranger for the rule to keep out. Without this a pod served from
221
- // a subdomain of localhost — which is how a Solid server with subdomain pods
222
- // runs on a developer's machine — refused its own socket and fell back to
223
- // polling, so every delivery waited up to two minutes.
224
- export function sameSocketOrigin(socketUrl, podBase) {
225
- let s, p;
226
- try { s = new URL(socketUrl); p = new URL(podBase); } catch { return false; }
227
- if (s.protocol !== (p.protocol === 'https:' ? 'wss:' : 'ws:')) return false;
228
- if (s.host === p.host) return true;
229
- const parent = s.hostname.toLowerCase();
230
- return (parent.split('.').length >= 2 || parent === 'localhost')
231
- && s.port === p.port
232
- && p.hostname.toLowerCase().endsWith('.' + parent);
233
- }
234
-
235
- export function httpUrl(u) {
236
- try {
237
- const p = new URL(String(u)).protocol;
238
- return p === 'https:' || p === 'http:';
239
- } catch { return false; }
240
- }
241
-
242
- // Who a note is BY. A document may only speak for an actor at its own origin.
243
- // `attributedTo` used to be taken at face value, so a note served anywhere
244
- // could name anyone: one at a host the attacker controls, claiming to be by
245
- // someone the owner follows, passed every check we had — the envelope's
246
- // sameOrigin compares the ACTIVITY to its object, never the object to its
247
- // author — and landed in the home timeline and in the pod as them. For a group
248
- // it went further still, because amplify() gates on the author's membership,
249
- // so the group signed an Announce of it and delivered it to every member.
250
- //
251
- // `delivered` is the actor that brought it, used only when the note names no
252
- // author of its own; it has to clear the same test, which is why a boost of an
253
- // unattributed note is refused rather than credited to the booster.
254
- //
255
- // Returns the author, or null when nothing at the note's origin vouches for one.
256
- export function authorOf(note, delivered = null) {
257
- const claimed = [].concat(note?.attributedTo || [])
258
- .map(a => (typeof a === 'string' ? a : a?.id)).find(Boolean) || null;
259
- const author = claimed || delivered;
260
- if (!author) return null;
261
- // sameIdentity, not sameOrigin: on a multi-tenant front every tenant shares
262
- // an origin, so origin-equality let any of them be credited with any other's
263
- // post. See sameIdentity.
264
- return sameIdentity(note?.id, author) ? author : null;
265
- }
266
-
267
- // A reaction: an emoji somebody put on a post. Pleroma, Akkoma, Misskey and
268
- // Sharkey send EmojiReact; Misskey also sends a Like whose `content` is the
269
- // emoji, which is what its own users see as a reaction. Either way the
270
- // emoji is the content — a unicode character, or `:name:` for a custom one
271
- // whose image rides the tag list. Null when the activity is a plain Like.
272
- export const MAX_REACTION_CHARS = 64;
273
- export function reactionOf(activity) {
274
- if (!activity || (activity.type !== 'EmojiReact' && activity.type !== 'Like')) return null;
275
- const content = typeof activity.content === 'string' ? activity.content.trim() : '';
276
- if (!content) return null;
277
- const emoji = content.slice(0, MAX_REACTION_CHARS);
278
- const bare = emoji.replace(/^:|:$/g, '');
279
- const custom = emojisOf(activity).find(e => e.shortcode === bare);
280
- return { emoji, ...(custom ? { url: custom.url } : {}) };
281
- }
282
-
283
- // What a note quotes, whichever way its server says it. Mastodon writes
284
- // FEP-044f's `quote` alongside the two older spellings, Fedibird's quoteUri
285
- // and Misskey's _misskey_quote; Pleroma writes quoteUrl; FEP-e232 puts a Link
286
- // in the tag list with the Misskey relation. One post, one quote.
287
- const QUOTE_REL = 'https://misskey-hub.net/ns#_misskey_quote';
288
- export function quoteOf(note) {
289
- const idOf = (v) => (typeof v === 'string' ? v : v?.id);
290
- const named = [note?.quote, note?.quoteUri, note?._misskey_quote, note?.quoteUrl]
291
- .map(v => idOf(Array.isArray(v) ? v[0] : v)).find(Boolean);
292
- const linked = [].concat(note?.tag || []).find(t => t?.type === 'Link' && t.href
293
- && [].concat(t.rel || []).includes(QUOTE_REL))?.href;
294
- const q = named || linked;
295
- return q && q !== note?.id ? httpOnly(String(q).slice(0, MAX_URL_CHARS)) : null;
296
- }
297
-
298
- // Who the author lets quote a post: their interaction policy's canQuote, in
299
- // the words a client uses. The public collection is `public`; the author's
300
- // own followers or following collections are `followers` and `following`.
301
- // Null when the note says nothing, which is the common case on servers that
302
- // predate the policy.
303
- export function quotePolicyOf(note, author) {
304
- const can = note?.interactionPolicy?.canQuote;
305
- if (!can || typeof can !== 'object') return null;
306
- const word = (v) => {
307
- const id = typeof v === 'string' ? v : v?.id;
308
- if (!id) return null;
309
- if (id === 'https://www.w3.org/ns/activitystreams#Public' || id === 'as:Public' || id === 'Public') return 'public';
310
- if (author && sameIdentity(id, author)) {
311
- if (/\/followers\/?$/u.test(id)) return 'followers';
312
- if (/\/following\/?$/u.test(id)) return 'following';
313
- }
314
- return null;
315
- };
316
- const list = (v) => [...new Set([].concat(v || []).map(word).filter(Boolean))];
317
- return { automatic: list(can.automaticApproval), manual: list(can.manualApproval) };
318
- }
@@ -1,144 +0,0 @@
1
- // channel.mjs — the inbox's push channel: a WebSocketChannel2023 subscription
2
- // on the pod's inbox container, reconnecting with backoff, and the drain it
3
- // wakes. State (ws, wsState, reconnectTries, resubTimer) lives on the Intake
4
- // so status pages and stop() see it; this module is the behaviour.
5
-
6
- import * as podNotifications from '../../pod/notifications.mjs';
7
- import { USER_AGENT } from '../../shared/ua.mjs';
8
- import { HTTP_TIMEOUT_MS, readCapped } from '../../shared/safefetch.mjs';
9
- import { sameSocketOrigin } from './activity.mjs';
10
-
11
- // The channel a subscription returns outlives a dropped socket, so reconnecting
12
- // reuses it. Creating a new one per reconnect is what buried solidcommunity.net
13
- // in channel records they then had to sweep.
14
- const CHANNEL_DOC = 'inbox-channel.json';
15
- // A flapping socket used to POST a NEW WebSocketChannel2023 channel every two
16
- // seconds — hundreds an hour against a server that is already struggling, and
17
- // channel churn its operators have to sweep up. Backs off instead, and an open
18
- // only triggers a sweep if we have not just swept.
19
- const RECONNECT_MIN_MS = 2_000;
20
- const RECONNECT_MAX_MS = 5 * 60_000;
21
- // How long a socket must stay up before the backoff counts it as a success and
22
- // resets. Shorter than that is a flap, not a connection.
23
- const RECONNECT_STABLE_MS = 60_000;
24
- const OPEN_DRAIN_MIN_GAP_MS = 30_000;
25
-
26
- // Jittered exponential, floor to ceiling, reset by a successful open.
27
- export function reconnectDelay(intake) {
28
- const capped = Math.min(RECONNECT_MIN_MS * 2 ** intake.reconnectTries, RECONNECT_MAX_MS);
29
- intake.reconnectTries++;
30
- return Math.round(capped * (0.8 + Math.random() * 0.4));
31
- }
32
-
33
- // --- push ---
34
- // Any failure in here used to end push for the life of the process: the
35
- // retry lived only in the "server refused the subscription" branch, so a
36
- // network blip left wsState at never-connected and the agent silently on
37
- // polling. Every path now schedules a retry on the same backoff.
38
- export async function subscribe(intake) {
39
- try {
40
- await intake._subscribeOnce();
41
- } catch (e) {
42
- intake.wsState = 'subscribe-error';
43
- const wait = intake._reconnectDelay();
44
- intake.log(`subscribe failed (${e.message}) — retrying in ${Math.round(wait / 1000)}s (polling meanwhile)`);
45
- if (!intake.stopped) {
46
- intake.resubTimer = setTimeout(() => intake.subscribe().catch(() => {}), wait);
47
- intake.resubTimer.unref?.();
48
- }
49
- }
50
- }
51
-
52
- /**
53
- * Where this pod describes the services it offers. The pod says so on any
54
- * response about one of its resources; the well-known path is only what a
55
- * pod that says nothing has always used.
56
- */
57
- export async function storageDescriptionUrl(intake) {
58
- return podNotifications.storageDescriptionUrl(intake.urls.base,
59
- { headers: { 'user-agent': USER_AGENT }, timeoutMs: HTTP_TIMEOUT_MS });
60
- }
61
-
62
- export async function subscribeOnce(intake) {
63
- // Reuse a channel we already have rather than asking for another one.
64
- const saved = intake.store.read(CHANNEL_DOC, null);
65
- if (saved?.receiveFrom && (!saved.endAt || Date.parse(saved.endAt) - Date.now() > 60_000)) {
66
- intake._openSocket(saved.receiveFrom, true);
67
- return;
68
- }
69
- const descUrl = await intake._storageDescriptionUrl();
70
- const { channel, error } = await podNotifications.readWebSocketChannel(descUrl,
71
- { headers: { 'user-agent': USER_AGENT }, timeoutMs: HTTP_TIMEOUT_MS });
72
- if (!channel) { intake.wsState = 'unavailable'; intake.log(`${error} — polling only`); return; }
73
- // The topic is a POD resource, and it travels in the BODY — so the url map
74
- // RemotePod applies to the request line never reaches it. A fronted
75
- // identity's inbox url names the front, which the pod cannot grant read on,
76
- // and the subscription came back 403. A no-op when unfronted.
77
- const topic = intake.urls.toPod ? intake.urls.toPod(intake.urls.inbox) : intake.urls.inbox;
78
- const sub = await podNotifications.subscribeToInbox(intake.remote,
79
- { channelUrl: channel, podTopicUrl: topic });
80
- const body = await readCapped(sub).then(JSON.parse).catch(() => null);
81
- if (!body?.receiveFrom) {
82
- intake.wsState = `subscribe-failed-${sub.status}`;
83
- const wait = intake._reconnectDelay();
84
- intake.log(`subscription failed (${sub.status}) — retrying in ${Math.round(wait / 1000)}s (polling meanwhile)`);
85
- if (!intake.stopped) {
86
- intake.resubTimer = setTimeout(() => intake.subscribe().catch(e => intake.log(`resubscribe: ${e.message}`)), wait);
87
- intake.resubTimer.unref?.();
88
- }
89
- return;
90
- }
91
- intake.store.write(CHANNEL_DOC, { receiveFrom: body.receiveFrom, endAt: body.endAt || null });
92
- intake._openSocket(body.receiveFrom, false);
93
- }
94
-
95
- // The socket URL arrives in the pod's own subscription response, and it was
96
- // the one outbound address in the project that reached the network without
97
- // passing anything — safefetch guards every fetch, and `new WebSocket()` is
98
- // not a fetch. A pod that answered with somebody else's address had us open a
99
- // long-lived connection there and treat what came back as our inbox waking up.
100
- //
101
- // Same origin as the pod, not assertPublicUrl: a pod on this machine is a
102
- // documented setup and its socket is legitimately ws://localhost:3000, which
103
- // a public-address check would refuse.
104
- export function openSocket(intake, receiveFrom, reused) {
105
- if (!sameSocketOrigin(receiveFrom, intake.urls.base)) {
106
- intake.wsState = 'refused';
107
- intake.log(`subscription named ${receiveFrom}, which is not this pod — polling only`);
108
- if (reused) intake.store.write(CHANNEL_DOC, null);
109
- return;
110
- }
111
- intake.ws = new WebSocket(receiveFrom);
112
- intake.ws.onopen = () => {
113
- intake.wsState = 'open';
114
- if (!intake._announcedPush) { intake.log('inbox push subscription active'); intake._announcedPush = true; }
115
- intake._openedAt = Date.now();
116
- // Anything that arrived while the socket was down is waiting — sweep it,
117
- // unless a sweep just ran: a flapping socket must not re-list the inbox
118
- // on every open.
119
- if (Date.now() - intake.lastDrainAtMs > OPEN_DRAIN_MIN_GAP_MS) {
120
- intake.drain().catch(e => intake.log(`drain: ${e.message}`));
121
- }
122
- };
123
- intake.ws.onmessage = () => intake.drain().catch(e => intake.log(`drain: ${e.message}`));
124
- intake.ws.onclose = () => {
125
- intake.wsState = 'closed';
126
- // Only a connection that STAYED up counts as a success. Resetting on open
127
- // alone meant the 2026-07-29 failure — a server that accepts the upgrade
128
- // and then drops the socket on a crash cycle — reconnected at the 2s
129
- // floor indefinitely: every cycle "succeeded", so the exponential cap was
130
- // never reached, and each open also drained the inbox.
131
- if (intake._openedAt && Date.now() - intake._openedAt >= RECONNECT_STABLE_MS) intake.reconnectTries = 0;
132
- intake._openedAt = 0;
133
- if (!intake.stopped) {
134
- intake.resubTimer = setTimeout(() => intake.subscribe().catch(e => intake.log(`resubscribe: ${e.message}`)), intake._reconnectDelay());
135
- intake.resubTimer.unref?.();
136
- }
137
- };
138
- intake.ws.onerror = () => {
139
- intake.wsState = 'error';
140
- // A channel we reused may simply be gone: forget it so the next attempt
141
- // asks for a fresh one instead of retrying a dead URL forever.
142
- if (reused) intake.store.write(CHANNEL_DOC, null);
143
- };
144
- }