@weaveprotocol/core 0.2.0 → 0.2.2

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 (71) hide show
  1. package/README.md +137 -44
  2. package/dist/doors/doors.d.ts +139 -0
  3. package/dist/doors/doors.d.ts.map +1 -0
  4. package/dist/doors/doors.js +210 -0
  5. package/dist/doors/doors.js.map +1 -0
  6. package/dist/doors/index.d.ts +9 -0
  7. package/dist/doors/index.d.ts.map +1 -0
  8. package/dist/doors/index.js +7 -0
  9. package/dist/doors/index.js.map +1 -0
  10. package/dist/elements/auth-styles.d.ts +1 -1
  11. package/dist/elements/auth-styles.d.ts.map +1 -1
  12. package/dist/elements/auth-styles.js +13 -0
  13. package/dist/elements/auth-styles.js.map +1 -1
  14. package/dist/elements/weave-auth.d.ts +2 -2
  15. package/dist/elements/weave-auth.d.ts.map +1 -1
  16. package/dist/elements/weave-auth.js +251 -119
  17. package/dist/elements/weave-auth.js.map +1 -1
  18. package/dist/identity/account-vault.d.ts +10 -3
  19. package/dist/identity/account-vault.d.ts.map +1 -1
  20. package/dist/identity/account-vault.js +10 -3
  21. package/dist/identity/account-vault.js.map +1 -1
  22. package/dist/identity/contact-key.d.ts +26 -0
  23. package/dist/identity/contact-key.d.ts.map +1 -1
  24. package/dist/identity/contact-key.js +52 -0
  25. package/dist/identity/contact-key.js.map +1 -1
  26. package/dist/index.d.ts +3 -3
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +2 -2
  29. package/dist/index.js.map +1 -1
  30. package/dist/network/index.d.ts +2 -0
  31. package/dist/network/index.d.ts.map +1 -1
  32. package/dist/network/index.js +1 -0
  33. package/dist/network/index.js.map +1 -1
  34. package/dist/network/mailbox.d.ts +49 -0
  35. package/dist/network/mailbox.d.ts.map +1 -0
  36. package/dist/network/mailbox.js +125 -0
  37. package/dist/network/mailbox.js.map +1 -0
  38. package/dist/node/host.d.ts +2 -2
  39. package/dist/node/host.d.ts.map +1 -1
  40. package/dist/node/index.d.ts +1 -1
  41. package/dist/node/index.d.ts.map +1 -1
  42. package/dist/node/node.d.ts.map +1 -1
  43. package/dist/node/node.js +320 -2
  44. package/dist/node/node.js.map +1 -1
  45. package/dist/node/types.d.ts +107 -1
  46. package/dist/node/types.d.ts.map +1 -1
  47. package/dist/schemas/contacts.d.ts +137 -0
  48. package/dist/schemas/contacts.d.ts.map +1 -1
  49. package/dist/schemas/contacts.js +63 -0
  50. package/dist/schemas/contacts.js.map +1 -1
  51. package/dist/schemas/index.d.ts +2 -2
  52. package/dist/schemas/index.d.ts.map +1 -1
  53. package/dist/schemas/index.js +1 -1
  54. package/dist/schemas/index.js.map +1 -1
  55. package/dist/session/auth.d.ts +39 -9
  56. package/dist/session/auth.d.ts.map +1 -1
  57. package/dist/session/auth.js +177 -70
  58. package/dist/session/auth.js.map +1 -1
  59. package/dist/session/connect.js +2 -2
  60. package/dist/session/credentials.d.ts +22 -6
  61. package/dist/session/credentials.d.ts.map +1 -1
  62. package/dist/session/credentials.js +35 -6
  63. package/dist/session/credentials.js.map +1 -1
  64. package/dist/session/hosting.d.ts +1 -1
  65. package/dist/session/index.d.ts +3 -3
  66. package/dist/session/index.d.ts.map +1 -1
  67. package/dist/session/index.js +2 -2
  68. package/dist/session/index.js.map +1 -1
  69. package/dist/space/notify.d.ts +1 -1
  70. package/dist/storage/blob/s3.js +1 -1
  71. package/package.json +54 -40
