fedipod 1.40.0 → 1.42.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 (65) hide show
  1. package/README.md +5 -0
  2. package/architecture.md +1 -3
  3. package/architecture.svg +93 -85
  4. package/gateway.md +7 -2
  5. package/lib/client/c2s.mjs +16 -5
  6. package/lib/client/masto/oauth.mjs +1 -1
  7. package/lib/connections/vault.mjs +1 -1
  8. package/lib/core/agent.mjs +720 -0
  9. package/lib/core/gateway-move.mjs +58 -0
  10. package/lib/core/keys.mjs +1 -1
  11. package/lib/core/lease.mjs +4 -3
  12. package/lib/core/publisher/index.mjs +4 -6
  13. package/lib/core/publisher/notes.mjs +73 -4
  14. package/lib/core/remote.mjs +54 -0
  15. package/lib/core/shapes/shapes-text.mjs +11 -3
  16. package/lib/core/wire.mjs +4 -1
  17. package/lib/device/admin/index.mjs +4 -11
  18. package/lib/device/admin/routes/setup.mjs +60 -5
  19. package/lib/device/admin/server.mjs +18 -5
  20. package/lib/device/cli/commands/account.mjs +1 -1
  21. package/lib/device/cli/commands/run.mjs +2 -2
  22. package/lib/device/cli/commands/service.mjs +1 -1
  23. package/lib/device/cli/commands/setup.mjs +1 -1
  24. package/lib/device/cli/commands/state.mjs +1 -1
  25. package/lib/device/cli/context.mjs +1 -1
  26. package/lib/{gateway → device}/directory.mjs +1 -1
  27. package/lib/device/gateway-move.mjs +3 -58
  28. package/lib/device/home.mjs +3 -50
  29. package/lib/{client → device}/localapi.mjs +2 -2
  30. package/lib/device/remote.mjs +3 -54
  31. package/lib/gateway/copy.mjs +7 -1
  32. package/lib/gateway/front-core.mjs +6 -16
  33. package/lib/gateway/front-pages.mjs +31 -0
  34. package/lib/gateway/keeper-session.mjs +10 -1
  35. package/lib/gateway/masto-gateway.mjs +61 -12
  36. package/lib/gateway/outbox-door.mjs +31 -0
  37. package/lib/gateway/state-api.mjs +39 -7
  38. package/lib/server/embed.mjs +4 -4
  39. package/lib/shared/files.mjs +49 -0
  40. package/lib/shared/ua.mjs +3 -0
  41. package/lib/surface/index.mjs +7 -0
  42. package/lib/{device/admin → surface}/origins.mjs +1 -1
  43. package/lib/{device/admin → surface}/routes/gateway.mjs +7 -11
  44. package/lib/{device/admin → surface}/routes/lifecycle.mjs +3 -3
  45. package/lib/{device/admin → surface}/routes/owner.mjs +4 -60
  46. package/lib/{device/admin → surface}/routes/social.mjs +1 -1
  47. package/lib/{device/admin → surface}/static.mjs +6 -1
  48. package/lib/{device/admin → surface}/surface.mjs +19 -16
  49. package/package.json +23 -5
  50. package/run-agent.mjs +18 -714
  51. package/scripts/build-app.mjs +4 -0
  52. package/scripts/check-netlify-bundles.mjs +84 -0
  53. package/scripts/check-pod-calls.mjs +42 -0
  54. package/scripts/vendor-sanitize.mjs +27 -0
  55. package/vendor/idp-grant.cjs +11 -5
  56. package/vendor/sanitize-html.cjs +11456 -0
  57. package/web/app/agent.mjs +4 -2
  58. package/web/app/copy-mode.mjs +27 -3
  59. package/web/app/dist/boot.js.map +1 -1
  60. package/web/app/dist/sw.js +110 -24
  61. package/web/app/dist/sw.js.map +4 -4
  62. package/web/app-signin/app-signin.mjs +2 -2
  63. package/web/app-signin/index.html +13 -5
  64. package/browser.svg +0 -72
  65. /package/lib/{device/admin → surface}/routes/connections.mjs +0 -0
package/web/app/agent.mjs CHANGED
@@ -34,7 +34,7 @@ import { AcctFeed } from '../../lib/connections/acctfeed.mjs';
34
34
  import { followActor, unfollowActor, resolveHandle } from '../../lib/core/social.mjs';
35
35
  import { podBaseOfWebId } from '../../lib/pod/urls.mjs';
36
36
  import { ImportWorker } from '../../lib/connections/import.mjs';
37
- import { openCopy, copyStorage, copyLeaseOf, moveIntoCopy, leaveCopy, renewCopyToken, standDown, ensureCopyLease, handOverCopy } from './copy-mode.mjs';
37
+ import { openCopy, copyStorage, copyLeaseOf, moveIntoCopy, leaveCopy, renewCopyToken, standDown, ensureCopyLease, handOverCopy, forgetFailedOpen } from './copy-mode.mjs';
38
38
 
39
39
  // The authorities this identity answers on: exactly one, this origin. The Node
40
40
  // agent gets this from lib/guard.mjs, which is not in the browser bundle and
