fedipod-server 0.29.0 → 0.31.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 (444) hide show
  1. package/README.md +16 -6
  2. package/dist/claims.d.ts +2 -0
  3. package/dist/claims.js +13 -4
  4. package/dist/door.d.ts +4 -0
  5. package/dist/door.js +31 -0
  6. package/dist/fedipod.d.ts +15 -0
  7. package/dist/fedipod.js +13 -0
  8. package/dist/front.d.ts +3 -0
  9. package/dist/front.js +92 -0
  10. package/dist/handler.d.ts +36 -101
  11. package/dist/handler.js +93 -644
  12. package/dist/handler.jsonld +2 -30
  13. package/dist/identities.d.ts +71 -0
  14. package/dist/identities.js +236 -0
  15. package/dist/mounts.d.ts +43 -0
  16. package/dist/mounts.js +123 -0
  17. package/dist/opt-in.d.ts +46 -0
  18. package/dist/opt-in.js +176 -0
  19. package/dist/private-read.d.ts +4 -0
  20. package/dist/private-read.js +34 -0
  21. package/package.json +12 -20
  22. package/lib/client/c2s.mjs +0 -636
  23. package/lib/client/localapi.mjs +0 -87
  24. package/lib/client/masto/accounts.mjs +0 -310
  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 -387
  65. package/lib/core/graphview.mjs +0 -327
  66. package/lib/core/intake/activities.mjs +0 -649
  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 -258
  70. package/lib/core/intake/index.mjs +0 -732
  71. package/lib/core/intake/notes.mjs +0 -367
  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/place.mjs +0 -82
  76. package/lib/core/polls.mjs +0 -105
  77. package/lib/core/profile-page.mjs +0 -104
  78. package/lib/core/proof.mjs +0 -79
  79. package/lib/core/publisher/collections.mjs +0 -272
  80. package/lib/core/publisher/index.mjs +0 -522
  81. package/lib/core/publisher/notes.mjs +0 -519
  82. package/lib/core/publisher/own.mjs +0 -143
  83. package/lib/core/publisher/questions.mjs +0 -241
  84. package/lib/core/publisher/restore.mjs +0 -224
  85. package/lib/core/shapes/activitystreams.ttl +0 -157
  86. package/lib/core/shapes/index.mjs +0 -132
  87. package/lib/core/shapes/shapes-text.mjs +0 -13
  88. package/lib/core/social.mjs +0 -561
  89. package/lib/core/storage.mjs +0 -176
  90. package/lib/core/store.mjs +0 -578
  91. package/lib/core/wire-quotes.mjs +0 -71
  92. package/lib/core/wire.mjs +0 -925
  93. package/lib/device/account.mjs +0 -87
  94. package/lib/device/admin/index.mjs +0 -13
  95. package/lib/device/admin/origins.mjs +0 -35
  96. package/lib/device/admin/routes/connections.mjs +0 -144
  97. package/lib/device/admin/routes/gateway.mjs +0 -199
  98. package/lib/device/admin/routes/lifecycle.mjs +0 -200
  99. package/lib/device/admin/routes/owner.mjs +0 -322
  100. package/lib/device/admin/routes/setup.mjs +0 -428
  101. package/lib/device/admin/routes/social.mjs +0 -188
  102. package/lib/device/admin/server.mjs +0 -95
  103. package/lib/device/admin/static.mjs +0 -247
  104. package/lib/device/admin/surface.mjs +0 -315
  105. package/lib/device/certs.mjs +0 -281
  106. package/lib/device/cli/commands/account.mjs +0 -586
  107. package/lib/device/cli/commands/run.mjs +0 -278
  108. package/lib/device/cli/commands/service.mjs +0 -221
  109. package/lib/device/cli/commands/setup.mjs +0 -489
  110. package/lib/device/cli/commands/state.mjs +0 -561
  111. package/lib/device/cli/context.mjs +0 -288
  112. package/lib/device/export-collections.mjs +0 -161
  113. package/lib/device/gateway-move.mjs +0 -58
  114. package/lib/device/home.mjs +0 -161
  115. package/lib/device/migrate.mjs +0 -115
  116. package/lib/device/ports.mjs +0 -27
  117. package/lib/device/remote.mjs +0 -54
  118. package/lib/device/setup.mjs +0 -496
  119. package/lib/device/update.mjs +0 -96
  120. package/lib/gateway/caches.mjs +0 -36
  121. package/lib/gateway/directory.mjs +0 -90
  122. package/lib/gateway/front-core.mjs +0 -966
  123. package/lib/gateway/gateway-core.mjs +0 -244
  124. package/lib/gateway/headers.mjs +0 -49
  125. package/lib/gateway/httpsig.mjs +0 -159
  126. package/lib/gateway/notices.mjs +0 -84
  127. package/lib/gateway/quiet.mjs +0 -189
  128. package/lib/gateway/relay-extras.mjs +0 -89
  129. package/lib/gateway/token-claims.mjs +0 -16
  130. package/lib/pod/README.md +0 -61
  131. package/lib/pod/actor.mjs +0 -96
  132. package/lib/pod/collection.mjs +0 -77
  133. package/lib/pod/containers.mjs +0 -140
  134. package/lib/pod/discovery.mjs +0 -46
  135. package/lib/pod/featured.mjs +0 -18
  136. package/lib/pod/followers.mjs +0 -24
  137. package/lib/pod/following.mjs +0 -10
  138. package/lib/pod/http.mjs +0 -74
  139. package/lib/pod/inbox.mjs +0 -166
  140. package/lib/pod/links.mjs +0 -35
  141. package/lib/pod/location.mjs +0 -52
  142. package/lib/pod/media.mjs +0 -27
  143. package/lib/pod/notes.mjs +0 -78
  144. package/lib/pod/notifications.mjs +0 -89
  145. package/lib/pod/outbox.mjs +0 -18
  146. package/lib/pod/package.json +0 -19
  147. package/lib/pod/policy.mjs +0 -58
  148. package/lib/pod/private.mjs +0 -19
  149. package/lib/pod/root.mjs +0 -104
  150. package/lib/pod/state.mjs +0 -40
  151. package/lib/pod/transport.mjs +0 -674
  152. package/lib/pod/type-index.mjs +0 -101
  153. package/lib/pod/urls.mjs +0 -91
  154. package/lib/server/embed.mjs +0 -424
  155. package/lib/session/README.md +0 -137
  156. package/lib/session/demo.html +0 -94
  157. package/lib/session/fedi-account.mjs +0 -417
  158. package/lib/session/fedi-login.mjs +0 -123
  159. package/lib/session/oidc-session.mjs +0 -233
  160. package/lib/session/package.json +0 -49
  161. package/lib/shared/guard.mjs +0 -213
  162. package/lib/shared/links.mjs +0 -9
  163. package/lib/shared/safefetch.mjs +0 -235
  164. package/lib/shared/ua.mjs +0 -29
  165. package/phanpy/dist/404.html +0 -40
  166. package/phanpy/dist/_headers +0 -2
  167. package/phanpy/dist/apple-touch-icon.png +0 -0
  168. package/phanpy/dist/assets/ICONS-DIPJeU0C.js +0 -2
  169. package/phanpy/dist/assets/ICONS-DIPJeU0C.js.map +0 -1
  170. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js +0 -183
  171. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js.map +0 -1
  172. package/phanpy/dist/assets/Temml-CCjoacWt.woff2 +0 -0
  173. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js +0 -2
  174. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js.map +0 -1
  175. package/phanpy/dist/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
  176. package/phanpy/dist/assets/catch-up-O5q5C75Z.png +0 -0
  177. package/phanpy/dist/assets/chunk-aKtaBQYM.js +0 -1
  178. package/phanpy/dist/assets/compose-CFEx7wb4.js +0 -2
  179. package/phanpy/dist/assets/compose-CFEx7wb4.js.map +0 -1
  180. package/phanpy/dist/assets/compose-ZJN5xSb4.js +0 -35
  181. package/phanpy/dist/assets/compose-ZJN5xSb4.js.map +0 -1
  182. package/phanpy/dist/assets/debug.module-YFQgwnIr.js +0 -52
  183. package/phanpy/dist/assets/debug.module-YFQgwnIr.js.map +0 -1
  184. package/phanpy/dist/assets/dom-p1YcJ5Rw.js +0 -2
  185. package/phanpy/dist/assets/dom-p1YcJ5Rw.js.map +0 -1
  186. package/phanpy/dist/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
  187. package/phanpy/dist/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
  188. package/phanpy/dist/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
  189. package/phanpy/dist/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
  190. package/phanpy/dist/assets/icons/add-circle-line-WD0mwORD.js +0 -1
  191. package/phanpy/dist/assets/icons/alert-line-CpFUMf9s.js +0 -1
  192. package/phanpy/dist/assets/icons/android-2-line-Bzxnrd3S.js +0 -1
  193. package/phanpy/dist/assets/icons/announcement-line-5nwaJFJm.js +0 -1
  194. package/phanpy/dist/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +0 -1
  195. package/phanpy/dist/assets/icons/arrow-down-line-B8uzXzq6.js +0 -1
  196. package/phanpy/dist/assets/icons/arrow-left-line-CGrGT9C-.js +0 -1
  197. package/phanpy/dist/assets/icons/arrow-right-line-CE-HERfX.js +0 -1
  198. package/phanpy/dist/assets/icons/arrow-to-up-line-CvdQjmcy.js +0 -1
  199. package/phanpy/dist/assets/icons/arrow-up-circle-line-DEparSNF.js +0 -1
  200. package/phanpy/dist/assets/icons/arrow-up-line-C7yBeugc.js +0 -1
  201. package/phanpy/dist/assets/icons/arrows-right-line-BtbXmJGP.js +0 -1
  202. package/phanpy/dist/assets/icons/at-line-CF66Bznk.js +0 -1
  203. package/phanpy/dist/assets/icons/attachment-line-BY8It-l9.js +0 -1
  204. package/phanpy/dist/assets/icons/blockquote-line-BS6pU_hm.js +0 -1
  205. package/phanpy/dist/assets/icons/board-line-C0UWXyA6.js +0 -1
  206. package/phanpy/dist/assets/icons/bookmark-line-1CtX5JLF.js +0 -1
  207. package/phanpy/dist/assets/icons/building-5-line-Dd59MzbO.js +0 -1
  208. package/phanpy/dist/assets/icons/bus-2-line-6_dvA0GG.js +0 -1
  209. package/phanpy/dist/assets/icons/calendar-day-line-Crpl_fmu.js +0 -1
  210. package/phanpy/dist/assets/icons/calendar-month-line-vommts2C.js +0 -1
  211. package/phanpy/dist/assets/icons/calendar-time-add-line-BU_DTlQk.js +0 -1
  212. package/phanpy/dist/assets/icons/camera-line-Cv69MEei.js +0 -1
  213. package/phanpy/dist/assets/icons/celebrate-line-B8DvIoi0.js +0 -1
  214. package/phanpy/dist/assets/icons/chart-bar-line-FK-QOmtF.js +0 -1
  215. package/phanpy/dist/assets/icons/chart-line-line-BGcJAFWw.js +0 -1
  216. package/phanpy/dist/assets/icons/chat-3-line-YFK2-hwz.js +0 -1
  217. package/phanpy/dist/assets/icons/check-circle-line-DsAxFr5-.js +0 -1
  218. package/phanpy/dist/assets/icons/clipboard-line-BBp364gJ.js +0 -1
  219. package/phanpy/dist/assets/icons/close-circle-line-CICdiuGz.js +0 -1
  220. package/phanpy/dist/assets/icons/close-line-DYrFWcjv.js +0 -1
  221. package/phanpy/dist/assets/icons/cloud-line-tiQAbah8.js +0 -1
  222. package/phanpy/dist/assets/icons/code-line-fAl8iiV3.js +0 -1
  223. package/phanpy/dist/assets/icons/comment-2-line-CR3g7WeB.js +0 -1
  224. package/phanpy/dist/assets/icons/copy-2-line-CLW69yF5.js +0 -1
  225. package/phanpy/dist/assets/icons/delete-2-line-B8xmbr4n.js +0 -1
  226. package/phanpy/dist/assets/icons/document-3-line-DBGNhAg2.js +0 -1
  227. package/phanpy/dist/assets/icons/document-line-C7mb9sb7.js +0 -1
  228. package/phanpy/dist/assets/icons/down-line-BGqSSThd.js +0 -1
  229. package/phanpy/dist/assets/icons/edit-4-line-DcgGaEhx.js +0 -1
  230. package/phanpy/dist/assets/icons/emoji-2-line-D8YgnM-y.js +0 -1
  231. package/phanpy/dist/assets/icons/exit-line-Dc07BS-x.js +0 -1
  232. package/phanpy/dist/assets/icons/external-link-line-BvpSkmjO.js +0 -1
  233. package/phanpy/dist/assets/icons/eye-2-line-CiWXx-f_.js +0 -1
  234. package/phanpy/dist/assets/icons/eye-close-line-BgTaSJ4P.js +0 -1
  235. package/phanpy/dist/assets/icons/filter-2-line-B_mR2usy.js +0 -1
  236. package/phanpy/dist/assets/icons/filter-line-C_nm-3cU.js +0 -1
  237. package/phanpy/dist/assets/icons/flag-1-line-C2HB05I0.js +0 -1
  238. package/phanpy/dist/assets/icons/forbid-circle-line-D3LTT9yd.js +0 -1
  239. package/phanpy/dist/assets/icons/formula-line-DLrt2d6j.js +0 -1
  240. package/phanpy/dist/assets/icons/grid-line-CgUmEikf.js +0 -1
  241. package/phanpy/dist/assets/icons/group-line-C5fnX0wU.js +0 -1
  242. package/phanpy/dist/assets/icons/hand-finger-2-line-BGgxbuL9.js +0 -1
  243. package/phanpy/dist/assets/icons/hashtag-line-CyTEEXBb.js +0 -1
  244. package/phanpy/dist/assets/icons/heart-crack-line-BZddbO2C.js +0 -1
  245. package/phanpy/dist/assets/icons/heart-line-CJ7K47J4.js +0 -1
  246. package/phanpy/dist/assets/icons/history-2-line-kWWvFzqv.js +0 -1
  247. package/phanpy/dist/assets/icons/history-line-bGQiwbTH.js +0 -1
  248. package/phanpy/dist/assets/icons/home-3-line-fRU1zeLG.js +0 -1
  249. package/phanpy/dist/assets/icons/information-line-iNk6-sOY.js +0 -1
  250. package/phanpy/dist/assets/icons/keyboard-line-DsCyuQNz.js +0 -1
  251. package/phanpy/dist/assets/icons/layout-4-line-CZkOX52Z.js +0 -1
  252. package/phanpy/dist/assets/icons/layout-5-line-B_znt5wO.js +0 -1
  253. package/phanpy/dist/assets/icons/left-line-eUw0tya4.js +0 -1
  254. package/phanpy/dist/assets/icons/lightning-line-DaKoDdix.js +0 -1
  255. package/phanpy/dist/assets/icons/link-2-line-DCqLmMA7.js +0 -1
  256. package/phanpy/dist/assets/icons/list-check-line-BAxosBh9.js +0 -1
  257. package/phanpy/dist/assets/icons/lock-line-CPoaXeUW.js +0 -1
  258. package/phanpy/dist/assets/icons/mail-line-B0P8aa1L.js +0 -1
  259. package/phanpy/dist/assets/icons/moon-line-CEqNAToP.js +0 -1
  260. package/phanpy/dist/assets/icons/more-1-fill-Dw2e5xab.js +0 -1
  261. package/phanpy/dist/assets/icons/more-3-line-BIiVjYTz.js +0 -1
  262. package/phanpy/dist/assets/icons/notification-line-BZZq2Gtu.js +0 -1
  263. package/phanpy/dist/assets/icons/pencil-line-CKh8-A1v.js +0 -1
  264. package/phanpy/dist/assets/icons/photo-album-line-Bnfg2u40.js +0 -1
  265. package/phanpy/dist/assets/icons/pin-line-DpTolyhs.js +0 -1
  266. package/phanpy/dist/assets/icons/play-fill-B0-G_8lX.js +0 -1
  267. package/phanpy/dist/assets/icons/qrcode-2-line-B6SPCaHn.js +0 -1
  268. package/phanpy/dist/assets/icons/quill-pen-line-B6LUrnCP.js +0 -1
  269. package/phanpy/dist/assets/icons/quote-left-fill-hQZ1cj_6.js +0 -1
  270. package/phanpy/dist/assets/icons/radar-line-CKyroTth.js +0 -1
  271. package/phanpy/dist/assets/icons/react-line-CoIKhhtK.js +0 -1
  272. package/phanpy/dist/assets/icons/refresh-2-line-D0mTF97S.js +0 -1
  273. package/phanpy/dist/assets/icons/right-line-B3KWJaqc.js +0 -1
  274. package/phanpy/dist/assets/icons/rocket-line-4KZl537D.js +0 -1
  275. package/phanpy/dist/assets/icons/round-fill-C69NTrZ0.js +0 -1
  276. package/phanpy/dist/assets/icons/round-line-B9k_1uo-.js +0 -1
  277. package/phanpy/dist/assets/icons/route-line-B28ReHcE.js +0 -1
  278. package/phanpy/dist/assets/icons/rows-4-line-CrBniagM.js +0 -1
  279. package/phanpy/dist/assets/icons/scan-line-DSf74YdP.js +0 -1
  280. package/phanpy/dist/assets/icons/search-2-line-B6geIU1z.js +0 -1
  281. package/phanpy/dist/assets/icons/settings-3-line-azJwK47w.js +0 -1
  282. package/phanpy/dist/assets/icons/settings-6-line-C_5zc5YR.js +0 -1
  283. package/phanpy/dist/assets/icons/share-2-line-D5MxWjrm.js +0 -1
  284. package/phanpy/dist/assets/icons/share-forward-line-Cp61fZzZ.js +0 -1
  285. package/phanpy/dist/assets/icons/sparkles-2-line-DYYyXTaW.js +0 -1
  286. package/phanpy/dist/assets/icons/sparkles-line-C2hY1s6I.js +0 -1
  287. package/phanpy/dist/assets/icons/time-line-BF3eet1v.js +0 -1
  288. package/phanpy/dist/assets/icons/transfer-4-line-oisfvTBw.js +0 -1
  289. package/phanpy/dist/assets/icons/translate-line-BFWol2Ce.js +0 -1
  290. package/phanpy/dist/assets/icons/unlock-line-X_x5vwv3.js +0 -1
  291. package/phanpy/dist/assets/icons/upload-3-line-DgzwUJeW.js +0 -1
  292. package/phanpy/dist/assets/icons/user-4-line-BVSynZCp.js +0 -1
  293. package/phanpy/dist/assets/icons/user-add-2-line-BlGS5Q8q.js +0 -1
  294. package/phanpy/dist/assets/icons/user-add-line-BbXdHG4z.js +0 -1
  295. package/phanpy/dist/assets/icons/user-edit-line-BFAHOV0X.js +0 -1
  296. package/phanpy/dist/assets/icons/user-follow-line-BXux0wSd.js +0 -1
  297. package/phanpy/dist/assets/icons/user-star-line-DdYlpHLU.js +0 -1
  298. package/phanpy/dist/assets/icons/user-warning-line-Cj43j0_E.js +0 -1
  299. package/phanpy/dist/assets/icons/user-x-line-DIDgNlPH.js +0 -1
  300. package/phanpy/dist/assets/icons/volume-line-Bs390KQt.js +0 -1
  301. package/phanpy/dist/assets/icons/volume-mute-line-DzlzIqFZ.js +0 -1
  302. package/phanpy/dist/assets/icons/walk-line-yNXfB-md.js +0 -1
  303. package/phanpy/dist/assets/icons/world-2-line-CenkGIwj.js +0 -1
  304. package/phanpy/dist/assets/icons/zoom-in-line-CaENUphf.js +0 -1
  305. package/phanpy/dist/assets/icons/zoom-out-line-BDNk0EWo.js +0 -1
  306. package/phanpy/dist/assets/instances-BNPptnn-.json +0 -333
  307. package/phanpy/dist/assets/locales/ar-SA-CfheRbCu.js +0 -1
  308. package/phanpy/dist/assets/locales/ca-ES-DHRW86Tz.js +0 -1
  309. package/phanpy/dist/assets/locales/cs-CZ-CKoqEuA1.js +0 -1
  310. package/phanpy/dist/assets/locales/de-DE-ByRPMRFy.js +0 -1
  311. package/phanpy/dist/assets/locales/eo-UY-CPC7wLCS.js +0 -1
  312. package/phanpy/dist/assets/locales/es-ES-dJa0Fd9t.js +0 -1
  313. package/phanpy/dist/assets/locales/eu-ES-C3djlPR2.js +0 -1
  314. package/phanpy/dist/assets/locales/fa-IR-DTT490Ck.js +0 -1
  315. package/phanpy/dist/assets/locales/fi-FI-B23MCPuz.js +0 -1
  316. package/phanpy/dist/assets/locales/fr-FR-BcaQsO5C.js +0 -1
  317. package/phanpy/dist/assets/locales/gl-ES-BR-CVKBN.js +0 -1
  318. package/phanpy/dist/assets/locales/he-IL-CfheRbCu.js +0 -1
  319. package/phanpy/dist/assets/locales/hu-HU-CfheRbCu.js +0 -1
  320. package/phanpy/dist/assets/locales/it-IT-C1136UFV.js +0 -1
  321. package/phanpy/dist/assets/locales/ja-JP-CRZmRAhD.js +0 -1
  322. package/phanpy/dist/assets/locales/kab-Cz8xH_TT.js +0 -1
  323. package/phanpy/dist/assets/locales/ko-KR-CG9UiY4o.js +0 -1
  324. package/phanpy/dist/assets/locales/lt-LT-CJcI6sHZ.js +0 -1
  325. package/phanpy/dist/assets/locales/nb-NO-CfheRbCu.js +0 -1
  326. package/phanpy/dist/assets/locales/nl-NL-DxN4Yc1t.js +0 -1
  327. package/phanpy/dist/assets/locales/oc-FR-CfheRbCu.js +0 -1
  328. package/phanpy/dist/assets/locales/pl-PL-DkwGXf6P.js +0 -1
  329. package/phanpy/dist/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +0 -1
  330. package/phanpy/dist/assets/locales/pt-BR-nGL0HdFS.js +0 -1
  331. package/phanpy/dist/assets/locales/pt-PT-Cp17aj2z.js +0 -1
  332. package/phanpy/dist/assets/locales/ru-RU-0Y8cIoaK.js +0 -1
  333. package/phanpy/dist/assets/locales/th-TH-CfheRbCu.js +0 -1
  334. package/phanpy/dist/assets/locales/tok-CfheRbCu.js +0 -1
  335. package/phanpy/dist/assets/locales/tr-TR-CfheRbCu.js +0 -1
  336. package/phanpy/dist/assets/locales/uk-UA-C5E3dFBf.js +0 -1
  337. package/phanpy/dist/assets/locales/zh-CN-BHJi7_et.js +0 -1
  338. package/phanpy/dist/assets/locales/zh-TW-CfheRbCu.js +0 -1
  339. package/phanpy/dist/assets/main-BdqNbG-a.js +0 -26
  340. package/phanpy/dist/assets/main-BdqNbG-a.js.map +0 -1
  341. package/phanpy/dist/assets/mock-home-DGhR__He.js +0 -2
  342. package/phanpy/dist/assets/mock-home-DGhR__He.js.map +0 -1
  343. package/phanpy/dist/assets/multi-column-ROck0NVt.jpg +0 -0
  344. package/phanpy/dist/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
  345. package/phanpy/dist/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
  346. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js +0 -2
  347. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js.map +0 -1
  348. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js +0 -20
  349. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js.map +0 -1
  350. package/phanpy/dist/assets/style-B1iLPTPR.css +0 -2
  351. package/phanpy/dist/assets/temml-DuyIQMei.js +0 -97
  352. package/phanpy/dist/assets/temml-DuyIQMei.js.map +0 -1
  353. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js +0 -2
  354. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js.map +0 -1
  355. package/phanpy/dist/assets/year-in-posts-B2lywqdx.png +0 -0
  356. package/phanpy/dist/assets/year-in-posts-DJv_star.js +0 -3
  357. package/phanpy/dist/assets/year-in-posts-DJv_star.js.map +0 -1
  358. package/phanpy/dist/compose/index.html +0 -20
  359. package/phanpy/dist/favicon.ico +0 -0
  360. package/phanpy/dist/index.html +0 -73
  361. package/phanpy/dist/logo-192.png +0 -0
  362. package/phanpy/dist/logo-512.png +0 -0
  363. package/phanpy/dist/logo-badge-72.png +0 -0
  364. package/phanpy/dist/logo-maskable-512.png +0 -0
  365. package/phanpy/dist/logo-monochrome-512.png +0 -0
  366. package/phanpy/dist/logo-monochrome-maskable-512.png +0 -0
  367. package/phanpy/dist/manifest.webmanifest +0 -1
  368. package/phanpy/dist/og-image-2.jpg +0 -0
  369. package/phanpy/dist/og-image.png +0 -0
  370. package/phanpy/dist/robots.txt +0 -2
  371. package/phanpy/dist/sw.js +0 -2
  372. package/phanpy/dist/sw.js.map +0 -1
  373. package/phanpy/dist/version.json +0 -1
  374. package/run-agent.mjs +0 -899
  375. package/vendor/gate.cjs +0 -138
  376. package/vendor/idp-grant.cjs +0 -394
  377. package/vendor/jfetch.cjs +0 -16
  378. package/web/admin/actors.js +0 -147
  379. package/web/admin/bar.css +0 -126
  380. package/web/admin/bar.js +0 -99
  381. package/web/admin/client/client.js +0 -66
  382. package/web/admin/client/index.html +0 -69
  383. package/web/admin/client-pick.js +0 -79
  384. package/web/admin/common.js +0 -23
  385. package/web/admin/connections.js +0 -112
  386. package/web/admin/gateway.js +0 -133
  387. package/web/admin/group.js +0 -258
  388. package/web/admin/index.html +0 -612
  389. package/web/admin/notices-bar.js +0 -90
  390. package/web/admin/oauth-signin.mjs +0 -37
  391. package/web/admin/record.js +0 -393
  392. package/web/admin/setup/index.html +0 -257
  393. package/web/admin/setup/setup.js +0 -339
  394. package/web/admin/tokens.css +0 -68
  395. package/web/admin/upkeep.js +0 -177
  396. package/web/admin/window.css +0 -55
  397. package/web/admin/window.js +0 -136
  398. package/web/app/README.md +0 -81
  399. package/web/app/admin-facade.mjs +0 -588
  400. package/web/app/agent.mjs +0 -672
  401. package/web/app/atproto-browser.mjs +0 -86
  402. package/web/app/authorize_interaction.html +0 -56
  403. package/web/app/authorize_interaction.js +0 -69
  404. package/web/app/boot.mjs +0 -616
  405. package/web/app/deliver-relay.mjs +0 -129
  406. package/web/app/dist/boot.js +0 -34562
  407. package/web/app/dist/boot.js.map +0 -7
  408. package/web/app/dist/sw.js +0 -73456
  409. package/web/app/dist/sw.js.map +0 -7
  410. package/web/app/fediacct-browser.mjs +0 -146
  411. package/web/app/gateway-move.mjs +0 -60
  412. package/web/app/idb-kv.mjs +0 -35
  413. package/web/app/index.html +0 -258
  414. package/web/app/keys-browser.mjs +0 -71
  415. package/web/app/keystore.mjs +0 -82
  416. package/web/app/oidc-session.mjs +0 -6
  417. package/web/app/pod-auth.mjs +0 -248
  418. package/web/app/pod-remote.mjs +0 -84
  419. package/web/app/shims/_globals.mjs +0 -2
  420. package/web/app/shims/fedify-sig.mjs +0 -77
  421. package/web/app/shims/node-crypto.mjs +0 -113
  422. package/web/app/shims/node-fs.mjs +0 -17
  423. package/web/app/shims/node-path.mjs +0 -19
  424. package/web/app/shims/node-url.mjs +0 -7
  425. package/web/app/shims/prelude.js +0 -58
  426. package/web/app/shims/safefetch.mjs +0 -58
  427. package/web/app/shims/shapes-text.mjs +0 -8
  428. package/web/app/shims/web-push.mjs +0 -14
  429. package/web/app/signup.mjs +0 -357
  430. package/web/app/sw-src.mjs +0 -220
  431. package/web/app/update.js +0 -70
  432. package/web/front/admin.html +0 -55
  433. package/web/front/admin.js +0 -89
  434. package/web/front/install.sh +0 -69
  435. package/web/front/new-account.html +0 -35
  436. package/web/front/new-account.js +0 -13
  437. package/web/front/notices.html +0 -69
  438. package/web/front/notices.js +0 -107
  439. package/web/front/run.html +0 -64
  440. package/web/front/run.js +0 -221
  441. package/web/front/solid-oidc-client.js +0 -6
  442. package/web/icons/forum.svg +0 -11
  443. package/web/icons/gateway.svg +0 -14
  444. package/web/icons/local.svg +0 -11