@@ -0,0 +1,125 @@
1
+ /**
2
+ * @module network/mailbox
3
+ * Talking to a relay's mailbox: leaving a sealed knock under a door's topic,
4
+ * and reading what was left under one's own (`packages/relay/relay.mjs`).
5
+ *
6
+ * Each call opens its own short-lived socket, says one thing, waits for the
7
+ * answer and closes. A mailbox socket never joins a room, so it names no DID,
8
+ * and the relay can't tie a knock to the peer that left it or fetched it.
9
+ *
10
+ * client → { type: 'drop', topic, blob, ttl? } relay → { type: 'dropped', topic, id } | { type: 'refused', topic, reason }
11
+ * client → { type: 'fetch', topic, after? } relay → { type: 'mail', topic, items: [{ seq, id, at, blob }], more }
12
+ * client → { type: 'challenge' } relay → { type: 'challenge', nonce }
13
+ * client → { type: 'purge', topic, sign, ids?, sig } relay → { type: 'purged', topic, count } | { type: 'refused', topic, reason }
14
+ */
15
+ /** At most this many pages per fetch: 16 knocks each, and a door holds 64 */
16
+ const MAX_PAGES = 8;
17
+ export function createMailboxClient(options = {}) {
18
+ const timeoutMs = options.timeoutMs ?? 8000;
19
+ /** Opens a socket, sends one message, and resolves with the first answer `pick` accepts */
20
+ function ask(relay, message, pick) {
21
+ return talk(relay, (send) => send(message), async (answer) => pick(answer));
22
+ }
23
+ /**
24
+ * Opens a socket and holds a short conversation: `start` says the first
25
+ * thing, `hear` answers what comes back (it may `send` again) and settles it
26
+ * by returning a value.
27
+ */
28
+ function talk(relay, start, hear) {
29
+ const Socket = options.WebSocket ?? globalThis.WebSocket;
30
+ if (!Socket)
31
+ return Promise.reject(new Error('No WebSocket here to reach a relay with'));
32
+ return new Promise((resolve, reject) => {
33
+ let settled = false;
34
+ const ws = new Socket(relay);
35
+ const finish = (error, value) => {
36
+ if (settled)
37
+ return;
38
+ settled = true;
39
+ clearTimeout(timer);
40
+ try {
41
+ ws.close();
42
+ }
43
+ catch {
44
+ // already closing
45
+ }
46
+ if (error)
47
+ reject(error);
48
+ else
49
+ resolve(value);
50
+ };
51
+ const timer = setTimeout(() => finish(new Error(`${relay} did not answer`)), timeoutMs);
52
+ const say = (message) => ws.send(JSON.stringify(message));
53
+ ws.onopen = () => start(say);
54
+ ws.onerror = () => finish(new Error(`Could not reach ${relay}`));
55
+ ws.onclose = () => finish(new Error(`${relay} closed the connection`));
56
+ ws.onmessage = (event) => {
57
+ let answer;
58
+ try {
59
+ answer = JSON.parse(String(event.data));
60
+ }
61
+ catch {
62
+ return;
63
+ }
64
+ if (!answer || typeof answer !== 'object')
65
+ return;
66
+ hear(answer, say).then((value) => {
67
+ if (value !== undefined)
68
+ finish(null, value);
69
+ }, (error) => finish(error instanceof Error ? error : new Error(String(error))));
70
+ };
71
+ });
72
+ }
73
+ const client = {
74
+ drop(relay, topic, blob, ttlSeconds) {
75
+ return ask(relay, { type: 'drop', topic, blob, ...(ttlSeconds ? { ttl: ttlSeconds } : {}) }, (answer) => {
76
+ if (answer.topic !== topic)
77
+ return undefined;
78
+ if (answer.type === 'dropped' && typeof answer.id === 'string')
79
+ return answer.id;
80
+ if (answer.type === 'refused')
81
+ throw new Error(`${relay} refused the knock: ${String(answer.reason ?? 'no reason given')}`);
82
+ return undefined;
83
+ });
84
+ },
85
+ async fetch(relay, topic, after = 0) {
86
+ const found = [];
87
+ let from = after;
88
+ for (let page = 0; page < MAX_PAGES; page++) {
89
+ const { items, more } = await ask(relay, { type: 'fetch', topic, after: from }, (answer) => answer.type === 'mail' && answer.topic === topic && Array.isArray(answer.items)
90
+ ? { items: answer.items.filter(isMailItem), more: answer.more === true }
91
+ : undefined);
92
+ found.push(...items);
93
+ if (!more || items.length === 0)
94
+ break;
95
+ from = Math.max(...items.map((item) => item.seq));
96
+ }
97
+ return found;
98
+ },
99
+ purge(relay, topic, sign, ids, signChallenge) {
100
+ return talk(relay, (send) => send({ type: 'challenge' }), async (answer, send) => {
101
+ if (answer.type === 'challenge' && typeof answer.nonce === 'string') {
102
+ send({ type: 'purge', topic, sign, ...(ids ? { ids: [...ids] } : {}), sig: await signChallenge(answer.nonce) });
103
+ return undefined;
104
+ }
105
+ if (answer.topic !== topic)
106
+ return undefined;
107
+ if (answer.type === 'purged' && typeof answer.count === 'number')
108
+ return answer.count;
109
+ if (answer.type === 'refused')
110
+ throw new Error(`${relay} refused to clear the door: ${String(answer.reason ?? 'no reason given')}`);
111
+ return undefined;
112
+ });
113
+ },
114
+ };
115
+ return Object.freeze(client);
116
+ }
117
+ function isMailItem(value) {
118
+ const item = value;
119
+ return (!!item &&
120
+ Number.isSafeInteger(item.seq) &&
121
+ typeof item.id === 'string' &&
122
+ typeof item.at === 'number' &&
123
+ typeof item.blob === 'string');
124
+ }
125
+ //# sourceMappingURL=mailbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mailbox.js","sourceRoot":"","sources":["../../src/network/mailbox.ts"],"names":[],"mappings":"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"}
@@ -15,7 +15,7 @@
15
15
  * — through the carry spaces — which account asked for which.