@@ -191,6 +191,8 @@ export class BrowserAgent {
191
191
  if (!on) return { status: 200, ok: true, kept: false };
192
192
  const said = await this.tellGateway('keeper', { handle: this.doorKey, on: true });
193
193
  if (said?.status === 200) {
194
+ // A copy that could not be had while the account was not kept may be now.
195
+ await forgetFailedOpen(this, this.frontOrigin);
194
196
  this._keeper.kept = true;
195
197
  if (this.masto) this.masto.scheduling = true;
196
198
  this.log('the gateway keeps this account running while the app is closed');
@@ -420,7 +422,7 @@ export class BrowserAgent {
420
422
  // is read, since it decides where the state is read from.
421
423
  this.holderId = await this.deviceId();
422
424
  this.frontOrigin = frontOrigin || null;
423
- this.copy = await openCopy(this, this.frontOrigin, { kept: isWarm(warmFrom) ? warmFrom.copy : null });
425
+ this.copy = await openCopy(this, this.frontOrigin, { kept: isWarm(warmFrom) ? warmFrom.copy : null, askAnyway: !restart });
424
426
  const warmRoot = isWarm(warmFrom) && !!warmFrom.copy === !!this.copy ? warmFrom.root : null;
425
427
  const copyRoot = this.copy?.podHome?.startsWith(remotePod) ? this.copy.podHome.slice(remotePod.length) : null;
426
428
  this._sweptAt = { ...(warmFrom?.sweptAt || {}) };
@@ -8,19 +8,38 @@
8
8
  // gateway writes the copy to the pod every fifteen minutes.
9
9
  import { HttpStorage, StateApiStorage, podOnly } from '../../lib/core/storage.mjs';
10
10
  import { Lease } from '../../lib/core/lease.mjs';
11
+ import { kvGet, kvPut, kvDel } from './idb-kv.mjs';
11
12
 
12
13
  // A token this much short of its end is renewed before it is used again.
13
14
  const RENEW_BEFORE_MS = 2 * 3600_000;
14
15
 
16
+ // After the gateway could not find or make the copy, this browser does not
17
+ // ask again for this long: its worker is restarted whenever a client checks
18
+ // for posts, about once a minute, and every ask cost the gateway a try at the
19
+ // pod. The account works from its pod meanwhile, as after any failed ask. A
20
+ // sign-in, as against a restart, asks anyway, and turning keeping on forgets
21
+ // the failure (agent.mjs setKeeper). A network failure or a refused sign-in is
22
+ // not remembered: those are this browser's to put right. Nor is "held by
23
+ // another device" after this browser lets go of the pod (moveIntoCopy): what
24
+ // the gateway found held may have been this browser's own lease.
25
+ const ASK_AGAIN_MS = 15 * 60_000;
26
+ const REMEMBERED = [404, 409, 501, 502, 503];
27
+ const failedKey = (agent, frontOrigin) => `copy-open-failed:${frontOrigin}:${agent.webId}`;
28
+ export const forgetFailedOpen = (agent, frontOrigin) => kvDel(failedKey(agent, frontOrigin)).catch(() => {});
29
+
15
30
  /**
16
31
  * Ask the gateway for this account's copy, making it if there is none yet.
17
32
  * `handle` names the account when the agent knows it. Returns the copy
18
33
  * ({ handle, base, token, expiresAt }) or null, in which case the account
19
34
  * works from its pod.
20
35
  */
21
- export async function openCopy(agent, frontOrigin, { handle = null, kept = null } = {}) {
36
+ export async function openCopy(agent, frontOrigin, { handle = null, kept = null, askAnyway = false, letGo = false } = {}) {
22
37
  if (kept && kept.expiresAt - Date.now() > RENEW_BEFORE_MS) return kept;
23
38
  if (!agent.sessionFetch || !frontOrigin) return null;
39
+ const key = failedKey(agent, frontOrigin);
40
+ const failed = await kvGet(key).catch(() => null);
41
+ const held = letGo && failed?.status === 409;
42
+ if (!askAnyway && !held && failed && Date.now() - failed.at < ASK_AGAIN_MS) return null;
24
43
  let res;
25
44
  try {
26
45
  res = await agent.sessionFetch(`${frontOrigin.replace(/\/$/u, '')}/api/state/open`, {
@@ -30,9 +49,14 @@ export async function openCopy(agent, frontOrigin, { handle = null, kept = null
30
49
  if (res.status !== 200) {
31
50
  // 404, 409 and 501 are answers (no copy for this account here); anything
32
51
  // else is worth saying.
33
- if (![404, 409, 501].includes(res.status)) agent.log(`the account's copy: the gateway answered ${res.status}`);
52
+ if (![404, 409, 501].includes(res.status)) {
53
+ const why = (await res.json().catch(() => null))?.error;
54
+ agent.log(`the account's copy: the gateway answered ${res.status}${why ? ` (${why})` : ''}`);
55
+ }
56
+ if (REMEMBERED.includes(res.status)) await kvPut(key, { at: Date.now(), status: res.status }).catch(() => {});
34
57
  return null;
35
58
  }
59
+ if (failed) await kvDel(key).catch(() => {});
36
60
  const copy = await res.json().catch(() => null);
37
61
  return copy?.base && copy?.token
38
62
  ? { handle: copy.handle, base: copy.base, token: copy.token, expiresAt: copy.expiresAt, podHome: copy.podHome || null }
@@ -140,7 +164,7 @@ export async function moveIntoCopy(agent, frontOrigin) {
140
164
  if (agent.copy) return true;
141
165
  await agent.store.commit();
142
166
  await agent.lease.release();
143
- const copy = await openCopy(agent, frontOrigin, { handle: agent.doorKey });
167
+ const copy = await openCopy(agent, frontOrigin, { handle: agent.doorKey, letGo: true });
144
168
  if (!copy) {
145
169
  if (await agent.lease.acquire()) agent.lease.startRenewal();
146
170
  return false;