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,408 +0,0 @@
1
- // fedi-account.mjs — type a Fediverse handle or a WebID, and get back an
2
- // account you can act with: post, read its timeline, follow, reply,
3
- // favourite, boost. The library works out whether the account lives on a
4
- // Mastodon-family server or on a Solid pod through FediPod, and speaks to
5
- // whichever it is. The app never has to know which it got.
6
- //
7
- // import { fediAccount } from './fedi-account.mjs';
8
- // const accounts = fediAccount({ dbName: 'my-app', clientName: 'My app' });
9
- //
10
- // await accounts.resume(); // on every page load
11
- // await accounts.login('@kwame@mastodon.social'); // or '@mei@fedipod.net', or a WebID
12
- // const me = await accounts.current(); // null, or the account below
13
- //
14
- // me.handle, me.kind ('mastodon' | 'pod'), me.notice (a sentence to show, or null)
15
- // await me.post({ text }); await me.timeline({ limit: 20 })
16
- // await me.reply(postUrlOrId, text); await me.follow('@aisha@her.server')
17
- // await me.favourite(postUrlOrId); await me.boost(postUrlOrId)
18
- // await me.outbox({ limit: 20 }); // this account's own posts, newest first
19
- // await me.outbox({ rdf: true }); // the same, plus the real RDF graph on .rdf
20
- // await me.signOut()
21
- //
22
- // A Mastodon account is used through its server's API with a token the
23
- // person granted this app; the token stays in this browser. A pod account
24
- // is used through its FediPod outbox door with the person's pod sign-in:
25
- // what is posted there is carried out by the account's own agent. When that
26
- // agent is a browser at fedipod.net, the account carries a notice saying so.
27
- //
28
- // No imports but the two files beside it, no Node built-ins, nothing named
29
- // after the application that uses it.
30
- import { fediLogin, parseAddress } from './fedi-login.mjs';
31
-
32
- const AS = 'https://www.w3.org/ns/activitystreams';
33
- const PUBLIC = `${AS}#Public`;
34
- const AS_OUTBOX = `${AS}#outbox`;
35
- const FOAF_ACCOUNT = 'http://xmlns.com/foaf/0.1/account';
36
- const DEFAULT_SCOPES = 'read write follow';
37
- const PENDING_TTL_MS = 10 * 60_000;
38
-
39
- export const BROWSER_ACCOUNT_NOTICE = 'Because you have a browser-based account, your posts and interactions will only go out to the Fediverse when your browser is opened to your account.';
40
-
41
- const esc = (s) => String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
42
- const toHtml = (text) => `<p>${esc(text).replace(/\r?\n/g, '<br>')}</p>`;
43
- const memoryStorage = () => { const m = new Map(); return { getItem: (k) => (m.has(k) ? m.get(k) : null), setItem: (k, v) => m.set(k, String(v)), removeItem: (k) => m.delete(k) }; };
44
-
45
- export function fediAccount({
46
- dbName = 'fediverse-account', clientName = 'Fediverse app', redirectUri = null, scopes = DEFAULT_SCOPES,
47
- website = null, fetch: f = globalThis.fetch?.bind(globalThis), storage = globalThis.localStorage || memoryStorage(),
48
- } = {}) {
49
- const pod = fediLogin({ dbName, clientName, redirectUri, fetch: f });
50
- const here = () => (globalThis.location ? location.origin + location.pathname : null);
51
- const key = (k) => `${dbName}:${k}`;
52
- const read = (k) => { try { return JSON.parse(storage.getItem(key(k)) || 'null'); } catch { return null; } };
53
- const write = (k, v) => storage.setItem(key(k), JSON.stringify(v));
54
- const drop = (k) => storage.removeItem(key(k));
55
- const json = (url, init = {}) => f(url, { ...init, headers: { accept: 'application/json', ...(init.headers || {}) } })
56
- .then((r) => (r.ok ? r.json() : null)).catch(() => null);
57
- const said = async (res, host) => {
58
- const body = await res.json().catch(() => ({}));
59
- if (!res.ok) throw new Error(body.error_description || body.error || `${host} answered ${res.status}`);
60
- return body;
61
- };
62
-
63
- // Only loaded when `outbox({ rdf: true })` is actually called, so nobody
64
- // pays for a JSON-LD parser just by importing this file. Not in
65
- // `dependencies` — the app supplies "jsonld" if it wants this flag to work.
66
- const loadJsonLd = async () => {
67
- try { return (await import('jsonld')).default; }
68
- catch { throw new Error('outbox({ rdf: true }) needs the "jsonld" package available to the app — it is not bundled with this library'); }
69
- };
70
-
71
- // The account's real outbox, read from the pod: the head collection names
72
- // `first`, each page names `orderedItems` and (while there is more) `next`.
73
- // Walked only far enough to cover `limit`, and run through jsonld.toRDF so
74
- // the result is a genuine RDF/JS quad array, not this library's own shape.
75
- const fetchOutboxRdf = async (actorUrl, limit) => {
76
- if (!actorUrl) return null;
77
- const jsonld = await loadJsonLd();
78
- const actorDoc = await json(actorUrl, { headers: { accept: 'application/activity+json' } });
79
- let pageUrl = actorDoc?.outbox ? (await json(actorDoc.outbox, { headers: { accept: 'application/activity+json' } }))?.first : null;
80
- const quads = []; const seen = new Set(); let collected = 0;
81
- while (pageUrl && collected < limit && !seen.has(pageUrl)) {
82
- seen.add(pageUrl);
83
- const page = await json(pageUrl, { headers: { accept: 'application/activity+json' } });
84
- if (!page) break;
85
- quads.push(...(await jsonld.toRDF(page)));
86
- collected += (page.orderedItems || []).length;
87
- pageUrl = page.next || null;
88
- }
89
- return quads;
90
- };
91
-
92
- // ---- finding out what an address is ----
93
-
94
- // Does this host speak the Mastodon API? Any server of that family says so
95
- // here; a pod, a Gateway and a plain web host do not.
96
- const speaksMastodon = async (host) => {
97
- const inst = await json(`https://${host}/api/v1/instance`);
98
- return !!(inst && (inst.uri || inst.domain || inst.title));
99
- };
100
-
101
- const webfinger = async (name, host) => {
102
- const jrd = await json(`https://${host}/.well-known/webfinger?resource=${encodeURIComponent(`acct:${name}@${host}`)}`,
103
- { headers: { accept: 'application/jrd+json, application/json' } });
104
- if (!jrd) throw new Error(`${host} does not know @${name}@${host}`);
105
- const self = (jrd.links || []).find((l) => l.rel === 'self' && /activity\+json|ld\+json/u.test(l.type || ''));
106
- return { actor: self?.href || null, aliases: (jrd.aliases || []).filter((u) => typeof u === 'string') };
107
- };
108
-
109
- // Where a pod account keeps its records: the actor on the pod itself is
110
- // `<root>ap/actor`, and the aliases name it even when the address is at a
111
- // Gateway.
112
- const rootOf = (actor, aliases) => {
113
- const onPod = [...aliases, actor].find((u) => typeof u === 'string' && /\/ap\/actor$/u.test(u) && (!actor || new URL(u).origin !== new URL(actor).origin))
114
- || [...aliases, actor].find((u) => typeof u === 'string' && /\/ap\/actor$/u.test(u));
115
- return onPod ? onPod.replace(/ap\/actor$/u, '') : null;
116
- };
117
-
118
- /** What an address is, without signing in: { kind: 'mastodon' | 'pod', host, handle, actor, … }. */
119
- async function describe(address) {
120
- const a = parseAddress(address);
121
- if (!a) throw new Error('an address looks like @you@your.server, or is your WebID');
122
- if (a.url) {
123
- // A WebID: the account it names leads to the handle, and the handle to
124
- // everything else.
125
- const card = await json(a.url, { headers: { accept: 'application/ld+json' } });
126
- const nodes = Array.isArray(card) ? card : (card?.['@graph'] || (card ? [card] : []));
127
- const me = nodes.find((n) => n?.['@id'] === a.url) || {};
128
- const actorId = [].concat(me[FOAF_ACCOUNT] || []).map((v) => v?.['@id'] || v).find((v) => typeof v === 'string');
129
- if (!actorId) throw new Error(`${a.host} names no Fediverse account on that WebID`);
130
- const actor = await json(actorId, { headers: { accept: 'application/activity+json' } });
131
- if (!actor?.preferredUsername) throw new Error(`the account ${actorId} could not be read`);
132
- return describe(`@${actor.preferredUsername}@${new URL(actorId).host}`);
133
- }
134
- const { actor, aliases } = await webfinger(a.name, a.host);
135
- const actorHost = actor ? new URL(actor).host : a.host;
136
- for (const host of [...new Set([actorHost, a.host])]) {
137
- if (await speaksMastodon(host)) return { kind: 'mastodon', host, handle: a.at, actor };
138
- }
139
- const where = await pod.resolve(a.at);
140
- return { kind: 'pod', host: a.host, handle: a.at, actor: actor || where.actor, issuer: where.issuer, root: rootOf(actor || where.actor, aliases) };
141
- }
142
-
143
- // ---- signing in ----
144
-
145
- const appFor = async (host, ru) => {
146
- const apps = read('masto:apps') || {};
147
- const known = apps[host];
148
- if (known?.redirectUri === ru && known.clientId) return known;
149
- const res = await f(`https://${host}/api/v1/apps`, {
150
- method: 'POST', headers: { 'content-type': 'application/json' },
151
- body: JSON.stringify({ client_name: clientName, redirect_uris: ru, scopes, ...(website ? { website } : {}) }),
152
- });
153
- const body = await said(res, host);
154
- if (!body.client_id) throw new Error(`${host} registered no client`);
155
- const app = { host, redirectUri: ru, clientId: body.client_id, clientSecret: body.client_secret || null };
156
- write('masto:apps', { ...apps, [host]: app });
157
- return app;
158
- };
159
-
160
- /** Everything `login` does short of leaving the page: what the address is and the page to send the person to. */
161
- async function startLogin(address, { returnTo = globalThis.location?.href || null, redirectUri: ru = redirectUri || here() } = {}) {
162
- const what = await describe(address);
163
- if (what.kind === 'pod') {
164
- const s = await pod.startLogin(what.handle, { returnTo, redirectUri: ru });
165
- write('pod:pending', { handle: what.handle, actor: what.actor, root: what.root, at: Date.now() });
166
- return { ...what, authorizationUrl: s.authorizationUrl, returnTo };
167
- }
168
- const app = await appFor(what.host, ru);
169
- const bytes = crypto.getRandomValues(new Uint8Array(24));
170
- const state = [...bytes].map((b) => b.toString(16).padStart(2, '0')).join('');
171
- write('masto:pending', { state, host: what.host, redirectUri: ru, returnTo, at: Date.now() });
172
- const u = new URL(`https://${what.host}/oauth/authorize`);
173
- for (const [k, v] of Object.entries({ client_id: app.clientId, redirect_uri: ru, response_type: 'code', scope: scopes, state })) u.searchParams.set(k, v);
174
- return { ...what, authorizationUrl: u.href, returnTo };
175
- }
176
-
177
- /** Send the person to wherever this account signs in. They come back to this
178
- * page (unless `redirectUri` says otherwise) and `resume` returns them to
179
- * where they were (unless `returnTo` says otherwise). */
180
- async function login(address, opts) {
181
- const s = await startLogin(address, opts);
182
- location.href = s.authorizationUrl;
183
- return s;
184
- }
185
-
186
- /** Call on every page load. Finishes a sign-in the person is coming back
187
- * from, of either kind, and returns them to where they were. Returns the
188
- * account, or null when nobody is coming back. */
189
- async function resume({ currentUrl = globalThis.location?.href, go = (u) => location.replace(u) } = {}) {
190
- const u = new URL(currentUrl);
191
- const code = u.searchParams.get('code'); const state = u.searchParams.get('state');
192
- const pend = read('masto:pending');
193
- if (code && state && pend?.state === state) {
194
- drop('masto:pending');
195
- if (Date.now() - pend.at > PENDING_TTL_MS) throw new Error('that sign-in has expired — start again');
196
- const app = await appFor(pend.host, pend.redirectUri);
197
- const tok = await said(await f(`https://${pend.host}/oauth/token`, {
198
- method: 'POST', headers: { 'content-type': 'application/json' },
199
- body: JSON.stringify({ grant_type: 'authorization_code', code, redirect_uri: pend.redirectUri, client_id: app.clientId, ...(app.clientSecret ? { client_secret: app.clientSecret } : {}), scope: scopes }),
200
- }), pend.host);
201
- if (!tok.access_token) throw new Error(`${pend.host} returned no token`);
202
- const me = await said(await f(`https://${pend.host}/api/v1/accounts/verify_credentials`, { headers: { authorization: `Bearer ${tok.access_token}` } }), pend.host);
203
- write('masto:account', { host: pend.host, token: tok.access_token, handle: `@${me.username}@${pend.host}`, name: me.display_name || me.username, actor: me.url || me.uri || null });
204
- drop('pod:facts');
205
- if (pend.returnTo && pend.returnTo !== currentUrl) go(pend.returnTo);
206
- return current();
207
- }
208
- const session = await pod.resume({ currentUrl, go: () => {} });
209
- if (!session) return null;
210
- const p = read('pod:pending') || {};
211
- drop('pod:pending');
212
- const facts = await podFacts(session, p);
213
- write('pod:facts', facts);
214
- drop('masto:account');
215
- if (session.returnTo && session.returnTo !== currentUrl) go(session.returnTo);
216
- return current();
217
- }
218
-
219
- // What a pod account needs, found once at sign-in: its door, its records,
220
- // its followers collection, and whether its agent is a browser.
221
- const podFacts = async (session, p) => {
222
- const card = await json(session.webId, { headers: { accept: 'application/ld+json' } });
223
- const nodes = Array.isArray(card) ? card : (card?.['@graph'] || (card ? [card] : []));
224
- const me = nodes.find((n) => n?.['@id'] === session.webId) || {};
225
- const pick = (node, k) => [].concat(node?.[k] || []).map((v) => v?.['@id'] || v).find((v) => typeof v === 'string') || null;
226
- let actor = p.actor || pick(me, FOAF_ACCOUNT);
227
- let door = pick(me, AS_OUTBOX);
228
- const doc = actor ? await json(actor, { headers: { accept: 'application/activity+json' } }) : null;
229
- if (!door && doc?.outbox) door = doc.outbox;
230
- let root = p.root || null;
231
- if (!root && doc?.preferredUsername && actor) {
232
- const { aliases } = await webfinger(doc.preferredUsername, new URL(actor).host).catch(() => ({ aliases: [] }));
233
- root = rootOf(actor, aliases);
234
- }
235
- const handle = p.handle || (doc?.preferredUsername && actor ? `@${doc.preferredUsername}@${new URL(actor).host}` : null);
236
- // A browser-made account keeps its signing key on the pod sealed under
237
- // the password; a device keeps its key with itself. The shape says which.
238
- let browserBased = false;
239
- if (root) {
240
- const keys = await session.fetch(`${root}ap-state/keys.json`, { headers: { accept: 'application/json' } }).then((r) => (r.ok ? r.json() : null)).catch(() => null);
241
- const envelope = keys && (keys.v === 1 && typeof keys.ct === 'string' && typeof keys.salt === 'string'
242
- || Object.values(keys).some((k) => k && k.v === 1 && typeof k.ct === 'string' && typeof k.salt === 'string'));
243
- browserBased = !!envelope;
244
- }
245
- return { handle, actor, name: doc?.name || null, door, root, followers: doc?.followers || null, webId: session.webId, browserBased };
246
- };
247
-
248
- // ---- the account ----
249
-
250
- const mastodonAccount = (a) => {
251
- const base = `https://${a.host}`;
252
- const auth = { authorization: `Bearer ${a.token}` };
253
- const call = (path, init = {}) => f(`${base}${path}`, { ...init, headers: { ...auth, ...(init.headers || {}) } });
254
- const post = (path, body) => call(path, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body || {}) }).then((r) => said(r, a.host));
255
- // A post named by its address is found on this server by a search that
256
- // fetches it if need be; one named by an id here is used as it is.
257
- const idOf = async (target) => {
258
- if (!/^https?:\/\//u.test(String(target))) return String(target);
259
- const u = new URL(`${base}/api/v2/search`);
260
- u.searchParams.set('q', target); u.searchParams.set('resolve', 'true'); u.searchParams.set('type', 'statuses'); u.searchParams.set('limit', '1');
261
- const body = await call(u.href).then((r) => said(r, a.host));
262
- const id = body.statuses?.[0]?.id;
263
- if (!id) throw new Error(`${a.host} could not find that post`);
264
- return id;
265
- };
266
- const item = (s) => ({
267
- id: s.id, url: s.url || s.uri || null, published: s.created_at,
268
- author: { id: s.account?.url || null, handle: s.account?.acct ? `@${s.account.acct}${s.account.acct.includes('@') ? '' : '@' + a.host}` : null, name: s.account?.display_name || s.account?.username || null },
269
- html: s.content || '', inReplyTo: s.in_reply_to_id || null,
270
- ...(s.reblog ? { boostOf: item(s.reblog) } : {}),
271
- });
272
- return {
273
- kind: 'mastodon', handle: a.handle, name: a.name, actor: a.actor, notice: null,
274
- fetch: call,
275
- async profile() {
276
- const me = await call('/api/v1/accounts/verify_credentials').then((r) => said(r, a.host));
277
- return { handle: a.handle, name: me.display_name || me.username || null, url: me.url || null, avatar: me.avatar || null, bio: me.note || '',
278
- followers: me.followers_count ?? null, following: me.following_count ?? null, posts: me.statuses_count ?? null, actor: a.actor, webId: null };
279
- },
280
- async post({ text, inReplyTo = null, visibility = 'public' }) {
281
- const made = await post('/api/v1/statuses', { status: text, visibility, ...(inReplyTo ? { in_reply_to_id: await idOf(inReplyTo) } : {}) });
282
- return { id: made.id, url: made.url || made.uri || null, sent: true };
283
- },
284
- reply(target, text) { return this.post({ text, inReplyTo: target }); },
285
- async timeline({ limit = 20 } = {}) {
286
- const list = await call(`/api/v1/timelines/home?limit=${Math.min(40, limit)}`).then((r) => said(r, a.host));
287
- return (Array.isArray(list) ? list : []).map(item);
288
- },
289
- // This account's own posts, newest first — Mastodon's equivalent of an
290
- // outbox. A Mastodon server IS an ActivityPub server, so its actor and
291
- // outbox are real AS2/JSON-LD too, same as a pod's; `rdf: true` reads
292
- // that, not the REST API above. It comes back empty on an instance that
293
- // requires a signed request just to read the public actor document
294
- // (some do — mastodon.social among them); `fetchOutboxRdf`'s plain GET
295
- // then gets 401 and the loop below finds nothing to walk.
296
- async outbox({ limit = 20, rdf = false } = {}) {
297
- const me = await call('/api/v1/accounts/verify_credentials').then((r) => said(r, a.host));
298
- const list = await call(`/api/v1/accounts/${me.id}/statuses?limit=${Math.min(40, limit)}`).then((r) => said(r, a.host));
299
- const items = (Array.isArray(list) ? list : []).map(item);
300
- if (rdf) items.rdf = await fetchOutboxRdf(a.actor, limit);
301
- return items;
302
- },
303
- async follow(handle) {
304
- const who = parseAddress(handle);
305
- if (!who?.at) throw new Error('a handle looks like @you@your.server');
306
- const acct = await call(`/api/v1/accounts/lookup?acct=${encodeURIComponent(who.at.slice(1))}`).then((r) => said(r, a.host));
307
- if (!acct.id) throw new Error(`${a.host} could not find ${who.at}`);
308
- await post(`/api/v1/accounts/${acct.id}/follow`);
309
- return { followed: who.at, sent: true };
310
- },
311
- async favourite(target) { await post(`/api/v1/statuses/${await idOf(target)}/favourite`); return { sent: true }; },
312
- async boost(target) { await post(`/api/v1/statuses/${await idOf(target)}/reblog`); return { sent: true }; },
313
- async signOut() {
314
- const apps = read('masto:apps') || {};
315
- const app = apps[a.host];
316
- if (app) {
317
- try { await f(`${base}/oauth/revoke`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ client_id: app.clientId, client_secret: app.clientSecret, token: a.token }) }); } catch { /* told if we could */ }
318
- }
319
- drop('masto:account');
320
- },
321
- };
322
- };
323
-
324
- const podAccount = (session, facts) => {
325
- const knock = async (activity) => {
326
- if (!facts.door) throw new Error('this account names no outbox to post through');
327
- const res = await session.fetch(facts.door, { method: 'POST', headers: { 'content-type': 'application/activity+json' }, body: JSON.stringify({ '@context': AS, ...activity }) });
328
- if (!res.ok) { const body = await res.text().catch(() => ''); throw new Error(`the outbox refused it (HTTP ${res.status})${body ? `: ${body.slice(0, 200)}` : ''}`); }
329
- return { id: res.headers.get('location') || null, sent: res.status === 201, queued: res.status === 202 };
330
- };
331
- const audience = (visibility) => {
332
- if (visibility === 'unlisted') return { to: facts.followers ? [facts.followers] : [], cc: [PUBLIC] };
333
- if (visibility === 'private' || visibility === 'followers') return { to: facts.followers ? [facts.followers] : [], cc: [] };
334
- return { to: [PUBLIC], cc: facts.followers ? [facts.followers] : [] };
335
- };
336
- const state = (name) => (facts.root ? session.fetch(`${facts.root}ap-state/${name}`, { headers: { accept: 'application/json' } }).then((r) => (r.ok ? r.json() : null)).catch(() => null) : Promise.resolve(null));
337
- return {
338
- kind: 'pod', handle: facts.handle, name: facts.name, actor: facts.actor, webId: facts.webId,
339
- notice: facts.browserBased ? BROWSER_ACCOUNT_NOTICE : null,
340
- fetch: session.fetch,
341
- async profile() {
342
- const doc = facts.actor ? await json(facts.actor, { headers: { accept: 'application/activity+json' } }) : null;
343
- const count = async (url) => (url ? (await json(url, { headers: { accept: 'application/activity+json' } }))?.totalItems ?? null : null);
344
- return { handle: facts.handle, name: doc?.name || null, url: doc?.url || null, avatar: doc?.icon?.url || (typeof doc?.icon === 'string' ? doc.icon : null), bio: doc?.summary || '',
345
- followers: await count(doc?.followers), following: await count(doc?.following), posts: await count(doc?.outbox), actor: facts.actor, webId: facts.webId };
346
- },
347
- async post({ text, inReplyTo = null, visibility = 'public' }) {
348
- const made = await knock({ type: 'Note', content: toHtml(text), source: { content: text, mediaType: 'text/plain' }, ...(inReplyTo ? { inReplyTo } : {}), ...audience(visibility) });
349
- return { id: made.id, url: made.id, sent: made.sent, queued: made.queued };
350
- },
351
- reply(target, text) { return this.post({ text, inReplyTo: target }); },
352
- async timeline({ limit = 20 } = {}) {
353
- const rows = await state('statuses.json');
354
- const actors = (await state('actors.json')) || {};
355
- const who = (id) => { const d = actors[id]?.doc || actors[id] || {}; return { id, handle: d.preferredUsername ? `@${d.preferredUsername}@${new URL(id).host}` : null, name: d.name || null }; };
356
- return (Array.isArray(rows) ? rows : [])
357
- .filter((s) => s.kind !== 'remote' && s.kind !== 'mention' && !s.direct && s.visibility !== 'direct')
358
- .sort((x, y) => String(y.published || '').localeCompare(String(x.published || '')))
359
- .slice(0, limit)
360
- .map((s) => ({ id: s.noteId, url: s.noteId, published: s.published || null, author: who(s.actor), html: s.content || '', inReplyTo: s.inReplyTo || null }));
361
- },
362
- // This account's own posts, newest first, read from the same local
363
- // record `timeline` uses (fast, no network call). With `rdf: true`,
364
- // also fetches the real outbox from the pod and parses it into RDF/JS
365
- // quads on the returned array's `.rdf` — a second, live document, not
366
- // derived from the local record, so it needs `jsonld` (see loadJsonLd).
367
- async outbox({ limit = 20, rdf = false } = {}) {
368
- const rows = await state('statuses.json');
369
- const actors = (await state('actors.json')) || {};
370
- const who = (id) => { const d = actors[id]?.doc || actors[id] || {}; return { id, handle: d.preferredUsername ? `@${d.preferredUsername}@${new URL(id).host}` : null, name: d.name || null }; };
371
- const items = (Array.isArray(rows) ? rows : [])
372
- .filter((s) => s.kind === 'post')
373
- .sort((x, y) => String(y.published || '').localeCompare(String(x.published || '')))
374
- .slice(0, limit)
375
- .map((s) => ({ id: s.noteId, url: s.noteId, published: s.published || null, author: who(s.actor), html: s.content || '', inReplyTo: s.inReplyTo || null }));
376
- if (rdf) items.rdf = await fetchOutboxRdf(facts.actor, limit);
377
- return items;
378
- },
379
- async follow(handle) {
380
- const who = parseAddress(handle);
381
- if (!who?.at) throw new Error('a handle looks like @you@your.server');
382
- const made = await knock({ type: 'Follow', object: who.at });
383
- return { followed: who.at, sent: made.sent, queued: made.queued };
384
- },
385
- async favourite(target) { return knock({ type: 'Like', object: target }); },
386
- async boost(target) { return knock({ type: 'Announce', object: target, ...audience('public') }); },
387
- async signOut() { await session.signOut(); drop('pod:facts'); },
388
- };
389
- };
390
-
391
- /** The signed-in account, or null. */
392
- async function current() {
393
- const m = read('masto:account');
394
- if (m) return mastodonAccount(m);
395
- const session = await pod.getSession();
396
- if (!session) return null;
397
- let facts = read('pod:facts');
398
- if (!facts || facts.webId !== session.webId) { facts = await podFacts(session, {}); write('pod:facts', facts); }
399
- return podAccount(session, facts);
400
- }
401
-
402
- async function signOut() { const me = await current(); if (me) await me.signOut(); }
403
-
404
- // For a test that hands in a pod session of its own.
405
- const _podAccount = async (session, pending = {}) => podAccount(session, await podFacts(session, pending));
406
-
407
- return { describe, startLogin, login, resume, current, signOut, _podAccount };
408
- }
@@ -1,123 +0,0 @@
1
- // fedi-login.mjs — sign a person in at their own Solid pod from their
2
- // Fediverse address, and bring them back to where they were.
3
- //
4
- // import { fediLogin } from './fedi-login.mjs';
5
- // const login = fediLogin({ dbName: 'my-app-oidc', clientName: 'My app' });
6
- //
7
- // await login.login('@mei@fedipod.net'); // leaves for the pod's login page
8
- // await login.resume(); // on every page load: finishes a
9
- // // login in progress and returns them
10
- // const s = await login.getSession(); // null, or { webId, issuer, fetch, signOut }
11
- //
12
- // How an address becomes a login page. WebFinger at the address's host names
13
- // the actor and its aliases. An origin among those that answers
14
- // /.well-known/openid-configuration is the login provider, or says which is
15
- // (a pod host redirects there). Failing that, the WebID — the URL typed, or
16
- // the ones the actor names as its own — is read as JSON-LD and its
17
- // solid:oidcIssuer is the provider. A Mastodon address has neither, and the
18
- // person is told so in words.
19
- //
20
- // No imports but the session engine beside it, no Node built-ins, nothing
21
- // named after the application that uses it.
22
- import { solidOidcSession } from './oidc-session.mjs';
23
-
24
- const OIDC_ISSUER = 'http://www.w3.org/ns/solid/terms#oidcIssuer';
25
-
26
- /** `@mei@host`, `mei@host`, or a WebID or actor URL. Null when it is neither. */
27
- export function parseAddress(input) {
28
- const t = String(input || '').trim();
29
- if (/^https?:\/\//u.test(t)) { try { return { url: new URL(t).href, host: new URL(t).host }; } catch { return null; } }
30
- const m = /^@?([^@\s/]+)@([^@\s/]+)$/u.exec(t);
31
- if (!m) return null;
32
- const name = m[1]; const host = m[2].toLowerCase();
33
- return { name, host, at: `@${name}@${host}` };
34
- }
35
-
36
- export function fediLogin({ dbName, clientName, redirectUri = null, fetch: f = globalThis.fetch?.bind(globalThis) } = {}) {
37
- const oidc = solidOidcSession({ dbName, clientName });
38
- const get = (url, accept) => f(url, { headers: { accept }, redirect: 'follow' });
39
- const json = (url, accept) => get(url, accept).then((r) => (r.ok ? r.json() : null)).catch(() => null);
40
- const here = () => (globalThis.location ? location.origin + location.pathname : null);
41
-
42
- const discovery = (base) => json(`${String(base).replace(/\/+$/, '')}/.well-known/openid-configuration`, 'application/json');
43
- const isProvider = (cfg) => !!(cfg?.issuer && cfg.authorization_endpoint);
44
- const origin = (u) => { try { return new URL(u).origin; } catch { return null; } };
45
-
46
- // The issuer a WebID document names, read as the expanded JSON-LD a pod answers.
47
- const issuerOfWebId = async (webId) => {
48
- const doc = await json(webId, 'application/ld+json');
49
- const nodes = Array.isArray(doc) ? doc : (doc?.['@graph'] || (doc ? [doc] : []));
50
- const me = nodes.find((n) => n?.['@id'] === webId && n[OIDC_ISSUER]) || nodes.find((n) => n?.[OIDC_ISSUER]);
51
- const v = [].concat(me?.[OIDC_ISSUER] || [])[0];
52
- return typeof v === 'string' ? v : (v?.['@id'] || null);
53
- };
54
-
55
- /** Where an address signs in: { address, actor, origin, issuer, webId? }.
56
- * Throws, in plain words, when the host does not know the address or
57
- * nothing about it names a login provider. */
58
- async function resolve(address) {
59
- const a = parseAddress(address);
60
- if (!a) throw new Error('an address looks like @you@your.server');
61
- let actor = null;
62
- const urls = [];
63
- if (a.url) urls.push(a.url);
64
- else {
65
- const r = await get(`https://${a.host}/.well-known/webfinger?resource=${encodeURIComponent(`acct:${a.name}@${a.host}`)}`,
66
- 'application/jrd+json, application/json').catch(() => null);
67
- if (!r?.ok) throw new Error(`${a.host} does not know ${a.at}`);
68
- const jrd = await r.json().catch(() => null);
69
- const self = (jrd?.links || []).find((l) => l.rel === 'self' && /activity\+json|ld\+json/u.test(l.type || ''));
70
- actor = self?.href || null;
71
- // The aliases first: at a Gateway they name the actor on the pod itself,
72
- // and the pod is where the login is.
73
- for (const u of [...(jrd?.aliases || []), ...(actor ? [actor] : [])]) if (typeof u === 'string') urls.push(u);
74
- if (!urls.length) throw new Error(`${a.host} did not say where ${a.at} lives`);
75
- }
76
- const found = (extra) => ({ address: a.at || a.url, actor, ...extra });
77
-
78
- // 1. An origin that is the login provider, or says which is.
79
- for (const o of [...new Set(urls.map(origin).filter(Boolean))]) {
80
- const cfg = await discovery(o);
81
- if (isProvider(cfg)) return found({ origin: o, issuer: cfg.issuer });
82
- }
83
- // 2. The WebID: the URL typed, or the ones the actor names as its own.
84
- const webIds = a.url ? [a.url] : [];
85
- if (actor) {
86
- const doc = await json(actor, 'application/activity+json');
87
- for (const u of [].concat(doc?.alsoKnownAs || [])) if (typeof u === 'string') webIds.push(u);
88
- }
89
- for (const webId of webIds) {
90
- const issuer = await issuerOfWebId(webId);
91
- if (issuer && isProvider(await discovery(issuer))) return found({ origin: origin(webId), issuer, webId });
92
- }
93
- throw new Error(`${a.host} is not a Solid pod, so there is no pod login to send you to`);
94
- }
95
-
96
- /** Everything `login` does short of leaving the page: returns the found pod
97
- * and the `authorizationUrl` to send the person to. */
98
- async function startLogin(address, { returnTo = globalThis.location?.href || null, redirectUri: ru = redirectUri || here() } = {}) {
99
- const found = await resolve(address);
100
- const { authorizationUrl } = await oidc.beginLogin({ issuer: found.issuer, redirectUri: ru, returnTo });
101
- return { ...found, authorizationUrl, returnTo };
102
- }
103
-
104
- /** Send the person to their pod's login page. They come back to `redirectUri`
105
- * (this page, unless set) and `resume` there returns them to `returnTo`
106
- * (where they were, unless set). */
107
- async function login(address, opts) {
108
- const s = await startLogin(address, opts);
109
- location.href = s.authorizationUrl;
110
- return s;
111
- }
112
-
113
- /** Call on every page load. Finishes a login the person is coming back from
114
- * and returns them to where they were; null when no login is in progress. */
115
- async function resume({ currentUrl = globalThis.location?.href, go = (u) => location.replace(u) } = {}) {
116
- const session = await oidc.completeLogin({ currentUrl });
117
- if (!session) return null;
118
- if (session.returnTo && session.returnTo !== currentUrl) go(session.returnTo);
119
- return session;
120
- }
121
-
122
- return { resolve, startLogin, login, resume, getSession: oidc.getSession, signOut: oidc.signOut };
123
- }