16
16
  *
17
17
  * Subscriptions live in the host's own store, next to the carried spaces. The
18
- * payment side (`cli/src/host/…`) only ever moves a subscription's date.
18
+ * payment side (`packages/cli/src/host/…`) only ever moves a subscription's date.
19
19
  */
20
20
  import type { CryptoProvider } from '../types.js';
21
21
  import { type ServerAuth } from '../network/peer-auth.js';
@@ -39,7 +39,7 @@ export interface Subscription {
39
39
  };
40
40
  /** The payment provider's reference for whoever pays — nothing else about them is kept */
41
41
  readonly customer?: string;
42
- /** A wallet payment asked for and not yet seen (`cli/src/wallet.ts`) */
42
+ /** A wallet payment asked for and not yet seen (`packages/cli/src/wallet.ts`) */
43
43
  readonly invoice?: Invoice;
44
44
  }
45
45
  /** A payment a host asked a wallet for: an exact amount, so the transfer that arrives says whose it is */
@@ -1 +1 @@
1
- {"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../src/node/host.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../src/node/host.ts"],"names":[],"mappings":"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"}
@@ -16,5 +16,5 @@ export type { StoreFactory, StoreOptions } from './stores.js';
16
16
  export type { Keeper } from '../space/roles.js';
17
17
  export { NODE_ACTIONS, runAction, checkActionInput } from './actions.js';
18
18
  export type { NodeAction, ActionSchema } from './actions.js';