@@ -1,137 +0,0 @@
1
- # fediverse-account
2
-
3
- The library lets an app take a Fediverse handle or a WebID and act as that
4
- account.
5
-
6
- - **`login`** — sends the person to wherever their account signs in, their
7
- Mastodon server or their pod, and brings them back to where they were.
8
- - **`resume`** — on every page load, finishes a sign-in they are coming back
9
- from. Otherwise does nothing.
10
- - **`current`** — the signed-in account, or nothing.
11
- - **`signOut`** — signs them out, at their server or pod as well as in the
12
- browser.
13
- - **`describe`** — says what an address is without signing in: a Mastodon
14
- account or a pod account, and its handle.
15
-
16
- Once signed in, the account gives you:
17
-
18
- - **`post`** — publishes a post as them, public, unlisted or followers-only.
19
- - **`reply`** — answers a post.
20
- - **`timeline`** — their home timeline, newest first.
21
- - **`outbox`** — their own posts, newest first. Pass `rdf: true` to also get
22
- the real thing as RDF, for an app that wants to work with it as linked
23
- data rather than as this library's own shape.
24
- - **`follow`** — follows someone by handle.
25
- - **`favourite`**, **`boost`** — as they say.
26
- - **`profile`** — who they are as their server or pod shows them: name,
27
- handle, picture, bio, counts, profile page, and the WebID for a pod
28
- account.
29
- - **`notice`** — a sentence to show them, or nothing. A browser-based
30
- FediPod account gets: "Because you have a browser-based account, your
31
- posts and interactions will only go out to the Fediverse when your browser
32
- is opened to your account."
33
- - **`handle`, `name`, `kind`** — who they are, and whether the account is on
34
- Mastodon or a pod, for an app that wants to know.
35
- - **`signOut`** — the same as above.
36
-
37
- The app never asks which kind it has. A Mastodon account acts at once. A
38
- pod account's actions are carried out by its own agent, at once for a
39
- DeviceAgent or Server account, and when fedipod.net is next open for a
40
- browser-based one.
41
-
42
- Three files, no dependencies. It runs in a page and in a service worker.
43
- The one exception: `outbox({ rdf: true })` needs the `jsonld` package, and
44
- only loads it when that flag is actually used.
45
-
46
- [The demo](https://jeff-zucker.github.io/FediPod/) shows the sign-in and
47
- the profile that comes back.
48
-
49
- ## Get it
50
-
51
- From a CDN, with nothing to install:
52
-
53
- ```js
54
- import { fediAccount } from 'https://cdn.jsdelivr.net/npm/fediverse-account@0.1.0/fedi-account.mjs';
55
- ```
56
-
57
- Or from npm:
58
-
59
- ```bash
60
- npm install fediverse-account
61
- ```
62
-
63
- ```js
64
- import { fediAccount } from 'fediverse-account';
65
- ```
66
-
67
- ## Use
68
-
69
- ```js
70
- import { fediAccount } from 'fediverse-account';
71
- const accounts = fediAccount({ dbName: 'my-app', clientName: 'My app' });
72
-
73
- await accounts.resume(); // on every page load
74
-
75
- signInButton.onclick = () => accounts.login(addressField.value);
76
-
77
- const me = await accounts.current(); // null when nobody is signed in
78
- if (me?.notice) show(me.notice);
79
- await me.post({ text: 'Hello from my app' });
80
- for (const p of await me.timeline({ limit: 20 })) render(p);
81
- for (const p of await me.outbox({ limit: 20 })) render(p);
82
- await me.follow('@aisha@her.server');
83
- await me.reply(p.url, 'Well said');
84
- await me.signOut();
85
- ```
86
-
87
- ## The details
88
-
89
- - **`fediAccount({ dbName, clientName })`** — sets the library up for your
90
- app, once. `dbName` keeps this app's sign-ins apart from another app's on
91
- the same origin. `clientName` is what the person sees on the consent
92
- screen.
93
- - **`login(address, { returnTo, redirectUri })`** — `@kwame@mastodon.social`
94
- goes to Mastodon; `@mei@fedipod.net`, or a WebID, goes to the pod. They
95
- come back to this page unless `redirectUri` says otherwise, and are
96
- returned to where they were unless `returnTo` says otherwise.
97
- - **`post({ text, inReplyTo, visibility })`** — `visibility` is `public`,
98
- `unlisted` or `followers`.
99
- - **`reply(post, text)`** — the post is named by its address or its id.
100
- - **`timeline({ limit })`** — each post as `{ id, url, author: { id, handle,
101
- name }, html, published, inReplyTo }`.
102
- - **`outbox({ limit, rdf })`** — the same shape as `timeline`, but only this
103
- account's own posts. With `rdf: true`, the returned array also carries
104
- `.rdf`: the real outbox parsed into an RDF/JS quad array — for a Mastodon
105
- account too, since a Mastodon server is itself an ActivityPub server with
106
- a real actor and outbox, same as a pod's. It comes back empty on a server
107
- that requires a signed request just to read that document, which some
108
- do. Reading it needs the `jsonld` package available to your app; without
109
- `rdf: true` nothing changes and nothing extra loads.
110
- - **`fetch`** — the raw authenticated fetch, for anything the above does not
111
- cover.
112
- - **`actor`** — the account's ActivityPub id.
113
-
114
- Every action answers `{ sent: true }` when the server did it, or `{ queued:
115
- true }` when the account's own agent will carry it out.
116
-
117
- ## What it cannot do
118
-
119
- - **An address whose server is neither a Mastodon-family server nor a Solid
120
- pod** is refused with a sentence naming the host.
121
- - **A browser-based FediPod account acts when its browser is open.** Posts,
122
- follows and replies wait at the account's outbox until fedipod.net is next
123
- open in some browser, and its timeline is the one that browser last built.
124
- The account's `notice` says so; show it.
125
- - **A Mastodon account has no pod.** An app that keeps things on a pod has
126
- nowhere to keep them for such a person.
127
-
128
- ## Where the sign-in goes, and what stays in the browser
129
-
130
- A Mastodon account signs in at its own server, which asks the person to
131
- approve your app once. The token it grants stays in their browser and is
132
- handed back to the server at sign-out.
133
-
134
- A pod account signs in at the pod's login provider. The key that proves the
135
- sign-in is made in the browser and cannot be read out of it, even by a
136
- script on the page. The person stays signed in across browser restarts
137
- until they sign out, and sign-out is reported to the provider.
@@ -1,94 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>Act as a Fediverse account</title>
7
- <link rel="stylesheet" href="../../web/admin/tokens.css">
8
- <style>
9
- body { font-family: var(--font, Arial, Helvetica, sans-serif); background: var(--bg); color: var(--fg); margin: 0; padding: .75rem 1rem 2rem; }
10
- main { max-width: 44rem; margin: 0 auto; }
11
- h1 { margin-top: .5rem; }
12
- form { display: flex; gap: .75rem; flex-wrap: wrap; align-items: end; margin: 1.25rem 0; }
13
- label { display: flex; flex-direction: column; gap: .35rem; flex: 1 1 18rem; }
14
- input { font: inherit; padding: .6rem .8rem; background: var(--field-bg); color: var(--field-text); border: 1px solid var(--field-border); border-radius: .4rem; }
15
- button { font: inherit; padding: .6rem 1.2rem; background: var(--btn); color: var(--btn-text); border: 1px solid var(--btn-edge); border-radius: .4rem; cursor: pointer; }
16
- button:hover { background: var(--btn-hover); }
17
- a { color: var(--link); }
18
- .kinds { padding-left: 1.4rem; margin: .75rem 0 1.25rem; } .kinds li { margin: .4rem 0; }
19
- :focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
20
- .skip { position: absolute; left: -999px; } .skip:focus { left: 1rem; top: 1rem; }
21
- #status, #profile { padding: 1rem 1.2rem; border: 1px solid var(--line); border-radius: .4rem; background: var(--surface); margin: 1rem 0; }
22
- #status { min-height: 1.5em; }
23
- #status[data-kind="error"] { border-color: var(--err); }
24
- #status[hidden] { display: none; }
25
- #profile { display: grid; grid-template-columns: auto 1fr; gap: .5rem 1.2rem; align-items: start; }
26
- #profile img { width: 5rem; height: 5rem; border-radius: .6rem; object-fit: cover; background: var(--raised); }
27
- #profile dl { margin: 0; display: grid; grid-template-columns: max-content 1fr; gap: .3rem 1rem; }
28
- #profile dt { font-weight: bold; } #profile dd { margin: 0; overflow-wrap: anywhere; }
29
- section[hidden] { display: none; }
30
- </style>
31
- </head>
32
- <body>
33
- <a href="#main" class="skip">Skip to content</a>
34
- <main id="main">
35
- <h1>Act as a Fediverse account</h1>
36
- <p>Type an address and you are sent to wherever that account signs in, then come back here signed in as it. Any of these works:</p>
37
- <ul class="kinds">
38
- <li><strong>A FediPod handle</strong> — <code>@jeff@fedipod.net</code> — signs in at your Solid pod</li>
39
- <li><strong>Other Fediverse handle</strong> — <code>@you@mastodon.social</code> — signs in at your server</li>
40
- <li><strong>A WebID</strong> — <code>https://you.solidcommunity.net/profile/card#me</code> — signs in at your Solid pod</li>
41
- </ul>
42
- <p>This demo is created with the <a href="https://github.com/jeff-zucker/FediPod/tree/main/lib/session">fediverse-account</a> library. Once signed in, the demo shows you your profile. The library itself returns an account session with methods to post, reply, like, etc.</p>
43
- <form id="form">
44
- <label>Your address
45
- <input id="address" name="address" type="text" autocomplete="username" placeholder="@you@your.server" required>
46
- </label>
47
- <button type="submit" id="go">Sign in</button>
48
- <button type="button" id="out" hidden>Sign out</button>
49
- </form>
50
- <p id="status" role="status" aria-live="polite">Not signed in.</p>
51
- <section id="acting" hidden aria-labelledby="profile-h">
52
- <h2 id="profile-h">Profile</h2>
53
- <div id="profile"></div>
54
- </section>
55
- </main>
56
- <script type="module">
57
- import { fediAccount } from './fedi-account.mjs';
58
- const accounts = fediAccount({ dbName: 'fedi-account-demo', clientName: 'Fediverse account demo' });
59
- const $ = (id) => document.getElementById(id);
60
- const say = (text, kind = '') => { $('status').hidden = false; $('status').textContent = text; $('status').dataset.kind = kind; $('status').setAttribute('role', kind === 'error' ? 'alert' : 'status'); };
61
- const esc = (s) => String(s ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
62
- const link = (u) => (u ? `<a href="${esc(u)}">${esc(u)}</a>` : '—');
63
- const showProfile = async (me) => {
64
- try {
65
- const p = await me.profile();
66
- $('profile').innerHTML = `${p.avatar ? `<img src="${esc(p.avatar)}" alt="">` : '<span></span>'}
67
- <dl>
68
- <dt>Name</dt><dd>${esc(p.name || '—')}</dd>
69
- <dt>Handle</dt><dd>${esc(p.handle || '—')}</dd>
70
- <dt>Kind</dt><dd>${me.kind === 'pod' ? 'a FediPod account on a Solid pod' : 'a Mastodon account'}</dd>
71
- <dt>Bio</dt><dd>${p.bio || '—'}</dd>
72
- <dt>Counts</dt><dd>${p.posts ?? '?'} posts, ${p.followers ?? '?'} followers, ${p.following ?? '?'} following</dd>
73
- <dt>Profile page</dt><dd>${link(p.url)}</dd>
74
- <dt>Actor</dt><dd>${link(p.actor)}</dd>
75
- ${p.webId ? `<dt>WebID</dt><dd>${link(p.webId)}</dd>` : ''}
76
- </dl>`;
77
- } catch (e) { $('profile').textContent = `The profile could not be read: ${e.message}`; }
78
- };
79
- const show = async () => {
80
- const me = await accounts.current();
81
- $('out').hidden = !me; $('acting').hidden = !me; $('status').hidden = !!me;
82
- if (!me) { say('Not signed in.'); $('profile').innerHTML = ''; return; }
83
- await showProfile(me);
84
- };
85
- try { await accounts.resume(); } catch (e) { say(e.message, 'error'); }
86
- await show();
87
- $('form').addEventListener('submit', async (e) => {
88
- e.preventDefault(); say('Finding that account…');
89
- try { await accounts.login($('address').value); } catch (err) { say(err.message, 'error'); }
90
- });
91
- $('out').addEventListener('click', async () => { await accounts.signOut(); await show(); });
92
- </script>
93
- </body>
94
- </html>
@@ -1,417 +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
- // A collection, or a page of one, may be named by its address or given
64
- // whole in place (ActivityPub allows either); the whole one is used as it
65
- // is, an address is fetched.
66
- const idOf = (v) => (typeof v === 'string' ? v : v?.id || null);
67
- const asDoc = async (v) => (v && typeof v === 'object'
68
- ? (v.type ? v : await asDoc(v.id))
69
- : (typeof v === 'string' ? await json(v, { headers: { accept: 'application/activity+json' } }) : null));
70
-
71
- // Only loaded when `outbox({ rdf: true })` is actually called, so nobody
72
- // pays for a JSON-LD parser just by importing this file. Not in
73
- // `dependencies` — the app supplies "jsonld" if it wants this flag to work.
74
- const loadJsonLd = async () => {
75
- try { return (await import('jsonld')).default; }
76
- catch { throw new Error('outbox({ rdf: true }) needs the "jsonld" package available to the app — it is not bundled with this library'); }
77
- };
78
-
79
- // The account's real outbox, read from the pod: the head collection names
80
- // `first`, each page names `orderedItems` and (while there is more) `next`.
81
- // Walked only far enough to cover `limit`, and run through jsonld.toRDF so
82
- // the result is a genuine RDF/JS quad array, not this library's own shape.
83
- const fetchOutboxRdf = async (actorUrl, limit) => {
84
- if (!actorUrl) return null;
85
- const jsonld = await loadJsonLd();
86
- const actorDoc = await json(actorUrl, { headers: { accept: 'application/activity+json' } });
87
- const head = await asDoc(actorDoc?.outbox);
88
- let page = head?.first ?? null;
89
- const quads = []; const seen = new Set(); let collected = 0;
90
- while (page && collected < limit && !seen.has(idOf(page) || page)) {
91
- seen.add(idOf(page) || page);
92
- const doc = await asDoc(page);
93
- if (!doc) break;
94
- quads.push(...(await jsonld.toRDF(doc)));
95
- collected += (doc.orderedItems || []).length;
96
- page = doc.next ?? null;
97
- }
98
- return quads;
99
- };
100
-
101
- // ---- finding out what an address is ----
102
-
103
- // Does this host speak the Mastodon API? Any server of that family says so
104
- // here; a pod, a Gateway and a plain web host do not.
105
- const speaksMastodon = async (host) => {
106
- const inst = await json(`https://${host}/api/v1/instance`);
107
- return !!(inst && (inst.uri || inst.domain || inst.title));
108
- };
109
-
110
- const webfinger = async (name, host) => {
111
- const jrd = await json(`https://${host}/.well-known/webfinger?resource=${encodeURIComponent(`acct:${name}@${host}`)}`,
112
- { headers: { accept: 'application/jrd+json, application/json' } });
113
- if (!jrd) throw new Error(`${host} does not know @${name}@${host}`);
114
- const self = (jrd.links || []).find((l) => l.rel === 'self' && /activity\+json|ld\+json/u.test(l.type || ''));
115
- return { actor: self?.href || null, aliases: (jrd.aliases || []).filter((u) => typeof u === 'string') };
116
- };
117
-
118
- // Where a pod account keeps its records: the actor on the pod itself is
119
- // `<root>ap/actor`, and the aliases name it even when the address is at a
120
- // Gateway.
121
- const rootOf = (actor, aliases) => {
122
- const onPod = [...aliases, actor].find((u) => typeof u === 'string' && /\/ap\/actor$/u.test(u) && (!actor || new URL(u).origin !== new URL(actor).origin))
123
- || [...aliases, actor].find((u) => typeof u === 'string' && /\/ap\/actor$/u.test(u));
124
- return onPod ? onPod.replace(/ap\/actor$/u, '') : null;
125
- };
126
-
127
- /** What an address is, without signing in: { kind: 'mastodon' | 'pod', host, handle, actor, … }. */
128
- async function describe(address) {
129
- const a = parseAddress(address);
130
- if (!a) throw new Error('an address looks like @you@your.server, or is your WebID');
131
- if (a.url) {
132
- // A WebID: the account it names leads to the handle, and the handle to
133
- // everything else.
134
- const card = await json(a.url, { headers: { accept: 'application/ld+json' } });
135
- const nodes = Array.isArray(card) ? card : (card?.['@graph'] || (card ? [card] : []));
136
- const me = nodes.find((n) => n?.['@id'] === a.url) || {};
137
- const actorId = [].concat(me[FOAF_ACCOUNT] || []).map((v) => v?.['@id'] || v).find((v) => typeof v === 'string');
138
- if (!actorId) throw new Error(`${a.host} names no Fediverse account on that WebID`);
139
- const actor = await json(actorId, { headers: { accept: 'application/activity+json' } });
140
- if (!actor?.preferredUsername) throw new Error(`the account ${actorId} could not be read`);
141
- return describe(`@${actor.preferredUsername}@${new URL(actorId).host}`);
142
- }
143
- const { actor, aliases } = await webfinger(a.name, a.host);
144
- const actorHost = actor ? new URL(actor).host : a.host;
145
- for (const host of [...new Set([actorHost, a.host])]) {
146
- if (await speaksMastodon(host)) return { kind: 'mastodon', host, handle: a.at, actor };
147
- }
148
- const where = await pod.resolve(a.at);
149
- return { kind: 'pod', host: a.host, handle: a.at, actor: actor || where.actor, issuer: where.issuer, root: rootOf(actor || where.actor, aliases) };
150
- }
151
-
152
- // ---- signing in ----
153
-
154
- const appFor = async (host, ru) => {
155
- const apps = read('masto:apps') || {};
156
- const known = apps[host];
157
- if (known?.redirectUri === ru && known.clientId) return known;
158
- const res = await f(`https://${host}/api/v1/apps`, {
159
- method: 'POST', headers: { 'content-type': 'application/json' },
160
- body: JSON.stringify({ client_name: clientName, redirect_uris: ru, scopes, ...(website ? { website } : {}) }),
161
- });
162
- const body = await said(res, host);
163
- if (!body.client_id) throw new Error(`${host} registered no client`);
164
- const app = { host, redirectUri: ru, clientId: body.client_id, clientSecret: body.client_secret || null };
165
- write('masto:apps', { ...apps, [host]: app });
166
- return app;
167
- };
168
-
169
- /** Everything `login` does short of leaving the page: what the address is and the page to send the person to. */
170
- async function startLogin(address, { returnTo = globalThis.location?.href || null, redirectUri: ru = redirectUri || here() } = {}) {
171
- const what = await describe(address);
172
- if (what.kind === 'pod') {
173
- const s = await pod.startLogin(what.handle, { returnTo, redirectUri: ru });
174
- write('pod:pending', { handle: what.handle, actor: what.actor, root: what.root, at: Date.now() });
175
- return { ...what, authorizationUrl: s.authorizationUrl, returnTo };
176
- }
177
- const app = await appFor(what.host, ru);
178
- const bytes = crypto.getRandomValues(new Uint8Array(24));
179
- const state = [...bytes].map((b) => b.toString(16).padStart(2, '0')).join('');
180
- write('masto:pending', { state, host: what.host, redirectUri: ru, returnTo, at: Date.now() });
181
- const u = new URL(`https://${what.host}/oauth/authorize`);
182
- 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);
183
- return { ...what, authorizationUrl: u.href, returnTo };
184
- }
185
-
186
- /** Send the person to wherever this account signs in. They come back to this
187
- * page (unless `redirectUri` says otherwise) and `resume` returns them to
188
- * where they were (unless `returnTo` says otherwise). */
189
- async function login(address, opts) {
190
- const s = await startLogin(address, opts);
191
- location.href = s.authorizationUrl;
192
- return s;
193
- }
194
-
195
- /** Call on every page load. Finishes a sign-in the person is coming back
196
- * from, of either kind, and returns them to where they were. Returns the
197
- * account, or null when nobody is coming back. */
198
- async function resume({ currentUrl = globalThis.location?.href, go = (u) => location.replace(u) } = {}) {
199
- const u = new URL(currentUrl);
200
- const code = u.searchParams.get('code'); const state = u.searchParams.get('state');
201
- const pend = read('masto:pending');
202
- if (code && state && pend?.state === state) {
203
- drop('masto:pending');
204
- if (Date.now() - pend.at > PENDING_TTL_MS) throw new Error('that sign-in has expired — start again');
205
- const app = await appFor(pend.host, pend.redirectUri);
206
- const tok = await said(await f(`https://${pend.host}/oauth/token`, {
207
- method: 'POST', headers: { 'content-type': 'application/json' },
208
- body: JSON.stringify({ grant_type: 'authorization_code', code, redirect_uri: pend.redirectUri, client_id: app.clientId, ...(app.clientSecret ? { client_secret: app.clientSecret } : {}), scope: scopes }),
209
- }), pend.host);
210
- if (!tok.access_token) throw new Error(`${pend.host} returned no token`);
211
- const me = await said(await f(`https://${pend.host}/api/v1/accounts/verify_credentials`, { headers: { authorization: `Bearer ${tok.access_token}` } }), pend.host);
212
- 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 });
213
- drop('pod:facts');
214
- if (pend.returnTo && pend.returnTo !== currentUrl) go(pend.returnTo);
215
- return current();
216
- }
217
- const session = await pod.resume({ currentUrl, go: () => {} });
218
- if (!session) return null;
219
- const p = read('pod:pending') || {};
220
- drop('pod:pending');
221
- const facts = await podFacts(session, p);
222
- write('pod:facts', facts);
223
- drop('masto:account');
224
- if (session.returnTo && session.returnTo !== currentUrl) go(session.returnTo);
225
- return current();
226
- }
227
-
228
- // What a pod account needs, found once at sign-in: its door, its records,
229
- // its followers collection, and whether its agent is a browser.
230
- const podFacts = async (session, p) => {
231
- const card = await json(session.webId, { headers: { accept: 'application/ld+json' } });
232
- const nodes = Array.isArray(card) ? card : (card?.['@graph'] || (card ? [card] : []));
233
- const me = nodes.find((n) => n?.['@id'] === session.webId) || {};
234
- const pick = (node, k) => [].concat(node?.[k] || []).map((v) => v?.['@id'] || v).find((v) => typeof v === 'string') || null;
235
- let actor = p.actor || pick(me, FOAF_ACCOUNT);
236
- let door = pick(me, AS_OUTBOX);
237
- const doc = actor ? await json(actor, { headers: { accept: 'application/activity+json' } }) : null;
238
- if (!door && doc?.outbox) door = idOf(doc.outbox);
239
- let root = p.root || null;
240
- if (!root && doc?.preferredUsername && actor) {
241
- const { aliases } = await webfinger(doc.preferredUsername, new URL(actor).host).catch(() => ({ aliases: [] }));
242
- root = rootOf(actor, aliases);
243
- }
244
- const handle = p.handle || (doc?.preferredUsername && actor ? `@${doc.preferredUsername}@${new URL(actor).host}` : null);
245
- // A browser-made account keeps its signing key on the pod sealed under
246
- // the password; a device keeps its key with itself. The shape says which.
247
- let browserBased = false;
248
- if (root) {
249
- const keys = await session.fetch(`${root}ap-state/keys.json`, { headers: { accept: 'application/json' } }).then((r) => (r.ok ? r.json() : null)).catch(() => null);
250
- const envelope = keys && (keys.v === 1 && typeof keys.ct === 'string' && typeof keys.salt === 'string'
251
- || Object.values(keys).some((k) => k && k.v === 1 && typeof k.ct === 'string' && typeof k.salt === 'string'));
252
- browserBased = !!envelope;
253
- }
254
- return { handle, actor, name: doc?.name || null, door, root, followers: doc?.followers || null, webId: session.webId, browserBased };
255
- };
256
-
257
- // ---- the account ----
258
-
259
- const mastodonAccount = (a) => {
260
- const base = `https://${a.host}`;
261
- const auth = { authorization: `Bearer ${a.token}` };
262
- const call = (path, init = {}) => f(`${base}${path}`, { ...init, headers: { ...auth, ...(init.headers || {}) } });
263
- const post = (path, body) => call(path, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body || {}) }).then((r) => said(r, a.host));
264
- // A post named by its address is found on this server by a search that
265
- // fetches it if need be; one named by an id here is used as it is.
266
- const idOf = async (target) => {
267
- if (!/^https?:\/\//u.test(String(target))) return String(target);
268
- const u = new URL(`${base}/api/v2/search`);
269
- u.searchParams.set('q', target); u.searchParams.set('resolve', 'true'); u.searchParams.set('type', 'statuses'); u.searchParams.set('limit', '1');
270
- const body = await call(u.href).then((r) => said(r, a.host));
271
- const id = body.statuses?.[0]?.id;
272
- if (!id) throw new Error(`${a.host} could not find that post`);
273
- return id;
274
- };
275
- const item = (s) => ({
276
- id: s.id, url: s.url || s.uri || null, published: s.created_at,
277
- 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 },
278
- html: s.content || '', inReplyTo: s.in_reply_to_id || null,
279
- ...(s.reblog ? { boostOf: item(s.reblog) } : {}),
280
- });
281
- return {
282
- kind: 'mastodon', handle: a.handle, name: a.name, actor: a.actor, notice: null,
283
- fetch: call,
284
- async profile() {
285
- const me = await call('/api/v1/accounts/verify_credentials').then((r) => said(r, a.host));
286
- return { handle: a.handle, name: me.display_name || me.username || null, url: me.url || null, avatar: me.avatar || null, bio: me.note || '',
287
- followers: me.followers_count ?? null, following: me.following_count ?? null, posts: me.statuses_count ?? null, actor: a.actor, webId: null };
288
- },
289
- async post({ text, inReplyTo = null, visibility = 'public' }) {
290
- const made = await post('/api/v1/statuses', { status: text, visibility, ...(inReplyTo ? { in_reply_to_id: await idOf(inReplyTo) } : {}) });
291
- return { id: made.id, url: made.url || made.uri || null, sent: true };
292
- },
293
- reply(target, text) { return this.post({ text, inReplyTo: target }); },
294
- async timeline({ limit = 20 } = {}) {
295
- const list = await call(`/api/v1/timelines/home?limit=${Math.min(40, limit)}`).then((r) => said(r, a.host));
296
- return (Array.isArray(list) ? list : []).map(item);
297
- },
298
- // This account's own posts, newest first — Mastodon's equivalent of an
299
- // outbox. A Mastodon server IS an ActivityPub server, so its actor and
300
- // outbox are real AS2/JSON-LD too, same as a pod's; `rdf: true` reads
301
- // that, not the REST API above. It comes back empty on an instance that
302
- // requires a signed request just to read the public actor document
303
- // (some do — mastodon.social among them); `fetchOutboxRdf`'s plain GET
304
- // then gets 401 and the loop below finds nothing to walk.
305
- async outbox({ limit = 20, rdf = false } = {}) {
306
- const me = await call('/api/v1/accounts/verify_credentials').then((r) => said(r, a.host));
307
- const list = await call(`/api/v1/accounts/${me.id}/statuses?limit=${Math.min(40, limit)}`).then((r) => said(r, a.host));
308
- const items = (Array.isArray(list) ? list : []).map(item);
309
- if (rdf) items.rdf = await fetchOutboxRdf(a.actor, limit);
310
- return items;
311
- },
312
- async follow(handle) {
313
- const who = parseAddress(handle);
314
- if (!who?.at) throw new Error('a handle looks like @you@your.server');
315
- const acct = await call(`/api/v1/accounts/lookup?acct=${encodeURIComponent(who.at.slice(1))}`).then((r) => said(r, a.host));
316
- if (!acct.id) throw new Error(`${a.host} could not find ${who.at}`);
317
- await post(`/api/v1/accounts/${acct.id}/follow`);
318
- return { followed: who.at, sent: true };
319
- },
320
- async favourite(target) { await post(`/api/v1/statuses/${await idOf(target)}/favourite`); return { sent: true }; },
321
- async boost(target) { await post(`/api/v1/statuses/${await idOf(target)}/reblog`); return { sent: true }; },
322
- async signOut() {
323
- const apps = read('masto:apps') || {};
324
- const app = apps[a.host];
325
- if (app) {
326
- 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 */ }
327
- }
328
- drop('masto:account');
329
- },
330
- };
331
- };
332
-
333
- const podAccount = (session, facts) => {
334
- const knock = async (activity) => {
335
- if (!facts.door) throw new Error('this account names no outbox to post through');
336
- const res = await session.fetch(facts.door, { method: 'POST', headers: { 'content-type': 'application/activity+json' }, body: JSON.stringify({ '@context': AS, ...activity }) });
337
- if (!res.ok) { const body = await res.text().catch(() => ''); throw new Error(`the outbox refused it (HTTP ${res.status})${body ? `: ${body.slice(0, 200)}` : ''}`); }
338
- return { id: res.headers.get('location') || null, sent: res.status === 201, queued: res.status === 202 };
339
- };
340
- const audience = (visibility) => {
341
- if (visibility === 'unlisted') return { to: facts.followers ? [facts.followers] : [], cc: [PUBLIC] };
342
- if (visibility === 'private' || visibility === 'followers') return { to: facts.followers ? [facts.followers] : [], cc: [] };
343
- return { to: [PUBLIC], cc: facts.followers ? [facts.followers] : [] };
344
- };
345
- 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));
346
- return {
347
- kind: 'pod', handle: facts.handle, name: facts.name, actor: facts.actor, webId: facts.webId,
348
- notice: facts.browserBased ? BROWSER_ACCOUNT_NOTICE : null,
349
- fetch: session.fetch,
350
- async profile() {
351
- const doc = facts.actor ? await json(facts.actor, { headers: { accept: 'application/activity+json' } }) : null;
352
- const count = async (v) => (await asDoc(v))?.totalItems ?? null;
353
- 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 || '',
354
- followers: await count(doc?.followers), following: await count(doc?.following), posts: await count(doc?.outbox), actor: facts.actor, webId: facts.webId };
355
- },
356
- async post({ text, inReplyTo = null, visibility = 'public' }) {
357
- const made = await knock({ type: 'Note', content: toHtml(text), source: { content: text, mediaType: 'text/plain' }, ...(inReplyTo ? { inReplyTo } : {}), ...audience(visibility) });
358
- return { id: made.id, url: made.id, sent: made.sent, queued: made.queued };
359
- },
360
- reply(target, text) { return this.post({ text, inReplyTo: target }); },
361
- async timeline({ limit = 20 } = {}) {
362
- const rows = await state('statuses.json');
363
- const actors = (await state('actors.json')) || {};
364
- 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 }; };
365
- return (Array.isArray(rows) ? rows : [])
366
- .filter((s) => s.kind !== 'remote' && s.kind !== 'mention' && !s.direct && s.visibility !== 'direct')
367
- .sort((x, y) => String(y.published || '').localeCompare(String(x.published || '')))
368
- .slice(0, limit)
369
- .map((s) => ({ id: s.noteId, url: s.noteId, published: s.published || null, author: who(s.actor), html: s.content || '', inReplyTo: s.inReplyTo || null }));
370
- },
371
- // This account's own posts, newest first, read from the same local
372
- // record `timeline` uses (fast, no network call). With `rdf: true`,
373
- // also fetches the real outbox from the pod and parses it into RDF/JS
374
- // quads on the returned array's `.rdf` — a second, live document, not
375
- // derived from the local record, so it needs `jsonld` (see loadJsonLd).
376
- async outbox({ limit = 20, rdf = false } = {}) {
377
- const rows = await state('statuses.json');
378
- const actors = (await state('actors.json')) || {};
379
- 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 }; };
380
- const items = (Array.isArray(rows) ? rows : [])
381
- .filter((s) => s.kind === 'post')
382
- .sort((x, y) => String(y.published || '').localeCompare(String(x.published || '')))
383
- .slice(0, limit)
384
- .map((s) => ({ id: s.noteId, url: s.noteId, published: s.published || null, author: who(s.actor), html: s.content || '', inReplyTo: s.inReplyTo || null }));
385
- if (rdf) items.rdf = await fetchOutboxRdf(facts.actor, limit);
386
- return items;
387
- },
388
- async follow(handle) {
389
- const who = parseAddress(handle);
390
- if (!who?.at) throw new Error('a handle looks like @you@your.server');
391
- const made = await knock({ type: 'Follow', object: who.at });
392
- return { followed: who.at, sent: made.sent, queued: made.queued };
393
- },
394
- async favourite(target) { return knock({ type: 'Like', object: target }); },
395
- async boost(target) { return knock({ type: 'Announce', object: target, ...audience('public') }); },
396
- async signOut() { await session.signOut(); drop('pod:facts'); },
397
- };
398
- };
399
-
400
- /** The signed-in account, or null. */
401
- async function current() {
402
- const m = read('masto:account');
403
- if (m) return mastodonAccount(m);
404
- const session = await pod.getSession();
405
- if (!session) return null;
406
- let facts = read('pod:facts');
407
- if (!facts || facts.webId !== session.webId) { facts = await podFacts(session, {}); write('pod:facts', facts); }
408
- return podAccount(session, facts);
409
- }
410
-
411
- async function signOut() { const me = await current(); if (me) await me.signOut(); }
412
-
413
- // For a test that hands in a pod session of its own.
414
- const _podAccount = async (session, pending = {}) => podAccount(session, await podFacts(session, pending));
415
-
416
- return { describe, startLogin, login, resume, current, signOut, _podAccount };
417
- }