19
- export type { P2PNode, NodeConfig, NodeNetworkConfig, CacheConfig, NodeSpaces, NodeRecords, NodeRecord, NodeEvent, SpaceSummary, SpaceStatus, SpaceProfile, NewSpace, InvitePreview, ListOptions, ConnectionState, DelegateParams, Delegated, NodeCollection, NodeCollections, DefineCollection, NodeAccount, NodeCarriers, NodeNotifications, NotifyView, NodeHosting, HostingView, CarrierSummary, AccountProfileView, NodeContacts, ContactView, ContactRequest, } from './types.js';
19
+ export type { P2PNode, NodeConfig, NodeNetworkConfig, CacheConfig, NodeSpaces, NodeRecords, NodeRecord, NodeEvent, SpaceSummary, SpaceStatus, SpaceProfile, NewSpace, InvitePreview, ListOptions, ConnectionState, DelegateParams, Delegated, NodeCollection, NodeCollections, DefineCollection, NodeAccount, NodeCarriers, NodeNotifications, NotifyView, NodeHosting, HostingView, CarrierSummary, AccountProfileView, NodeContacts, ContactView, ContactRequest, NodeDoors, DoorView, KnockView, SentKnockView, } from './types.js';
20
20
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AACpH,YAAY,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5D,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAChG,YAAY,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC/D,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC9D,YAAY,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACzE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC7D,YAAY,EACV,OAAO,EACP,UAAU,EACV,iBAAiB,EACjB,WAAW,EACX,UAAU,EACV,WAAW,EACX,UAAU,EACV,SAAS,EACT,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,aAAa,EACb,WAAW,EACX,eAAe,EACf,cAAc,EACd,SAAS,EACT,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,UAAU,EACV,WAAW,EACX,WAAW,EACX,cAAc,EACd,kBAAkB,EAClB,YAAY,EACZ,WAAW,EACX,cAAc,GACf,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AACpH,YAAY,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5D,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAChG,YAAY,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC/D,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC9D,YAAY,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACzE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC7D,YAAY,EACV,OAAO,EACP,UAAU,EACV,iBAAiB,EACjB,WAAW,EACX,UAAU,EACV,WAAW,EACX,UAAU,EACV,SAAS,EACT,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,aAAa,EACb,WAAW,EACX,eAAe,EACf,cAAc,EACd,SAAS,EACT,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,UAAU,EACV,WAAW,EACX,WAAW,EACX,cAAc,EACd,kBAAkB,EAClB,YAAY,EACZ,WAAW,EACX,cAAc,EACd,SAAS,EACT,QAAQ,EACR,SAAS,EACT,aAAa,GACd,MAAM,YAAY,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/node/node.ts"],"names":[],"mappings":"AAqBA,OAAO,EAA+C,KAAK,UAAU,EAAkB,MAAM,qBAAqB,CAAC;AAkDnH,OAAO,KAAK,EASV,UAAU,EAaV,OAAO,EAER,MAAM,YAAY,CAAC;AAEpB,yEAAyE;AACzE,eAAO,MAAM,kBAAkB,EAAE,UAA+C,CAAC;AA+CjF;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,CAu5CrE"}
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/node/node.ts"],"names":[],"mappings":"AAqBA,OAAO,EAA+C,KAAK,UAAU,EAAkB,MAAM,qBAAqB,CAAC;AA6EnH,OAAO,KAAK,EAYV,UAAU,EAaV,OAAO,EAER,MAAM,YAAY,CAAC;AAEpB,yEAAyE;AACzE,eAAO,MAAM,kBAAkB,EAAE,UAA+C,CAAC;AA+CjF;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,CA+sDrE"}
package/dist/node/node.js CHANGED
@@ -30,8 +30,10 @@ import { DEFAULT_ICE_SERVERS } from '../network/rtc-transport.js';
30
30
  import { deriveInviteKey } from '../space/space-access.js';
31
31
  import { base64UrlDecode, base64UrlEncode } from '../utils/encoding.js';
32
32
  import { onePerKey } from '../records/rules.js';
33
- import { contactKeyPair, deriveMemberKeyBytes, openSealed, sealFor } from '../identity/contact-key.js';
34
- import { contact as contactSchema, contactRequest as contactRequestSchema } from '../schemas/contacts.js';
33
+ import { contactKeyPair, contactPublicKey, deriveDoorKeyBytes, deriveDoorSignKeyBytes, deriveMemberKeyBytes, openSealed, sealFor } from '../identity/contact-key.js';
34
+ import { contact as contactSchema, contactRequest as contactRequestSchema, door as doorSchema, knock as knockSchema, knockAnswer as knockAnswerSchema, } from '../schemas/contacts.js';
35
+ import { checkAnswer, clip, doorTopic, encodeDoorCode, KNOCK_TTL_SECONDS, knockId, MAX_DOOR_RELAYS, openKnock, parseDoorCode, sealKnock, signAnswer, signPurge, } from '../doors/doors.js';
36
+ import { createMailboxClient } from '../network/mailbox.js';
35
37
  import { team } from '../space/presets.js';
36
38
  import { CARRIER_COLLECTION, deriveAccountRegistry, deriveContactsSpace, MEMBERSHIP_COLLECTION, PROFILE_COLLECTION, PROFILE_KEY, } from '../space/account-registry.js';
37
39
  import { CARRY_CLOSED_KEY, makePass, PASS_COLLECTION, passKey } from '../space/pass.js';
@@ -1137,11 +1139,14 @@ export async function createNode(config) {
1137
1139
  const name = contact.name.trim().slice(0, 200);
1138
1140
  if (!name)
1139
1141
  throw new Error('A contact needs a name');
1142
+ // An answer to their knock still to be written stays to be written.
1143
+ const pending = (await contactRecords()).find((record) => record.body.did === contact.did)?.body?.door;
1140
1144
  return writeContact({
1141
1145
  did: contact.did,
1142
1146
  name,
1143
1147
  ...(contact.space ? { space: contact.space } : {}),
1144
1148
  ...(contact.note ? { note: contact.note.slice(0, 2000) } : {}),
1149
+ ...(pending ? { door: pending } : {}),
1145
1150
  });
1146
1151
  },
1147
1152
  async remove(did) {
@@ -1227,6 +1232,306 @@ export async function createNode(config) {
1227
1232
  return [...seen].filter((account) => account !== config.signer.did && account !== did).sort();
1228
1233
  },
1229
1234
  });
1235
+ // ─── Doors ─────────────────────────────────────────────────────────
1236
+ //
1237
+ // A door is a `std.door` in the contacts space; its keys are derived from
1238
+ // the contact key and its id, so every device with the contact key opens the
1239
+ // same doors. Knocks wait in the mailboxes of the relays a door names.
1240
+ //
1241
+ // A knock you left is a `std.knock` until the person behind the door
1242
+ // answers in the space for two — a `std.knock-answer` signed with the
1243
+ // door's signing key — when it becomes a contact and the invite is closed.
1244
+ // Accepting a knock writes that answer as soon as this account's membership
1245
+ // there has landed; until then the contact carries the door's id.
1246
+ // See `packages/core/src/doors/doors.ts`.
1247
+ const mailbox = config.mailbox ?? createMailboxClient();
1248
+ /** Knocks seen at this node's doors, by id: opened (null when they didn't check out), and until when to keep them */
1249
+ const knockCache = new Map();
1250
+ const DAY_MS = 24 * 3600 * 1000;
1251
+ function requireContactKey(what) {
1252
+ // Agents never open doors or knock, even one a home gave the contact key.
1253
+ if (agentSession)
1254
+ throw new Error(`An agent can't use doors. ${what} is the person's to do.`);
1255
+ if (!config.contactKey)
1256
+ throw new Error(`${what} needs your contact key. Connect to your account home again, and allow contacts.`);
1257
+ return config.contactKey;
1258
+ }
1259
+ /** A door's keys, from the contact key and its id */
1260
+ async function doorKeys(id) {
1261
+ const contactKey = requireContactKey('A door');
1262
+ const key = await deriveDoorKeyBytes(contactKey, id);
1263
+ const sign = await deriveDoorSignKeyBytes(contactKey, id);
1264
+ const signPublic = contactPublicKey(sign);
1265
+ return { key, sign, publicKey: contactPublicKey(key), signPublic, topic: await doorTopic(signPublic) };
1266
+ }
1267
+ async function doorRecords() {
1268
+ if (!contactsSpaceId)
1269
+ return [];
1270
+ return (await (await contactsRuntime()).list({ collection: doorSchema.name })).filter((record) => record.verified && record.root === config.signer.did && typeof record.body?.id === 'string' && Array.isArray(record.body.relays));
1271
+ }
1272
+ async function doorView(record) {
1273
+ const body = record.body;
1274
+ const keys = await doorKeys(body.id);
1275
+ return Object.freeze({
1276
+ id: body.id,
1277
+ ...(body.label ? { label: body.label } : {}),
1278
+ ...(body.name ? { name: body.name } : {}),
1279
+ key: keys.publicKey,
1280
+ sign: keys.signPublic,
1281
+ relays: Object.freeze([...body.relays]),
1282
+ code: encodeDoorCode({ key: keys.publicKey, sign: keys.signPublic, relays: body.relays, ...(body.name ? { name: body.name } : {}) }),
1283
+ createdAt: record.createdAt,
1284
+ });
1285
+ }
1286
+ /** Clears knocks from every relay a door names, as its owner; how many relays did */
1287
+ async function purgeDoor(door, ids) {
1288
+ const keys = await doorKeys(door.id);
1289
+ const done = await Promise.allSettled(door.relays.map((relay) => mailbox.purge(relay, keys.topic, keys.signPublic, ids, (nonce) => signPurge(keys.sign, keys.topic, nonce, ids))));
1290
+ return done.filter((result) => result.status === 'fulfilled').length;
1291
+ }
1292
+ async function sentRecords() {
1293
+ if (!contactsSpaceId)
1294
+ return [];
1295
+ return (await (await contactsRuntime()).list({ collection: knockSchema.name })).filter((record) => record.verified &&
1296
+ record.root === config.signer.did &&
1297
+ typeof record.body?.space === 'string' &&
1298
+ typeof record.body.sign === 'string' &&
1299
+ typeof record.body.invite === 'string');
1300
+ }
1301
+ /**
1302
+ * Knocks of yours: answered ones become contacts, and the invite is closed;
1303
+ * ones nobody answered within the knock's life are let go, space and all.
1304
+ */
1305
+ async function settleSent() {
1306
+ for (const record of await sentRecords()) {
1307
+ const { space, name, sign, invite } = record.body;
1308
+ if (!(await registry.get(space))) {
1309
+ await (await contactsRuntime()).remove(record.key);
1310
+ continue;
1311
+ }
1312
+ const open = await runtime(space);
1313
+ const { members } = await open.access();
1314
+ let answered = null;
1315
+ for (const answer of await open.list({ collection: knockAnswerSchema.name })) {
1316
+ const did = answer.root;
1317
+ if (!answer.verified || !did || did === config.signer.did || answer.createdBy !== did || typeof answer.body?.sig !== 'string')
1318
+ continue;
1319
+ if (!members.some((member) => member.did === did))
1320
+ continue;
1321
+ if (await checkAnswer(sign, space, did, answer.body.sig)) {
1322
+ answered = did;
1323
+ break;
1324
+ }
1325
+ }
1326
+ if (answered) {
1327
+ // Whoever else the invite reached, nobody more joins with it.
1328
+ await spaces.closeInvite(space, invite).catch(() => { });
1329
+ const given = (await open.profiles()).find((profile) => profile.did === answered)?.name;
1330
+ await writeContact({ did: answered, name: given ?? name, space });
1331
+ await (await contactsRuntime()).remove(record.key);
1332
+ }
1333
+ else if (Date.now() - Date.parse(record.createdAt) > KNOCK_TTL_SECONDS * 1000 + DAY_MS) {
1334
+ await spaces.leave(space).catch(() => { });
1335
+ await (await contactsRuntime()).remove(record.key);
1336
+ }
1337
+ }
1338
+ }
1339
+ /** Writes the answer to knocks this account accepted, once it can write in the space for two */
1340
+ async function answerAccepted() {
1341
+ for (const record of await contactRecords()) {
1342
+ const { did, space, door } = record.body;
1343
+ if (typeof door !== 'string' || typeof space !== 'string')
1344
+ continue;
1345
+ if (!(await registry.get(space)))
1346
+ continue;
1347
+ const open = await runtime(space);
1348
+ if ((await open.access()).role === null)
1349
+ continue;
1350
+ const { sign } = await doorKeys(door);
1351
+ try {
1352
+ await open.put(knockAnswerSchema.name, { sig: await signAnswer(sign, space, config.signer.did) });
1353
+ }
1354
+ catch {
1355
+ continue; // The space's definition of answers hasn't arrived yet.
1356
+ }
1357
+ const { door: _answered, ...rest } = record.body;
1358
+ await writeContact({ ...rest, did });
1359
+ }
1360
+ }
1361
+ /** What the mailboxes of this node's doors hold now, opened and cached */
1362
+ async function readDoors() {
1363
+ const now = Date.now();
1364
+ for (const [id, cached] of knockCache)
1365
+ if (cached.until <= now)
1366
+ knockCache.delete(id);
1367
+ const found = [];
1368
+ for (const record of await doorRecords()) {
1369
+ const { id: doorId, relays } = record.body;
1370
+ const keys = await doorKeys(doorId);
1371
+ // Every relay, in full: a door holds 64 knocks at most, and a knock
1372
+ // cleared from another device should go from here too. What was opened
1373
+ // once is not opened again.
1374
+ const held = new Map();
1375
+ for (const result of await Promise.allSettled(relays.map((relay) => mailbox.fetch(relay, keys.topic)))) {
1376
+ if (result.status !== 'fulfilled')
1377
+ continue;
1378
+ for (const item of result.value) {
1379
+ // A relay's id is only its word: file the knock under the hash of what it is.
1380
+ const id = await knockId(item.blob);
1381
+ const seen = held.get(id);
1382
+ if (!seen || item.at < seen.at)
1383
+ held.set(id, { blob: item.blob, at: item.at });
1384
+ }
1385
+ }
1386
+ for (const [id, item] of held) {
1387
+ let cached = knockCache.get(id);
1388
+ if (!cached) {
1389
+ cached = { opened: await openKnock(keys.key, item.blob, item.at, provider), door: doorId, until: item.at + KNOCK_TTL_SECONDS * 1000 };
1390
+ knockCache.set(id, cached);
1391
+ }
1392
+ if (cached.opened)
1393
+ found.push({ id, door: doorId, opened: cached.opened });
1394
+ }
1395
+ }
1396
+ return found;
1397
+ }
1398
+ const doors = Object.freeze({
1399
+ async list() {
1400
+ if (!config.contactKey || agentSession)
1401
+ return [];
1402
+ return Promise.all((await doorRecords()).map(doorView));
1403
+ },
1404
+ async open(options = {}) {
1405
+ requireContactKey('Opening a door');
1406
+ if (!contactsSpaceId)
1407
+ await contactsRuntime();
1408
+ const relays = [...(options.relays ?? (config.network?.relays ?? []).filter((url) => checkRelays([url]) === null))].slice(0, MAX_DOOR_RELAYS);
1409
+ if (relays.length === 0)
1410
+ throw new Error('A door needs a relay to hold its knocks, and this node has none.');
1411
+ const name = clip((options.name ?? (await ownName()) ?? '').trim(), 64);
1412
+ const label = options.label ? clip(options.label.trim(), 64) : '';
1413
+ const id = base64UrlEncode(globalThis.crypto.getRandomValues(new Uint8Array(16)));
1414
+ // Checks the relays and name the way a knocker will.
1415
+ const keys = await doorKeys(id);
1416
+ encodeDoorCode({ key: keys.publicKey, sign: keys.signPublic, relays, ...(name ? { name } : {}) });
1417
+ const open = await contactsRuntime();
1418
+ await ensureDefined(open, doorSchema);
1419
+ return doorView(await open.put(doorSchema.name, { id, relays, ...(name ? { name } : {}), ...(label ? { label } : {}) }));
1420
+ },
1421
+ async close(id) {
1422
+ const found = (await doorRecords()).find((record) => record.body.id === id);
1423
+ if (found)
1424
+ await (await contactsRuntime()).remove(found.key);
1425
+ for (const [knock, cached] of knockCache)
1426
+ if (cached.door === id)
1427
+ knockCache.delete(knock);
1428
+ },
1429
+ async clear(id) {
1430
+ requireContactKey('Clearing a door');
1431
+ const found = (await doorRecords()).find((record) => record.body.id === id);
1432
+ if (!found)
1433
+ throw new Error('There is no such door');
1434
+ if ((await purgeDoor(found.body, null)) === 0)
1435
+ throw new Error("None of the door's relays could be reached to clear it.");
1436
+ for (const [knock, cached] of knockCache)
1437
+ if (cached.door === id)
1438
+ knockCache.delete(knock);
1439
+ },
1440
+ async knock(code, options = {}) {
1441
+ requireContactKey('Knocking on a door');
1442
+ requireEverywhere('Knocking on a door');
1443
+ const door = parseDoorCode(code);
1444
+ for (const mine of await doors.list())
1445
+ if (mine.key === door.key)
1446
+ throw new Error('That is one of your own doors');
1447
+ if (!contactsSpaceId)
1448
+ await contactsRuntime();
1449
+ const mine = (await ownName()) ?? 'Someone';
1450
+ const theirs = door.name ?? 'Contact';
1451
+ const pair = await spaces.create({ name: `${clip(mine, 64)} & ${theirs}`, visibility: 'private', ...team });
1452
+ let invite;
1453
+ try {
1454
+ // Defined now, by the one who may: the answer is written by someone who joins as an editor.
1455
+ await ensureDefined(await runtime(pair.id), knockAnswerSchema);
1456
+ invite = await spaces.invite(pair.id, { role: 'editor' });
1457
+ const blob = await sealKnock(door.key, { from: config.signer.did, name: mine, invite, ...(options.note ? { note: options.note } : {}) }, { did: session.did, key: session.key, proof: session.proof() }, provider);
1458
+ const topic = await doorTopic(door.sign);
1459
+ const left = await Promise.allSettled(door.relays.map((relay) => mailbox.drop(relay, topic, blob, KNOCK_TTL_SECONDS)));
1460
+ if (!left.some((result) => result.status === 'fulfilled')) {
1461
+ const reasons = left.map((result) => (result.status === 'rejected' ? String(result.reason?.message ?? result.reason) : '')).filter(Boolean);
1462
+ throw new Error(`None of their door's relays took the knock. ${reasons.join('; ')}`);
1463
+ }
1464
+ }
1465
+ catch (error) {
1466
+ await spaces.leave(pair.id).catch(() => { });
1467
+ throw error;
1468
+ }
1469
+ const open = await contactsRuntime();
1470
+ await ensureDefined(open, knockSchema);
1471
+ await open.put(knockSchema.name, { space: pair.id, name: theirs, door: door.key, sign: door.sign, invite });
1472
+ return { space: pair.id };
1473
+ },
1474
+ async knocks() {
1475
+ requireContactKey('Reading knocks');
1476
+ await settleSent().catch(() => { });
1477
+ await answerAccepted().catch(() => { });
1478
+ const blocked = new Set((await contactRecords()).filter((record) => record.body.blocked === true).map((record) => record.body.did));
1479
+ const found = [];
1480
+ for (const { id, door, opened } of await readDoors()) {
1481
+ if (opened.from === config.signer.did || blocked.has(opened.from))
1482
+ continue;
1483
+ // Already accepted, here or on another device.
1484
+ if (await registry.get(opened.pairSpace))
1485
+ continue;
1486
+ found.push(Object.freeze({
1487
+ id,
1488
+ door,
1489
+ from: opened.from,
1490
+ name: opened.name,
1491
+ ...(opened.note ? { note: opened.note } : {}),
1492
+ pairSpace: opened.pairSpace,
1493
+ at: new Date(opened.at).toISOString(),
1494
+ }));
1495
+ }
1496
+ return found.sort((a, b) => b.at.localeCompare(a.at));
1497
+ },
1498
+ async sent() {
1499
+ await settleSent().catch(() => { });
1500
+ return (await sentRecords()).map((record) => Object.freeze({ space: record.body.space, name: record.body.name, at: record.createdAt }));
1501
+ },
1502
+ async accept(id) {
1503
+ requireContactKey('Opening the door to someone');
1504
+ requireEverywhere('Opening the door to someone');
1505
+ if (!knockCache.get(id)?.opened)
1506
+ await readDoors();
1507
+ const cached = knockCache.get(id);
1508
+ const knocked = cached?.opened;
1509
+ if (!cached || !knocked)
1510
+ throw new Error('That knock is gone: it expired, or the door was closed or cleared.');
1511
+ await spaces.join(knocked.invite);
1512
+ const known = await contacts.get(knocked.from);
1513
+ const contact = await writeContact({ did: knocked.from, name: known?.name ?? knocked.name, space: knocked.pairSpace, door: cached.door });
1514
+ await answerAccepted().catch(() => { });
1515
+ // Done with: nobody needs it in the mailboxes any more.
1516
+ const door = (await doorRecords()).find((record) => record.body.id === cached.door);
1517
+ if (door)
1518
+ await purgeDoor(door.body, [id]).catch(() => 0);
1519
+ knockCache.delete(id);
1520
+ return contact;
1521
+ },
1522
+ async dismiss(id) {
1523
+ requireContactKey('Dismissing a knock');
1524
+ if (!knockCache.has(id))
1525
+ await readDoors();
1526
+ const cached = knockCache.get(id);
1527
+ if (!cached)
1528
+ return;
1529
+ const door = (await doorRecords()).find((record) => record.body.id === cached.door);
1530
+ if (door)
1531
+ await purgeDoor(door.body, [id]);
1532
+ knockCache.delete(id);
1533
+ },
1534
+ });
1230
1535
  const collections = Object.freeze({
1231
1536
  async list(spaceId) {
1232
1537
  return (await runtime(spaceId)).collections();
@@ -1460,6 +1765,18 @@ export async function createNode(config) {
1460
1765
  accept: person('accept contact requests'),
1461
1766
  others: person('look inside a contact\'s space'),
1462
1767
  }),
1768
+ // Doors are the person's: an agent has none and knocks on none.
1769
+ doors: Object.freeze({
1770
+ list: async () => [],
1771
+ open: person('open a door'),
1772
+ close: person('close a door'),
1773
+ knock: person('knock on anyone\'s door'),
1774
+ clear: person('clear a door'),
1775
+ knocks: person('read knocks'),
1776
+ sent: person('look at knocks'),
1777
+ accept: person('open the door to anyone'),
1778
+ dismiss: person('dismiss a knock'),
1779
+ }),
1463
1780
  delegation: () => note,
1464
1781
  iceServers: node.iceServers,
1465
1782
  delegate: person('pass its access on'),
@@ -1483,6 +1800,7 @@ export async function createNode(config) {
1483
1800
  hosting,
1484
1801
  notifications,
1485
1802
  contacts,
1803
+ doors,
1486
1804
  asAgent,
1487
1805
  delegation: () => current,
1488
1806
  iceServers: async () => (mesh ? mesh.iceServers() : (config.network?.iceServers ?? DEFAULT_ICE_SERVERS)),