@athenaintel/react 0.48.0 → 0.50.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.
- package/README.md +48 -10
- package/dist/agent-runtime/src/call-recap.cjs +319 -0
- package/dist/agent-runtime/src/call-recap.cjs.map +1 -0
- package/dist/agent-runtime/src/call-recap.d.ts +142 -0
- package/dist/agent-runtime/src/call-recap.js +319 -0
- package/dist/agent-runtime/src/call-recap.js.map +1 -0
- package/dist/agent-runtime/src/chat-outbox-store.cjs +1 -0
- package/dist/agent-runtime/src/chat-outbox-store.cjs.map +1 -1
- package/dist/agent-runtime/src/chat-outbox-store.d.ts +7 -0
- package/dist/agent-runtime/src/chat-outbox-store.js +1 -0
- package/dist/agent-runtime/src/chat-outbox-store.js.map +1 -1
- package/dist/agent-runtime/src/chat-outbox.cjs +46 -5
- package/dist/agent-runtime/src/chat-outbox.cjs.map +1 -1
- package/dist/agent-runtime/src/chat-outbox.d.ts +29 -0
- package/dist/agent-runtime/src/chat-outbox.js +46 -5
- package/dist/agent-runtime/src/chat-outbox.js.map +1 -1
- package/dist/agent-runtime/src/conversation-index.cjs.map +1 -1
- package/dist/agent-runtime/src/conversation-index.d.ts +7 -0
- package/dist/agent-runtime/src/conversation-index.js.map +1 -1
- package/dist/agent-runtime/src/conversation-list.cjs +2 -1
- package/dist/agent-runtime/src/conversation-list.cjs.map +1 -1
- package/dist/agent-runtime/src/conversation-list.d.ts +19 -3
- package/dist/agent-runtime/src/conversation-list.js +2 -1
- package/dist/agent-runtime/src/conversation-list.js.map +1 -1
- package/dist/agent-runtime/src/index.d.ts +6 -5
- package/dist/agent-runtime/src/interrupts.cjs +28 -1
- package/dist/agent-runtime/src/interrupts.cjs.map +1 -1
- package/dist/agent-runtime/src/interrupts.d.ts +21 -1
- package/dist/agent-runtime/src/interrupts.js +28 -1
- package/dist/agent-runtime/src/interrupts.js.map +1 -1
- package/dist/agent-runtime/src/model-context.cjs +3 -2
- package/dist/agent-runtime/src/model-context.cjs.map +1 -1
- package/dist/agent-runtime/src/model-context.d.ts +6 -1
- package/dist/agent-runtime/src/model-context.js +3 -2
- package/dist/agent-runtime/src/model-context.js.map +1 -1
- package/dist/agent-runtime/src/runtime.d.ts +3 -2
- package/dist/agent-runtime/src/send-recovery.cjs +37 -3
- package/dist/agent-runtime/src/send-recovery.cjs.map +1 -1
- package/dist/agent-runtime/src/send-recovery.d.ts +19 -0
- package/dist/agent-runtime/src/send-recovery.js +37 -3
- package/dist/agent-runtime/src/send-recovery.js.map +1 -1
- package/dist/agent-runtime/src/thread-handoff-flow-machine.d.ts +8 -8
- package/dist/agent-runtime/src/thread-handoff-protocol.d.ts +113 -1
- package/dist/agent-runtime/src/thread-handoff.d.ts +1 -1
- package/dist/agent-runtime/src/use-conversation-list.cjs +3 -2
- package/dist/agent-runtime/src/use-conversation-list.cjs.map +1 -1
- package/dist/agent-runtime/src/use-conversation-list.d.ts +6 -0
- package/dist/agent-runtime/src/use-conversation-list.js +3 -2
- package/dist/agent-runtime/src/use-conversation-list.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.cjs +21 -1
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.d.ts +8 -2
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.js +21 -1
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-runtime.cjs +24 -6
- package/dist/agent-runtime/src/use-deep-agent-runtime.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-runtime.d.ts +17 -0
- package/dist/agent-runtime/src/use-deep-agent-runtime.js +24 -6
- package/dist/agent-runtime/src/use-deep-agent-runtime.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-voice.cjs +44 -9
- package/dist/agent-runtime/src/use-deep-agent-voice.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-voice.d.ts +34 -1
- package/dist/agent-runtime/src/use-deep-agent-voice.js +44 -9
- package/dist/agent-runtime/src/use-deep-agent-voice.js.map +1 -1
- package/dist/agent-runtime/src/use-thread-handoff-flow.d.ts +8 -2
- package/dist/agent-runtime/src/voice-earcon.cjs +139 -0
- package/dist/agent-runtime/src/voice-earcon.cjs.map +1 -0
- package/dist/agent-runtime/src/voice-earcon.d.ts +38 -0
- package/dist/agent-runtime/src/voice-earcon.js +139 -0
- package/dist/agent-runtime/src/voice-earcon.js.map +1 -0
- package/dist/agent-runtime/src/voice-media.cjs.map +1 -1
- package/dist/agent-runtime/src/voice-media.d.ts +14 -0
- package/dist/agent-runtime/src/voice-media.js.map +1 -1
- package/dist/agent-runtime/src/voice-presentation.cjs +2 -0
- package/dist/agent-runtime/src/voice-presentation.cjs.map +1 -1
- package/dist/agent-runtime/src/voice-presentation.js +2 -0
- package/dist/agent-runtime/src/voice-presentation.js.map +1 -1
- package/dist/assets/AthenaAssetEmbed.cjs.map +1 -1
- package/dist/assets/AthenaAssetEmbed.d.ts +11 -5
- package/dist/assets/AthenaAssetEmbed.js.map +1 -1
- package/dist/assets/use-asset-embed.cjs.map +1 -1
- package/dist/assets/use-asset-embed.d.ts +12 -5
- package/dist/assets/use-asset-embed.js.map +1 -1
- package/dist/chat/StatewireApprovalCard.cjs +41 -6
- package/dist/chat/StatewireApprovalCard.cjs.map +1 -1
- package/dist/chat/StatewireApprovalCard.d.ts +18 -1
- package/dist/chat/StatewireApprovalCard.js +42 -7
- package/dist/chat/StatewireApprovalCard.js.map +1 -1
- package/dist/chat/StatewireQueuedMessages.cjs +56 -6
- package/dist/chat/StatewireQueuedMessages.cjs.map +1 -1
- package/dist/chat/StatewireQueuedMessages.d.ts +14 -0
- package/dist/chat/StatewireQueuedMessages.js +58 -8
- package/dist/chat/StatewireQueuedMessages.js.map +1 -1
- package/dist/chat/WaitingOnYouBanner.cjs +6 -2
- package/dist/chat/WaitingOnYouBanner.cjs.map +1 -1
- package/dist/chat/WaitingOnYouBanner.js +7 -3
- package/dist/chat/WaitingOnYouBanner.js.map +1 -1
- package/dist/chat/chat-voice-controls.cjs +3 -1
- package/dist/chat/chat-voice-controls.cjs.map +1 -1
- package/dist/chat/chat-voice-controls.d.ts +6 -1
- package/dist/chat/chat-voice-controls.js +3 -1
- package/dist/chat/chat-voice-controls.js.map +1 -1
- package/dist/chat-ui/index.cjs +3 -0
- package/dist/chat-ui/index.cjs.map +1 -1
- package/dist/chat-ui/index.d.ts +1 -0
- package/dist/chat-ui/index.js +3 -0
- package/dist/chat-ui/index.js.map +1 -1
- package/dist/chat-ui/messages/from-the-call-card.cjs +124 -0
- package/dist/chat-ui/messages/from-the-call-card.cjs.map +1 -0
- package/dist/chat-ui/messages/from-the-call-card.d.ts +17 -0
- package/dist/chat-ui/messages/from-the-call-card.js +124 -0
- package/dist/chat-ui/messages/from-the-call-card.js.map +1 -0
- package/dist/chat-ui/rich-text/chat-link.cjs +1 -0
- package/dist/chat-ui/rich-text/chat-link.cjs.map +1 -1
- package/dist/chat-ui/rich-text/chat-link.d.ts +4 -0
- package/dist/chat-ui/rich-text/chat-link.js +4 -3
- package/dist/chat-ui/rich-text/chat-link.js.map +1 -1
- package/dist/chat-ui/rich-text/chat-message-body.cjs +1 -0
- package/dist/chat-ui/rich-text/chat-message-body.cjs.map +1 -1
- package/dist/chat-ui/rich-text/chat-message-body.d.ts +12 -0
- package/dist/chat-ui/rich-text/chat-message-body.js +2 -1
- package/dist/chat-ui/rich-text/chat-message-body.js.map +1 -1
- package/dist/chat-ui/thread/chat-thread-shell.cjs +2 -0
- package/dist/chat-ui/thread/chat-thread-shell.cjs.map +1 -1
- package/dist/chat-ui/thread/chat-thread-shell.js +2 -0
- package/dist/chat-ui/thread/chat-thread-shell.js.map +1 -1
- package/dist/collab/client.cjs +15 -3
- package/dist/collab/client.cjs.map +1 -1
- package/dist/collab/client.d.ts +4 -2
- package/dist/collab/client.js +16 -4
- package/dist/collab/client.js.map +1 -1
- package/dist/composer/index.d.ts +1 -1
- package/dist/composer/useReadComposerDraft.cjs +26 -7
- package/dist/composer/useReadComposerDraft.cjs.map +1 -1
- package/dist/composer/useReadComposerDraft.d.ts +23 -1
- package/dist/composer/useReadComposerDraft.js +27 -8
- package/dist/composer/useReadComposerDraft.js.map +1 -1
- package/dist/index.cjs +3 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/keryx-http/src/unconfirmed-update-attributions.cjs +190 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.cjs.map +1 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.d.ts +132 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.js +190 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.js.map +1 -0
- package/dist/keryx-http/src/update-attributions.cjs +34 -4
- package/dist/keryx-http/src/update-attributions.cjs.map +1 -1
- package/dist/keryx-http/src/update-attributions.d.ts +36 -4
- package/dist/keryx-http/src/update-attributions.js +35 -5
- package/dist/keryx-http/src/update-attributions.js.map +1 -1
- package/dist/provider/AthenaProvider.cjs +4 -0
- package/dist/provider/AthenaProvider.cjs.map +1 -1
- package/dist/provider/AthenaProvider.d.ts +11 -1
- package/dist/provider/AthenaProvider.js +4 -0
- package/dist/provider/AthenaProvider.js.map +1 -1
- package/dist/react/package.json.cjs +1 -1
- package/dist/react/package.json.js +1 -1
- package/dist/threads/api.cjs.map +1 -1
- package/dist/threads/api.d.ts +15 -0
- package/dist/threads/api.js.map +1 -1
- package/dist/threads/conversation-list-fetcher.cjs +13 -5
- package/dist/threads/conversation-list-fetcher.cjs.map +1 -1
- package/dist/threads/conversation-list-fetcher.d.ts +4 -1
- package/dist/threads/conversation-list-fetcher.js +13 -5
- package/dist/threads/conversation-list-fetcher.js.map +1 -1
- package/dist/threads/statewire-thread-list.cjs +28 -6
- package/dist/threads/statewire-thread-list.cjs.map +1 -1
- package/dist/threads/statewire-thread-list.d.ts +20 -8
- package/dist/threads/statewire-thread-list.js +28 -6
- package/dist/threads/statewire-thread-list.js.map +1 -1
- package/dist/threads/use-athena-sessions.cjs +27 -5
- package/dist/threads/use-athena-sessions.cjs.map +1 -1
- package/dist/threads/use-athena-sessions.d.ts +10 -5
- package/dist/threads/use-athena-sessions.js +28 -6
- package/dist/threads/use-athena-sessions.js.map +1 -1
- package/dist/threads/use-rest-conversation-history.d.ts +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
import { mergeContentIds, excludeContentIds, intersectContentIds, intersectUpdateWithContentIds, createContentIdsFromUpdate } from "@y/y";
|
|
5
|
+
import { withUpdateAttributionsTrailer, readVarUint, writeVarUint } from "./update-attributions.js";
|
|
6
|
+
const MESSAGE_SYNC = 0;
|
|
7
|
+
const SYNC_STEP_2 = 1;
|
|
8
|
+
const SYNC_UPDATE = 2;
|
|
9
|
+
const MAX_ATTRIBUTED_RUNS = 64;
|
|
10
|
+
function noIds(ids) {
|
|
11
|
+
return ids.inserts.isEmpty() && ids.deletes.isEmpty();
|
|
12
|
+
}
|
|
13
|
+
function sameAttributions(a, b) {
|
|
14
|
+
return a.length === b.length && a.every((pair, index) => {
|
|
15
|
+
var _a, _b;
|
|
16
|
+
return pair.k === ((_a = b[index]) == null ? void 0 : _a.k) && pair.v === ((_b = b[index]) == null ? void 0 : _b.v);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function readSyncFrame(frame) {
|
|
20
|
+
const type = readVarUint({ bytes: frame, start: 0 });
|
|
21
|
+
if ((type == null ? void 0 : type.value) !== MESSAGE_SYNC) return null;
|
|
22
|
+
const syncType = readVarUint({ bytes: frame, start: type.next });
|
|
23
|
+
if (syncType === null) return null;
|
|
24
|
+
const length = readVarUint({ bytes: frame, start: syncType.next });
|
|
25
|
+
if (length === null || length.next + length.value !== frame.length) return null;
|
|
26
|
+
return { syncType: syncType.value, payload: frame.subarray(length.next) };
|
|
27
|
+
}
|
|
28
|
+
function syncFrame({ syncType, payload }) {
|
|
29
|
+
const head = [];
|
|
30
|
+
writeVarUint({ out: head, value: MESSAGE_SYNC });
|
|
31
|
+
writeVarUint({ out: head, value: syncType });
|
|
32
|
+
writeVarUint({ out: head, value: payload.length });
|
|
33
|
+
const frame = new Uint8Array(head.length + payload.length);
|
|
34
|
+
frame.set(head, 0);
|
|
35
|
+
frame.set(payload, head.length);
|
|
36
|
+
return frame;
|
|
37
|
+
}
|
|
38
|
+
function idsOf(update) {
|
|
39
|
+
try {
|
|
40
|
+
return createContentIdsFromUpdate(update);
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function copyOf({ ids, attributions }) {
|
|
46
|
+
return { ids, attributions: attributions.map(({ k, v }) => ({ k, v })) };
|
|
47
|
+
}
|
|
48
|
+
function bounded(runs) {
|
|
49
|
+
return runs.length > MAX_ATTRIBUTED_RUNS ? runs.slice(runs.length - MAX_ATTRIBUTED_RUNS) : runs;
|
|
50
|
+
}
|
|
51
|
+
const ROOMS = Symbol.for("@athenaintel/keryx-http/unconfirmed-update-attributions");
|
|
52
|
+
function rooms() {
|
|
53
|
+
const host = globalThis;
|
|
54
|
+
return host[ROOMS] ?? (host[ROOMS] = { live: /* @__PURE__ */ new WeakMap(), left: /* @__PURE__ */ new WeakMap() });
|
|
55
|
+
}
|
|
56
|
+
class UnconfirmedUpdateAttributions {
|
|
57
|
+
constructor({
|
|
58
|
+
doc,
|
|
59
|
+
attributionsFor,
|
|
60
|
+
isRemoteOrigin
|
|
61
|
+
}) {
|
|
62
|
+
__publicField(this, "runs");
|
|
63
|
+
__publicField(this, "doc");
|
|
64
|
+
__publicField(this, "attributionsFor");
|
|
65
|
+
__publicField(this, "isRemoteOrigin");
|
|
66
|
+
__publicField(this, "handleUpdate", (update, origin) => {
|
|
67
|
+
if (this.isRemoteOrigin(origin)) return;
|
|
68
|
+
const attributions = this.attributionsFor();
|
|
69
|
+
if (!attributions || attributions.length === 0) return;
|
|
70
|
+
const ids = idsOf(update);
|
|
71
|
+
if (!ids || noIds(ids)) return;
|
|
72
|
+
const last = this.runs.at(-1);
|
|
73
|
+
if (last && sameAttributions(last.attributions, attributions)) {
|
|
74
|
+
last.ids = mergeContentIds([last.ids, ids]);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
this.runs = bounded([...this.runs, copyOf({ ids, attributions })]);
|
|
78
|
+
});
|
|
79
|
+
this.doc = doc;
|
|
80
|
+
this.attributionsFor = attributionsFor;
|
|
81
|
+
this.isRemoteOrigin = isRemoteOrigin;
|
|
82
|
+
const { live, left } = rooms();
|
|
83
|
+
this.runs = left.get(doc) ?? [];
|
|
84
|
+
left.delete(doc);
|
|
85
|
+
live.set(doc, this);
|
|
86
|
+
doc.on("update", this.handleUpdate);
|
|
87
|
+
}
|
|
88
|
+
/** The attributed writes Keryx has not acknowledged, oldest first. */
|
|
89
|
+
get pending() {
|
|
90
|
+
return this.runs.map(copyOf);
|
|
91
|
+
}
|
|
92
|
+
/** Writes an earlier session kept, older than any written here: they go out first. */
|
|
93
|
+
restore(writes) {
|
|
94
|
+
this.runs = bounded([...writes.map(copyOf), ...this.runs]);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* One socket of the room. While the room keeps writes, the socket's sync step 2 (its answer to
|
|
98
|
+
* Keryx's sync step 1) waits for the next frame Keryx sends, its whole document, which drops what
|
|
99
|
+
* Keryx already holds before the answer is split (see the module comment); every frame sent
|
|
100
|
+
* meanwhile waits behind it, in order. Nothing kept, or a socket that cannot hear Keryx: every
|
|
101
|
+
* frame goes out as it is sent.
|
|
102
|
+
*/
|
|
103
|
+
connect({
|
|
104
|
+
send,
|
|
105
|
+
hears
|
|
106
|
+
}) {
|
|
107
|
+
let held = null;
|
|
108
|
+
const sendNow = (frame) => {
|
|
109
|
+
for (const out of this.framesFor(frame)) send(out);
|
|
110
|
+
};
|
|
111
|
+
return {
|
|
112
|
+
received: (frame) => {
|
|
113
|
+
this.received(frame);
|
|
114
|
+
if (held === null) return;
|
|
115
|
+
const waiting = held;
|
|
116
|
+
held = null;
|
|
117
|
+
for (const next of waiting) sendNow(next);
|
|
118
|
+
},
|
|
119
|
+
send: (frame) => {
|
|
120
|
+
var _a;
|
|
121
|
+
if (held !== null) {
|
|
122
|
+
held.push(frame);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (hears && this.runs.length > 0 && ((_a = readSyncFrame(frame)) == null ? void 0 : _a.syncType) === SYNC_STEP_2) {
|
|
126
|
+
held = [frame];
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
sendNow(frame);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/** Keryx sent `frame`: the ids it carries are Keryx's, kept no longer. */
|
|
134
|
+
received(frame) {
|
|
135
|
+
if (this.runs.length === 0) return;
|
|
136
|
+
const message = readSyncFrame(frame);
|
|
137
|
+
if (message === null) return;
|
|
138
|
+
if (message.syncType !== SYNC_STEP_2 && message.syncType !== SYNC_UPDATE) return;
|
|
139
|
+
const held = idsOf(message.payload);
|
|
140
|
+
if (!held) return;
|
|
141
|
+
this.runs = this.runs.map(({ ids, attributions }) => ({ ids: excludeContentIds(ids, held), attributions })).filter((run) => !noIds(run.ids));
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* What goes out in place of `frame`: `[frame]` itself, except a sync step 2 carrying kept writes,
|
|
145
|
+
* which goes out behind each of them as its own marked sync update.
|
|
146
|
+
*/
|
|
147
|
+
framesFor(frame) {
|
|
148
|
+
if (this.runs.length === 0) return [frame];
|
|
149
|
+
const message = readSyncFrame(frame);
|
|
150
|
+
if (message === null || message.syncType !== SYNC_STEP_2) return [frame];
|
|
151
|
+
const answer = message.payload;
|
|
152
|
+
const all = idsOf(answer);
|
|
153
|
+
if (!all) return [frame];
|
|
154
|
+
const frames = [];
|
|
155
|
+
const marked = [];
|
|
156
|
+
for (const run of this.runs) {
|
|
157
|
+
const part = intersectContentIds(run.ids, all);
|
|
158
|
+
if (noIds(part)) continue;
|
|
159
|
+
frames.push(
|
|
160
|
+
withUpdateAttributionsTrailer({
|
|
161
|
+
message: syncFrame({
|
|
162
|
+
syncType: SYNC_UPDATE,
|
|
163
|
+
payload: intersectUpdateWithContentIds(answer, part)
|
|
164
|
+
}),
|
|
165
|
+
attributions: run.attributions
|
|
166
|
+
})
|
|
167
|
+
);
|
|
168
|
+
marked.push(part);
|
|
169
|
+
}
|
|
170
|
+
if (marked.length === 0) return [frame];
|
|
171
|
+
const rest = excludeContentIds(all, mergeContentIds(marked));
|
|
172
|
+
frames.push(
|
|
173
|
+
syncFrame({ syncType: SYNC_STEP_2, payload: intersectUpdateWithContentIds(answer, rest) })
|
|
174
|
+
);
|
|
175
|
+
return frames;
|
|
176
|
+
}
|
|
177
|
+
/** Stops keeping; what is still kept waits for the next tracker made for the document. */
|
|
178
|
+
destroy() {
|
|
179
|
+
this.doc.off("update", this.handleUpdate);
|
|
180
|
+
const { live, left } = rooms();
|
|
181
|
+
if (live.get(this.doc) === this) live.delete(this.doc);
|
|
182
|
+
if (this.runs.length > 0) left.set(this.doc, this.runs);
|
|
183
|
+
this.runs = [];
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
export {
|
|
187
|
+
MAX_ATTRIBUTED_RUNS,
|
|
188
|
+
UnconfirmedUpdateAttributions
|
|
189
|
+
};
|
|
190
|
+
//# sourceMappingURL=unconfirmed-update-attributions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unconfirmed-update-attributions.js","sources":["../../../../keryx-http/src/unconfirmed-update-attributions.ts"],"sourcesContent":["/**\n * The attributed writes a room has not seen Keryx acknowledge, kept so that a write made while the\n * room's socket is closed still reaches Keryx with its attributions.\n *\n * A write made inside `runWithDocUpdateAttributions` leaves as its own sync update followed by the\n * attributions trailer (`attributingWebSocket`), but only if the room's socket is open as it is\n * made. One made while it is closed (offline, reconnecting, before the first connection) reaches\n * Keryx inside the reconnect's sync step 2: the client's answer to Keryx's sync step 1, everything\n * it holds that Keryx lacks. Keryx reads no trailer after a sync step 2, so it would count the\n * write as the person's edit.\n *\n * So the room keeps the content ids of every write made in a scope, with the scope's attributions,\n * until Keryx sends those ids back: the echo of the update that carried them, or the whole document\n * Keryx sends when a socket opens (`received`; `@y/websocket` confirms its own unconfirmed updates\n * the same way). When the socket sends a sync step 2, each kept write's part of it goes out first,\n * oldest first, as its own sync update with its trailer, and the sync step 2 follows without those\n * ids (`framesFor`). The parts are cut from the sync step 2 itself, which already holds only what\n * Keryx lacks past its state vector. One socket keeps the order, so Keryx appends each marked update\n * before the person's sync step 2.\n *\n * That answer waits for Keryx's document (`connect`). Keryx sends its sync step 1 and, in the same\n * cork, its whole document, but `@y/websocket` answers the sync step 1 as it arrives. A kept write\n * Keryx already appended (its echo lost with the socket) is still kept then, and the sync step 2\n * holds the client's whole delete set, so its deletes would go out again as a marked update, which\n * Keryx appends: it drops what it holds from the first sync step 2 only. Held until the next frame,\n * the answer is split once Keryx's document has dropped what it holds.\n *\n * What Keryx records (`patches/@y%2Fhub@0.10.1.patch`):\n * - An update's content map attributes every id it carries, inserts and deletes, read from its\n * bytes: an update that cannot integrate yet (an item next to one the person typed offline, the\n * next clocks of the person's own client) is attributed like any other. Peers hold it until the\n * sync step 2 behind it brings what it needs.\n * - An id is attributed by the first stream entry that carries it (`getDoc`'s `excludeContentMap`),\n * so a later copy changes no merged history. The live change notices read each entry's own\n * content map, though (keryx/src/doc-watcher.ts), and the first sync step 2's strip of what Keryx\n * holds reads the known ids when the frame arrives, possibly before the update ahead of it is in\n * the stream (one whose content map the compute pool builds). A copy inside the sync step 2\n * would then be announced as the person's: the sync step 2 never carries a kept write's ids.\n *\n * Bounded: consecutive writes of one scope merge into one run, and past\n * {@link MAX_ATTRIBUTED_RUNS} runs the oldest is let go (its content then reaches Keryx in a sync\n * step 2, as the person's). A room's local copy keeps them across sessions\n * ({@link unconfirmedAttributedWritesOf}, {@link restoreUnconfirmedAttributedWrites}); without one,\n * a write the room kept is forgotten with the page.\n */\nimport {\n type ContentIds,\n createContentIdsFromUpdate,\n type Doc,\n excludeContentIds,\n intersectContentIds,\n intersectUpdateWithContentIds,\n mergeContentIds,\n} from '@y/y';\nimport {\n type AttributedWritesSocket,\n readVarUint,\n type UnconfirmedAttributedWrites,\n type UpdateAttribution,\n withUpdateAttributionsTrailer,\n writeVarUint,\n} from './update-attributions.js';\n\n/** y-protocols: a sync message, and the sub-types that carry content. */\nconst MESSAGE_SYNC = 0;\nconst SYNC_STEP_2 = 1;\nconst SYNC_UPDATE = 2;\n\n/** Writes made in one attribution scope, by the content ids they wrote (inserts and deletes). */\nexport interface AttributedWrites {\n ids: ContentIds;\n attributions: readonly UpdateAttribution[];\n}\n\n/**\n * The most runs a room keeps; a run is one scope's writes since a write of another scope. Housekeeping\n * all shares one scope, so runs pile up only as Quick Edit steps interleave with it.\n */\nexport const MAX_ATTRIBUTED_RUNS = 64;\n\nfunction noIds(ids: ContentIds): boolean {\n return ids.inserts.isEmpty() && ids.deletes.isEmpty();\n}\n\nfunction sameAttributions(\n a: readonly UpdateAttribution[],\n b: readonly UpdateAttribution[]\n): boolean {\n return (\n a.length === b.length &&\n a.every((pair, index) => pair.k === b[index]?.k && pair.v === b[index]?.v)\n );\n}\n\n/** The sync message `frame` is, when it is exactly one (nothing after its payload). */\nfunction readSyncFrame(frame: Uint8Array): { syncType: number; payload: Uint8Array } | null {\n const type = readVarUint({ bytes: frame, start: 0 });\n if (type?.value !== MESSAGE_SYNC) return null;\n const syncType = readVarUint({ bytes: frame, start: type.next });\n if (syncType === null) return null;\n const length = readVarUint({ bytes: frame, start: syncType.next });\n if (length === null || length.next + length.value !== frame.length) return null;\n return { syncType: syncType.value, payload: frame.subarray(length.next) };\n}\n\n/** A sync message of `syncType` carrying `payload`, as y-protocols writes one. */\nfunction syncFrame({ syncType, payload }: { syncType: number; payload: Uint8Array }): Uint8Array {\n const head: number[] = [];\n writeVarUint({ out: head, value: MESSAGE_SYNC });\n writeVarUint({ out: head, value: syncType });\n writeVarUint({ out: head, value: payload.length });\n const frame = new Uint8Array(head.length + payload.length);\n frame.set(head, 0);\n frame.set(payload, head.length);\n return frame;\n}\n\n/** The content ids an update carries, or `null` for bytes that are not one. */\nfunction idsOf(update: Uint8Array): ContentIds | null {\n try {\n return createContentIdsFromUpdate(update);\n } catch {\n return null;\n }\n}\n\nfunction copyOf({ ids, attributions }: AttributedWrites): AttributedWrites {\n return { ids, attributions: attributions.map(({ k, v }) => ({ k, v })) };\n}\n\n/** `runs` without the oldest past {@link MAX_ATTRIBUTED_RUNS}. */\nfunction bounded(runs: AttributedWrites[]): AttributedWrites[] {\n return runs.length > MAX_ATTRIBUTED_RUNS ? runs.slice(runs.length - MAX_ATTRIBUTED_RUNS) : runs;\n}\n\n/** What a page reads of a room's tracker, whichever copy of this module made it. */\ninterface KeptWrites {\n readonly pending: readonly AttributedWrites[];\n restore(writes: readonly AttributedWrites[]): void;\n}\n\n/**\n * Each document's tracker in the page, and what a destroyed one still kept for the next tracker made\n * for the same document (a transport rebuilt over a retained document; a local copy read into a\n * document before its room's transport exists). On `globalThis`, not in module scope, for the reason\n * the scopes are (`update-attributions.ts`): the SDK's dist inlines its own copy of this module, and\n * a local copy reading a room through another copy must find the same tracker. Weak: each entry goes\n * with its document.\n */\nconst ROOMS = Symbol.for('@athenaintel/keryx-http/unconfirmed-update-attributions');\n\ninterface Rooms {\n live: WeakMap<object, KeptWrites>;\n left: WeakMap<object, AttributedWrites[]>;\n}\n\ntype RoomsHost = typeof globalThis & {\n [ROOMS]?: Rooms;\n};\n\n// process-local: browser-only, each entry keyed by the document whose writes it holds\nfunction rooms(): Rooms {\n const host = globalThis as RoomsHost;\n return (host[ROOMS] ??= { live: new WeakMap(), left: new WeakMap() });\n}\n\n/**\n * The attributed writes Keryx has not acknowledged for `doc`, oldest first: its room's, or what a\n * destroyed room left for the next. What a room's local copy keeps of them.\n */\nexport function unconfirmedAttributedWritesOf(doc: object): readonly AttributedWrites[] {\n const { live, left } = rooms();\n return live.get(doc)?.pending ?? left.get(doc)?.map(copyOf) ?? [];\n}\n\n/**\n * Hands `doc`'s room attributed writes an earlier session kept (the room's local copy), or keeps\n * them for the next tracker made for it. They are older than anything written since, so they go\n * out first.\n */\nexport function restoreUnconfirmedAttributedWrites({\n doc,\n writes,\n}: {\n doc: object;\n writes: readonly AttributedWrites[];\n}): void {\n if (writes.length === 0) return;\n const { live, left } = rooms();\n const room = live.get(doc);\n if (room) {\n room.restore(writes);\n return;\n }\n left.set(doc, bounded([...writes.map(copyOf), ...(left.get(doc) ?? [])]));\n}\n\n/**\n * The two above, as a page hands them to its local copies (`@athenaintel/local-copy`'s\n * `openLocalCopyStore({ attributedWrites })`, `createLocalCopiesPage({ attributedWrites })`): the\n * copy records a room's kept writes with its edits and restores an ended session's to its room.\n */\nexport const roomAttributedWrites = {\n of: unconfirmedAttributedWritesOf,\n restore: restoreUnconfirmedAttributedWrites,\n};\n\n/**\n * One room's attributed writes Keryx has not acknowledged (see the module comment). Made once per\n * room by its transport, which hands it to every socket it opens (`attributingWebSocket`'s\n * `unconfirmed`) and destroys it with the room; a tracker made for the same document later takes\n * over what it still kept, and what its local copy kept from an earlier session\n * ({@link restoreUnconfirmedAttributedWrites}). Not for a room that only listens: its socket sends\n * nothing to split.\n */\nexport class UnconfirmedUpdateAttributions implements UnconfirmedAttributedWrites, KeptWrites {\n private runs: AttributedWrites[];\n private readonly doc: Doc;\n private readonly attributionsFor: () => readonly UpdateAttribution[] | undefined;\n private readonly isRemoteOrigin: (origin: unknown) => boolean;\n\n constructor({\n doc,\n attributionsFor,\n isRemoteOrigin,\n }: {\n doc: Doc;\n /** The attributions of the update being written now: what the socket's trailer reads. */\n attributionsFor: () => readonly UpdateAttribution[] | undefined;\n /** True for a transaction the transport applied (Keryx's state, a peer's edit). */\n isRemoteOrigin: (origin: unknown) => boolean;\n }) {\n this.doc = doc;\n this.attributionsFor = attributionsFor;\n this.isRemoteOrigin = isRemoteOrigin;\n const { live, left } = rooms();\n this.runs = left.get(doc) ?? [];\n left.delete(doc);\n live.set(doc, this);\n doc.on('update', this.handleUpdate);\n }\n\n private readonly handleUpdate = (update: Uint8Array, origin: unknown): void => {\n if (this.isRemoteOrigin(origin)) return;\n const attributions = this.attributionsFor();\n if (!attributions || attributions.length === 0) return;\n const ids = idsOf(update);\n if (!ids || noIds(ids)) return;\n const last = this.runs.at(-1);\n if (last && sameAttributions(last.attributions, attributions)) {\n last.ids = mergeContentIds([last.ids, ids]);\n return;\n }\n this.runs = bounded([...this.runs, copyOf({ ids, attributions })]);\n };\n\n /** The attributed writes Keryx has not acknowledged, oldest first. */\n get pending(): readonly AttributedWrites[] {\n return this.runs.map(copyOf);\n }\n\n /** Writes an earlier session kept, older than any written here: they go out first. */\n restore(writes: readonly AttributedWrites[]): void {\n this.runs = bounded([...writes.map(copyOf), ...this.runs]);\n }\n\n /**\n * One socket of the room. While the room keeps writes, the socket's sync step 2 (its answer to\n * Keryx's sync step 1) waits for the next frame Keryx sends, its whole document, which drops what\n * Keryx already holds before the answer is split (see the module comment); every frame sent\n * meanwhile waits behind it, in order. Nothing kept, or a socket that cannot hear Keryx: every\n * frame goes out as it is sent.\n */\n connect({\n send,\n hears,\n }: {\n send: (frame: Uint8Array) => void;\n hears: boolean;\n }): AttributedWritesSocket {\n let held: Uint8Array[] | null = null;\n const sendNow = (frame: Uint8Array) => {\n for (const out of this.framesFor(frame)) send(out);\n };\n return {\n received: (frame) => {\n this.received(frame);\n if (held === null) return;\n const waiting = held;\n held = null;\n for (const next of waiting) sendNow(next);\n },\n send: (frame) => {\n if (held !== null) {\n held.push(frame);\n return;\n }\n if (hears && this.runs.length > 0 && readSyncFrame(frame)?.syncType === SYNC_STEP_2) {\n held = [frame];\n return;\n }\n sendNow(frame);\n },\n };\n }\n\n /** Keryx sent `frame`: the ids it carries are Keryx's, kept no longer. */\n received(frame: Uint8Array): void {\n if (this.runs.length === 0) return;\n const message = readSyncFrame(frame);\n if (message === null) return;\n if (message.syncType !== SYNC_STEP_2 && message.syncType !== SYNC_UPDATE) return;\n const held = idsOf(message.payload);\n if (!held) return;\n this.runs = this.runs\n .map(({ ids, attributions }) => ({ ids: excludeContentIds(ids, held), attributions }))\n .filter((run) => !noIds(run.ids));\n }\n\n /**\n * What goes out in place of `frame`: `[frame]` itself, except a sync step 2 carrying kept writes,\n * which goes out behind each of them as its own marked sync update.\n */\n framesFor(frame: Uint8Array): readonly Uint8Array[] {\n if (this.runs.length === 0) return [frame];\n const message = readSyncFrame(frame);\n if (message === null || message.syncType !== SYNC_STEP_2) return [frame];\n const answer = message.payload;\n const all = idsOf(answer);\n if (!all) return [frame];\n const frames: Uint8Array[] = [];\n const marked: ContentIds[] = [];\n for (const run of this.runs) {\n const part = intersectContentIds(run.ids, all);\n if (noIds(part)) continue;\n frames.push(\n withUpdateAttributionsTrailer({\n message: syncFrame({\n syncType: SYNC_UPDATE,\n payload: intersectUpdateWithContentIds(answer, part),\n }),\n attributions: run.attributions,\n })\n );\n marked.push(part);\n }\n if (marked.length === 0) return [frame];\n const rest = excludeContentIds(all, mergeContentIds(marked));\n frames.push(\n syncFrame({ syncType: SYNC_STEP_2, payload: intersectUpdateWithContentIds(answer, rest) })\n );\n return frames;\n }\n\n /** Stops keeping; what is still kept waits for the next tracker made for the document. */\n destroy(): void {\n this.doc.off('update', this.handleUpdate);\n const { live, left } = rooms();\n if (live.get(this.doc) === this) live.delete(this.doc);\n if (this.runs.length > 0) left.set(this.doc, this.runs);\n this.runs = [];\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -82,20 +82,49 @@ function withUpdateAttributionsTrailer({
|
|
|
82
82
|
out.set(trailer, message.length);
|
|
83
83
|
return out;
|
|
84
84
|
}
|
|
85
|
+
function receivedBytes(data) {
|
|
86
|
+
if (data instanceof ArrayBuffer) return new Uint8Array(data);
|
|
87
|
+
if (ArrayBuffer.isView(data))
|
|
88
|
+
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
function listenToFrames({
|
|
92
|
+
socket,
|
|
93
|
+
onFrame
|
|
94
|
+
}) {
|
|
95
|
+
if (!("addEventListener" in socket) || typeof socket.addEventListener !== "function") {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
socket.addEventListener("message", (event) => {
|
|
99
|
+
const frame = receivedBytes(event.data);
|
|
100
|
+
if (frame) onFrame(frame);
|
|
101
|
+
});
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
85
104
|
function attributingWebSocket({
|
|
86
105
|
Base,
|
|
87
|
-
attributionsFor
|
|
106
|
+
attributionsFor,
|
|
107
|
+
unconfirmed
|
|
88
108
|
}) {
|
|
89
109
|
if (typeof Base !== "function") return Base;
|
|
90
110
|
return new Proxy(Base, {
|
|
91
111
|
construct(target, args) {
|
|
92
112
|
const socket = Reflect.construct(target, args);
|
|
93
113
|
const send = socket.send.bind(socket);
|
|
114
|
+
let connection = null;
|
|
115
|
+
if (unconfirmed) {
|
|
116
|
+
const hears = listenToFrames({ socket, onFrame: (frame) => connection == null ? void 0 : connection.received(frame) });
|
|
117
|
+
connection = unconfirmed.connect({ send, hears });
|
|
118
|
+
}
|
|
94
119
|
socket.send = (data) => {
|
|
120
|
+
if (!(data instanceof Uint8Array)) {
|
|
121
|
+
send(data);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
95
124
|
const attributions = attributionsFor();
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
);
|
|
125
|
+
const message = attributions ? withUpdateAttributionsTrailer({ message: data, attributions }) : data;
|
|
126
|
+
if (connection) connection.send(message);
|
|
127
|
+
else send(message);
|
|
99
128
|
};
|
|
100
129
|
return socket;
|
|
101
130
|
}
|
|
@@ -108,4 +137,5 @@ exports.docUpdateAttributions = docUpdateAttributions;
|
|
|
108
137
|
exports.readVarUint = readVarUint;
|
|
109
138
|
exports.runWithDocUpdateAttributions = runWithDocUpdateAttributions;
|
|
110
139
|
exports.withUpdateAttributionsTrailer = withUpdateAttributionsTrailer;
|
|
140
|
+
exports.writeVarUint = writeVarUint;
|
|
111
141
|
//# sourceMappingURL=update-attributions.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update-attributions.cjs","sources":["../../../../keryx-http/src/update-attributions.ts"],"sourcesContent":["/**\n * Attributions one Keryx socket update carries of its own.\n *\n * A Keryx connection's attributions are fixed when its socket opens, so every update a client\n * sends is attributed to the person whose client it is. A write the client makes on its own (the\n * sheet leader re-issuing `=AOP` formulas or re-pushing `=IMPORTRANGE` values, a restored result,\n * an editor binding re-keying what an older binding stored) read as that person's edit and woke\n * \"people only\" subscriptions. Keryx reads a trailer after the update in a sync update message\n * (`readUpdateAttributions` in `patches/@y%2Fhub@0.10.1.patch`): `varUint(1)`, `varUint(n)`, then\n * `n` pairs of `varString` key and value. It accepts only `origin: background`, which it counts as\n * system work (`actorOf` in `keryx/src/compaction-activity.ts`), and the pairs that name a Quick\n * Edit step (`@athenaintel/keryx-http/quick-edit`: `session_id`/`run_kind` on the apply,\n * `quick_edit_undo`/`quick_edit_redo` with `quick_edit_op` on an undo or redo), each value in\n * exactly its shape. A Keryx without that hunk reads the update and ignores the bytes after it, so\n * a trailer never costs an update; a pair it does not allow is dropped and the rest kept.\n *\n * A writer marks its writes by making them inside {@link runWithDocUpdateAttributions} for the\n * doc; the transport's socket ({@link attributingWebSocket}) appends the trailer to each sync\n * update it sends while that scope is open. Both Keryx transports wrap their sockets with it:\n * Olympus's rooms (`KeryxYjsProviderV14`) and `@athenaintel/react/collab`'s `connectGenericDoc`\n * (Midas, the phone's editor page). y-websocket sends a local update from inside the doc's\n * `update` event, which fires inside the write itself, so the trailer always rides on the update\n * the scope wrote. An update sent later (a reconnect's sync step 2 after the socket was down)\n * carries none and stays the person's, as before.\n */\n\n/** One custom attribution, as Keryx stores it on the items an update wrote. */\nexport interface UpdateAttribution {\n k: string;\n v: string;\n}\n\n/** The pair a housekeeping write carries; Agora and Keryx spell it the same way. */\nexport const BACKGROUND_UPDATE_ATTRIBUTIONS: readonly UpdateAttribution[] = [\n { k: 'origin', v: 'background' },\n];\n\n/** The trailer format Keryx reads (`updateAttributionsTrailerV1`). */\nexport const UPDATE_ATTRIBUTIONS_TRAILER_V1 = 1;\n\n/** y-protocols: a sync message, and its update sub-type. */\nconst MESSAGE_SYNC = 0;\nconst MESSAGE_SYNC_UPDATE = 2;\n\n/**\n * The open scopes live on `globalThis`, not in module scope, because two copies of this module can\n * run in one page: `@athenaintel/react` bundles it into its dist (this package is private, so the\n * published SDK must inline it) while a host may import it from this package. A scope a writer\n * opens through one copy must be the one the transport's socket reads through the other.\n */\nconst DOC_SCOPES = Symbol.for('@athenaintel/keryx-http/update-attribution-scopes');\n\ntype DocScopesHost = typeof globalThis & {\n [DOC_SCOPES]?: WeakMap<object, readonly UpdateAttribution[]>;\n};\n\n// process-local: browser-only scopes, each keyed by the doc it guards and cleared in the same synchronous call that set it\nfunction docScopes(): WeakMap<object, readonly UpdateAttribution[]> {\n const host = globalThis as DocScopesHost;\n return (host[DOC_SCOPES] ??= new WeakMap());\n}\n\n/**\n * Run `run` with every update it writes into `doc` marked with `attributions`. Synchronous only:\n * the scope ends when `run` returns. Nested scopes restore the outer one.\n */\nexport function runWithDocUpdateAttributions<T>({\n doc,\n attributions,\n run,\n}: {\n doc: object;\n attributions: readonly UpdateAttribution[];\n run: () => T;\n}): T {\n const scopes = docScopes();\n const outer = scopes.get(doc);\n scopes.set(doc, attributions);\n try {\n return run();\n } finally {\n if (outer === undefined) scopes.delete(doc);\n else scopes.set(doc, outer);\n }\n}\n\n/** The attributions the update being written into `doc` right now carries, if any. */\nexport function docUpdateAttributions(doc: object): readonly UpdateAttribution[] | undefined {\n return docScopes().get(doc);\n}\n\n/** The lib0 varUint at `start` in `bytes` and where the next field begins; `null` past the end. */\nexport function readVarUint({\n bytes,\n start,\n}: {\n bytes: Uint8Array;\n start: number;\n}): { value: number; next: number } | null {\n let value = 0;\n let multiplier = 1;\n for (let pos = start; pos < bytes.length; pos += 1) {\n const byte = bytes[pos];\n if (byte === undefined) return null;\n value += (byte & 0x7f) * multiplier;\n if (byte < 0x80) return { value, next: pos + 1 };\n multiplier *= 0x80;\n if (multiplier > Number.MAX_SAFE_INTEGER) return null;\n }\n return null;\n}\n\n/** Append `value` to `out` as a lib0 varUint. */\nfunction writeVarUint({ out, value }: { out: number[]; value: number }): void {\n let rest = value;\n while (rest > 0x7f) {\n out.push(0x80 | (rest % 0x80));\n rest = Math.floor(rest / 0x80);\n }\n out.push(rest);\n}\n\n/** Exactly one sync update message, with nothing after the update. */\nfunction isBareSyncUpdate(message: Uint8Array): boolean {\n const type = readVarUint({ bytes: message, start: 0 });\n if (type?.value !== MESSAGE_SYNC) return false;\n const syncType = readVarUint({ bytes: message, start: type.next });\n if (syncType?.value !== MESSAGE_SYNC_UPDATE) return false;\n const length = readVarUint({ bytes: message, start: syncType.next });\n return length !== null && length.next + length.value === message.length;\n}\n\n/**\n * `message` with the trailer for `attributions` appended, when it is a bare sync update message;\n * any other message (sync step 1 or 2, awareness, one that already ends in a trailer) is returned\n * as it is.\n */\nexport function withUpdateAttributionsTrailer({\n message,\n attributions,\n}: {\n message: Uint8Array;\n attributions: readonly UpdateAttribution[];\n}): Uint8Array {\n if (attributions.length === 0 || !isBareSyncUpdate(message)) return message;\n const trailer: number[] = [];\n writeVarUint({ out: trailer, value: UPDATE_ATTRIBUTIONS_TRAILER_V1 });\n writeVarUint({ out: trailer, value: attributions.length });\n const utf8 = new TextEncoder();\n for (const { k, v } of attributions) {\n for (const text of [k, v]) {\n const bytes = utf8.encode(text);\n writeVarUint({ out: trailer, value: bytes.length });\n for (const byte of bytes) trailer.push(byte);\n }\n }\n const out = new Uint8Array(message.length + trailer.length);\n out.set(message, 0);\n out.set(trailer, message.length);\n return out;\n}\n\n/** What {@link attributingWebSocket} needs of a socket: the `send` y-websocket writes frames with. */\nexport interface SendingSocket {\n send(data: string | ArrayBufferLike | Blob | ArrayBufferView): void;\n}\n\n/**\n * `Base` with `send` appending the trailer of `attributionsFor()` to each sync update it sends.\n * A `Proxy` keeps the constructor's statics and type, so it drops in as y-websocket's\n * `WebSocketPolyfill`.\n */\nexport function attributingWebSocket<TBase extends new (...args: never[]) => SendingSocket>({\n Base,\n attributionsFor,\n}: {\n Base: TBase;\n attributionsFor: () => readonly UpdateAttribution[] | undefined;\n}): TBase {\n // No WebSocket where the provider is built (server render, a test without one): nothing to wrap,\n // and the transport fails at connect as it always did.\n if (typeof Base !== 'function') return Base;\n return new Proxy(Base, {\n construct(target, args) {\n const socket: SendingSocket = Reflect.construct(target, args);\n const send = socket.send.bind(socket);\n socket.send = (data) => {\n const attributions = attributionsFor();\n send(\n attributions && data instanceof Uint8Array\n ? withUpdateAttributionsTrailer({ message: data, attributions })\n : data\n );\n };\n return socket;\n },\n });\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"update-attributions.cjs","sources":["../../../../keryx-http/src/update-attributions.ts"],"sourcesContent":["/**\n * Attributions one Keryx socket update carries of its own.\n *\n * A Keryx connection's attributions are fixed when its socket opens, so every update a client\n * sends is attributed to the person whose client it is. A write the client makes on its own (the\n * sheet leader re-issuing `=AOP` formulas or re-pushing `=IMPORTRANGE` values, a restored result,\n * an editor binding re-keying what an older binding stored) read as that person's edit and woke\n * \"people only\" subscriptions. Keryx reads a trailer after the update in a sync update message\n * (`readUpdateAttributions` in `patches/@y%2Fhub@0.10.1.patch`): `varUint(1)`, `varUint(n)`, then\n * `n` pairs of `varString` key and value. It accepts only `origin: background`, which it counts as\n * system work (`actorOf` in `keryx/src/compaction-activity.ts`), and the pairs that name a Quick\n * Edit step (`@athenaintel/keryx-http/quick-edit`: `session_id`/`run_kind` on the apply,\n * `quick_edit_undo`/`quick_edit_redo` with `quick_edit_op` on an undo or redo), each value in\n * exactly its shape. A Keryx without that hunk reads the update and ignores the bytes after it, so\n * a trailer never costs an update; a pair it does not allow is dropped and the rest kept.\n *\n * A writer marks its writes by making them inside {@link runWithDocUpdateAttributions} for the\n * doc; the transport's socket ({@link attributingWebSocket}) appends the trailer to each sync\n * update it sends while that scope is open. Both Keryx transports wrap their sockets with it:\n * Olympus's rooms (`KeryxYjsProviderV14`) and `@athenaintel/react/collab`'s `connectGenericDoc`\n * (Midas, the phone's editor page). y-websocket sends a local update from inside the doc's\n * `update` event, which fires inside the write itself, so the trailer always rides on the update\n * the scope wrote. A write made while the socket is closed is not sent then: it reaches Keryx in\n * the reconnect's sync step 2, which can carry no trailer. A transport that hands its socket the\n * room's {@link UnconfirmedAttributedWrites} (`@athenaintel/keryx-http/unconfirmed-update-attributions`)\n * sends each such write ahead of that sync step 2 as its own marked update; without one it stays\n * the person's.\n */\n\n/** One custom attribution, as Keryx stores it on the items an update wrote. */\nexport interface UpdateAttribution {\n k: string;\n v: string;\n}\n\n/** The pair a housekeeping write carries; Agora and Keryx spell it the same way. */\nexport const BACKGROUND_UPDATE_ATTRIBUTIONS: readonly UpdateAttribution[] = [\n { k: 'origin', v: 'background' },\n];\n\n/** The trailer format Keryx reads (`updateAttributionsTrailerV1`). */\nexport const UPDATE_ATTRIBUTIONS_TRAILER_V1 = 1;\n\n/** y-protocols: a sync message, and its update sub-type. */\nconst MESSAGE_SYNC = 0;\nconst MESSAGE_SYNC_UPDATE = 2;\n\n/**\n * The open scopes live on `globalThis`, not in module scope, because two copies of this module can\n * run in one page: `@athenaintel/react` bundles it into its dist (this package is private, so the\n * published SDK must inline it) while a host may import it from this package. A scope a writer\n * opens through one copy must be the one the transport's socket reads through the other.\n */\nconst DOC_SCOPES = Symbol.for('@athenaintel/keryx-http/update-attribution-scopes');\n\ntype DocScopesHost = typeof globalThis & {\n [DOC_SCOPES]?: WeakMap<object, readonly UpdateAttribution[]>;\n};\n\n// process-local: browser-only scopes, each keyed by the doc it guards and cleared in the same synchronous call that set it\nfunction docScopes(): WeakMap<object, readonly UpdateAttribution[]> {\n const host = globalThis as DocScopesHost;\n return (host[DOC_SCOPES] ??= new WeakMap());\n}\n\n/**\n * Run `run` with every update it writes into `doc` marked with `attributions`. Synchronous only:\n * the scope ends when `run` returns. Nested scopes restore the outer one.\n */\nexport function runWithDocUpdateAttributions<T>({\n doc,\n attributions,\n run,\n}: {\n doc: object;\n attributions: readonly UpdateAttribution[];\n run: () => T;\n}): T {\n const scopes = docScopes();\n const outer = scopes.get(doc);\n scopes.set(doc, attributions);\n try {\n return run();\n } finally {\n if (outer === undefined) scopes.delete(doc);\n else scopes.set(doc, outer);\n }\n}\n\n/** The attributions the update being written into `doc` right now carries, if any. */\nexport function docUpdateAttributions(doc: object): readonly UpdateAttribution[] | undefined {\n return docScopes().get(doc);\n}\n\n/** The lib0 varUint at `start` in `bytes` and where the next field begins; `null` past the end. */\nexport function readVarUint({\n bytes,\n start,\n}: {\n bytes: Uint8Array;\n start: number;\n}): { value: number; next: number } | null {\n let value = 0;\n let multiplier = 1;\n for (let pos = start; pos < bytes.length; pos += 1) {\n const byte = bytes[pos];\n if (byte === undefined) return null;\n value += (byte & 0x7f) * multiplier;\n if (byte < 0x80) return { value, next: pos + 1 };\n multiplier *= 0x80;\n if (multiplier > Number.MAX_SAFE_INTEGER) return null;\n }\n return null;\n}\n\n/** Append `value` to `out` as a lib0 varUint. */\nexport function writeVarUint({ out, value }: { out: number[]; value: number }): void {\n let rest = value;\n while (rest > 0x7f) {\n out.push(0x80 | (rest % 0x80));\n rest = Math.floor(rest / 0x80);\n }\n out.push(rest);\n}\n\n/** Exactly one sync update message, with nothing after the update. */\nfunction isBareSyncUpdate(message: Uint8Array): boolean {\n const type = readVarUint({ bytes: message, start: 0 });\n if (type?.value !== MESSAGE_SYNC) return false;\n const syncType = readVarUint({ bytes: message, start: type.next });\n if (syncType?.value !== MESSAGE_SYNC_UPDATE) return false;\n const length = readVarUint({ bytes: message, start: syncType.next });\n return length !== null && length.next + length.value === message.length;\n}\n\n/**\n * `message` with the trailer for `attributions` appended, when it is a bare sync update message;\n * any other message (sync step 1 or 2, awareness, one that already ends in a trailer) is returned\n * as it is.\n */\nexport function withUpdateAttributionsTrailer({\n message,\n attributions,\n}: {\n message: Uint8Array;\n attributions: readonly UpdateAttribution[];\n}): Uint8Array {\n if (attributions.length === 0 || !isBareSyncUpdate(message)) return message;\n const trailer: number[] = [];\n writeVarUint({ out: trailer, value: UPDATE_ATTRIBUTIONS_TRAILER_V1 });\n writeVarUint({ out: trailer, value: attributions.length });\n const utf8 = new TextEncoder();\n for (const { k, v } of attributions) {\n for (const text of [k, v]) {\n const bytes = utf8.encode(text);\n writeVarUint({ out: trailer, value: bytes.length });\n for (const byte of bytes) trailer.push(byte);\n }\n }\n const out = new Uint8Array(message.length + trailer.length);\n out.set(message, 0);\n out.set(trailer, message.length);\n return out;\n}\n\n/** What {@link attributingWebSocket} needs of a socket: the `send` y-websocket writes frames with. */\nexport interface SendingSocket {\n send(data: string | ArrayBufferLike | Blob | ArrayBufferView): void;\n}\n\n/** One socket of a room, as the room's attributed writes Keryx has not acknowledged see it. */\nexport interface AttributedWritesSocket {\n /** Keryx sent `frame` on the socket: what it carries, Keryx holds. */\n received(frame: Uint8Array): void;\n /** The socket sends `frame` (now, or once Keryx has sent what it holds; see the implementation). */\n send(frame: Uint8Array): void;\n}\n\n/**\n * The room's attributed writes Keryx has not acknowledged yet, as its sockets see them\n * (`UnconfirmedUpdateAttributions` in `@athenaintel/keryx-http/unconfirmed-update-attributions`).\n */\nexport interface UnconfirmedAttributedWrites {\n /**\n * A socket opened, sending through `send`. `hears`: the socket hands it every frame Keryx sends\n * (`received`), ahead of the transport.\n */\n connect(params: { send: (frame: Uint8Array) => void; hears: boolean }): AttributedWritesSocket;\n}\n\n/** The bytes of a binary frame a socket received, or `null` for one that is not (a string, a `Blob`). */\nfunction receivedBytes(data: unknown): Uint8Array | null {\n if (data instanceof ArrayBuffer) return new Uint8Array(data);\n if (ArrayBuffer.isView(data))\n return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);\n return null;\n}\n\n/**\n * Hands `onFrame` every binary frame `socket` receives; whether it can. A listener added before\n * y-websocket sets `onmessage` runs ahead of it. A socket without `addEventListener` hands nothing.\n */\nfunction listenToFrames({\n socket,\n onFrame,\n}: {\n socket: SendingSocket;\n onFrame: (frame: Uint8Array) => void;\n}): boolean {\n if (!('addEventListener' in socket) || typeof socket.addEventListener !== 'function') {\n return false;\n }\n socket.addEventListener('message', (event: { data?: unknown }) => {\n const frame = receivedBytes(event.data);\n if (frame) onFrame(frame);\n });\n return true;\n}\n\n/**\n * `Base` with `send` appending the trailer of `attributionsFor()` to each sync update it sends.\n * With the room's `unconfirmed` attributed writes, each socket also tells them every frame Keryx\n * sends and sends its frames through them, so a sync step 2 goes out behind the kept writes it\n * carries. A `Proxy` keeps the constructor's statics and type, so it drops in as y-websocket's\n * `WebSocketPolyfill`.\n */\nexport function attributingWebSocket<TBase extends new (...args: never[]) => SendingSocket>({\n Base,\n attributionsFor,\n unconfirmed,\n}: {\n Base: TBase;\n attributionsFor: () => readonly UpdateAttribution[] | undefined;\n unconfirmed?: UnconfirmedAttributedWrites;\n}): TBase {\n // No WebSocket where the provider is built (server render, a test without one): nothing to wrap,\n // and the transport fails at connect as it always did.\n if (typeof Base !== 'function') return Base;\n return new Proxy(Base, {\n construct(target, args) {\n const socket: SendingSocket = Reflect.construct(target, args);\n const send = socket.send.bind(socket);\n let connection: AttributedWritesSocket | null = null;\n if (unconfirmed) {\n const hears = listenToFrames({ socket, onFrame: (frame) => connection?.received(frame) });\n connection = unconfirmed.connect({ send, hears });\n }\n socket.send = (data) => {\n if (!(data instanceof Uint8Array)) {\n send(data);\n return;\n }\n const attributions = attributionsFor();\n const message = attributions\n ? withUpdateAttributionsTrailer({ message: data, attributions })\n : data;\n if (connection) connection.send(message);\n else send(message);\n };\n return socket;\n },\n });\n}\n"],"names":[],"mappings":";;AAoCO,MAAM,iCAA+D;AAAA,EAC1E,EAAE,GAAG,UAAU,GAAG,aAAA;AACpB;AAGO,MAAM,iCAAiC;AAG9C,MAAM,eAAe;AACrB,MAAM,sBAAsB;AAQ5B,MAAM,aAAa,OAAO,IAAI,mDAAmD;AAOjF,SAAS,YAA2D;AAClE,QAAM,OAAO;AACb,SAAQ,wCAAqB,oBAAI,QAAA;AACnC;AAMO,SAAS,6BAAgC;AAAA,EAC9C;AAAA,EACA;AAAA,EACA;AACF,GAIM;AACJ,QAAM,SAAS,UAAA;AACf,QAAM,QAAQ,OAAO,IAAI,GAAG;AAC5B,SAAO,IAAI,KAAK,YAAY;AAC5B,MAAI;AACF,WAAO,IAAA;AAAA,EACT,UAAA;AACE,QAAI,UAAU,OAAW,QAAO,OAAO,GAAG;AAAA,QACrC,QAAO,IAAI,KAAK,KAAK;AAAA,EAC5B;AACF;AAGO,SAAS,sBAAsB,KAAuD;AAC3F,SAAO,UAAA,EAAY,IAAI,GAAG;AAC5B;AAGO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AACF,GAG2C;AACzC,MAAI,QAAQ;AACZ,MAAI,aAAa;AACjB,WAAS,MAAM,OAAO,MAAM,MAAM,QAAQ,OAAO,GAAG;AAClD,UAAM,OAAO,MAAM,GAAG;AACtB,QAAI,SAAS,OAAW,QAAO;AAC/B,cAAU,OAAO,OAAQ;AACzB,QAAI,OAAO,IAAM,QAAO,EAAE,OAAO,MAAM,MAAM,EAAA;AAC7C,kBAAc;AACd,QAAI,aAAa,OAAO,iBAAkB,QAAO;AAAA,EACnD;AACA,SAAO;AACT;AAGO,SAAS,aAAa,EAAE,KAAK,SAAiD;AACnF,MAAI,OAAO;AACX,SAAO,OAAO,KAAM;AAClB,QAAI,KAAK,MAAQ,OAAO,GAAK;AAC7B,WAAO,KAAK,MAAM,OAAO,GAAI;AAAA,EAC/B;AACA,MAAI,KAAK,IAAI;AACf;AAGA,SAAS,iBAAiB,SAA8B;AACtD,QAAM,OAAO,YAAY,EAAE,OAAO,SAAS,OAAO,GAAG;AACrD,OAAI,6BAAM,WAAU,aAAc,QAAO;AACzC,QAAM,WAAW,YAAY,EAAE,OAAO,SAAS,OAAO,KAAK,MAAM;AACjE,OAAI,qCAAU,WAAU,oBAAqB,QAAO;AACpD,QAAM,SAAS,YAAY,EAAE,OAAO,SAAS,OAAO,SAAS,MAAM;AACnE,SAAO,WAAW,QAAQ,OAAO,OAAO,OAAO,UAAU,QAAQ;AACnE;AAOO,SAAS,8BAA8B;AAAA,EAC5C;AAAA,EACA;AACF,GAGe;AACb,MAAI,aAAa,WAAW,KAAK,CAAC,iBAAiB,OAAO,EAAG,QAAO;AACpE,QAAM,UAAoB,CAAA;AAC1B,eAAa,EAAE,KAAK,SAAS,OAAO,gCAAgC;AACpE,eAAa,EAAE,KAAK,SAAS,OAAO,aAAa,QAAQ;AACzD,QAAM,OAAO,IAAI,YAAA;AACjB,aAAW,EAAE,GAAG,EAAA,KAAO,cAAc;AACnC,eAAW,QAAQ,CAAC,GAAG,CAAC,GAAG;AACzB,YAAM,QAAQ,KAAK,OAAO,IAAI;AAC9B,mBAAa,EAAE,KAAK,SAAS,OAAO,MAAM,QAAQ;AAClD,iBAAW,QAAQ,MAAO,SAAQ,KAAK,IAAI;AAAA,IAC7C;AAAA,EACF;AACA,QAAM,MAAM,IAAI,WAAW,QAAQ,SAAS,QAAQ,MAAM;AAC1D,MAAI,IAAI,SAAS,CAAC;AAClB,MAAI,IAAI,SAAS,QAAQ,MAAM;AAC/B,SAAO;AACT;AA4BA,SAAS,cAAc,MAAkC;AACvD,MAAI,gBAAgB,YAAa,QAAO,IAAI,WAAW,IAAI;AAC3D,MAAI,YAAY,OAAO,IAAI;AACzB,WAAO,IAAI,WAAW,KAAK,QAAQ,KAAK,YAAY,KAAK,UAAU;AACrE,SAAO;AACT;AAMA,SAAS,eAAe;AAAA,EACtB;AAAA,EACA;AACF,GAGY;AACV,MAAI,EAAE,sBAAsB,WAAW,OAAO,OAAO,qBAAqB,YAAY;AACpF,WAAO;AAAA,EACT;AACA,SAAO,iBAAiB,WAAW,CAAC,UAA8B;AAChE,UAAM,QAAQ,cAAc,MAAM,IAAI;AACtC,QAAI,eAAe,KAAK;AAAA,EAC1B,CAAC;AACD,SAAO;AACT;AASO,SAAS,qBAA4E;AAAA,EAC1F;AAAA,EACA;AAAA,EACA;AACF,GAIU;AAGR,MAAI,OAAO,SAAS,WAAY,QAAO;AACvC,SAAO,IAAI,MAAM,MAAM;AAAA,IACrB,UAAU,QAAQ,MAAM;AACtB,YAAM,SAAwB,QAAQ,UAAU,QAAQ,IAAI;AAC5D,YAAM,OAAO,OAAO,KAAK,KAAK,MAAM;AACpC,UAAI,aAA4C;AAChD,UAAI,aAAa;AACf,cAAM,QAAQ,eAAe,EAAE,QAAQ,SAAS,CAAC,UAAU,yCAAY,SAAS,QAAQ;AACxF,qBAAa,YAAY,QAAQ,EAAE,MAAM,OAAO;AAAA,MAClD;AACA,aAAO,OAAO,CAAC,SAAS;AACtB,YAAI,EAAE,gBAAgB,aAAa;AACjC,eAAK,IAAI;AACT;AAAA,QACF;AACA,cAAM,eAAe,gBAAA;AACrB,cAAM,UAAU,eACZ,8BAA8B,EAAE,SAAS,MAAM,aAAA,CAAc,IAC7D;AACJ,YAAI,WAAY,YAAW,KAAK,OAAO;AAAA,kBAC7B,OAAO;AAAA,MACnB;AACA,aAAO;AAAA,IACT;AAAA,EAAA,CACD;AACH;;;;;;;;;"}
|
|
@@ -20,8 +20,11 @@
|
|
|
20
20
|
* Olympus's rooms (`KeryxYjsProviderV14`) and `@athenaintel/react/collab`'s `connectGenericDoc`
|
|
21
21
|
* (Midas, the phone's editor page). y-websocket sends a local update from inside the doc's
|
|
22
22
|
* `update` event, which fires inside the write itself, so the trailer always rides on the update
|
|
23
|
-
* the scope wrote.
|
|
24
|
-
*
|
|
23
|
+
* the scope wrote. A write made while the socket is closed is not sent then: it reaches Keryx in
|
|
24
|
+
* the reconnect's sync step 2, which can carry no trailer. A transport that hands its socket the
|
|
25
|
+
* room's {@link UnconfirmedAttributedWrites} (`@athenaintel/keryx-http/unconfirmed-update-attributions`)
|
|
26
|
+
* sends each such write ahead of that sync step 2 as its own marked update; without one it stays
|
|
27
|
+
* the person's.
|
|
25
28
|
*/
|
|
26
29
|
/** One custom attribution, as Keryx stores it on the items an update wrote. */
|
|
27
30
|
export interface UpdateAttribution {
|
|
@@ -51,6 +54,11 @@ export declare function readVarUint({ bytes, start, }: {
|
|
|
51
54
|
value: number;
|
|
52
55
|
next: number;
|
|
53
56
|
} | null;
|
|
57
|
+
/** Append `value` to `out` as a lib0 varUint. */
|
|
58
|
+
export declare function writeVarUint({ out, value }: {
|
|
59
|
+
out: number[];
|
|
60
|
+
value: number;
|
|
61
|
+
}): void;
|
|
54
62
|
/**
|
|
55
63
|
* `message` with the trailer for `attributions` appended, when it is a bare sync update message;
|
|
56
64
|
* any other message (sync step 1 or 2, awareness, one that already ends in a trailer) is returned
|
|
@@ -64,12 +72,36 @@ export declare function withUpdateAttributionsTrailer({ message, attributions, }
|
|
|
64
72
|
export interface SendingSocket {
|
|
65
73
|
send(data: string | ArrayBufferLike | Blob | ArrayBufferView): void;
|
|
66
74
|
}
|
|
75
|
+
/** One socket of a room, as the room's attributed writes Keryx has not acknowledged see it. */
|
|
76
|
+
export interface AttributedWritesSocket {
|
|
77
|
+
/** Keryx sent `frame` on the socket: what it carries, Keryx holds. */
|
|
78
|
+
received(frame: Uint8Array): void;
|
|
79
|
+
/** The socket sends `frame` (now, or once Keryx has sent what it holds; see the implementation). */
|
|
80
|
+
send(frame: Uint8Array): void;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The room's attributed writes Keryx has not acknowledged yet, as its sockets see them
|
|
84
|
+
* (`UnconfirmedUpdateAttributions` in `@athenaintel/keryx-http/unconfirmed-update-attributions`).
|
|
85
|
+
*/
|
|
86
|
+
export interface UnconfirmedAttributedWrites {
|
|
87
|
+
/**
|
|
88
|
+
* A socket opened, sending through `send`. `hears`: the socket hands it every frame Keryx sends
|
|
89
|
+
* (`received`), ahead of the transport.
|
|
90
|
+
*/
|
|
91
|
+
connect(params: {
|
|
92
|
+
send: (frame: Uint8Array) => void;
|
|
93
|
+
hears: boolean;
|
|
94
|
+
}): AttributedWritesSocket;
|
|
95
|
+
}
|
|
67
96
|
/**
|
|
68
97
|
* `Base` with `send` appending the trailer of `attributionsFor()` to each sync update it sends.
|
|
69
|
-
*
|
|
98
|
+
* With the room's `unconfirmed` attributed writes, each socket also tells them every frame Keryx
|
|
99
|
+
* sends and sends its frames through them, so a sync step 2 goes out behind the kept writes it
|
|
100
|
+
* carries. A `Proxy` keeps the constructor's statics and type, so it drops in as y-websocket's
|
|
70
101
|
* `WebSocketPolyfill`.
|
|
71
102
|
*/
|
|
72
|
-
export declare function attributingWebSocket<TBase extends new (...args: never[]) => SendingSocket>({ Base, attributionsFor, }: {
|
|
103
|
+
export declare function attributingWebSocket<TBase extends new (...args: never[]) => SendingSocket>({ Base, attributionsFor, unconfirmed, }: {
|
|
73
104
|
Base: TBase;
|
|
74
105
|
attributionsFor: () => readonly UpdateAttribution[] | undefined;
|
|
106
|
+
unconfirmed?: UnconfirmedAttributedWrites;
|
|
75
107
|
}): TBase;
|
|
@@ -80,20 +80,49 @@ function withUpdateAttributionsTrailer({
|
|
|
80
80
|
out.set(trailer, message.length);
|
|
81
81
|
return out;
|
|
82
82
|
}
|
|
83
|
+
function receivedBytes(data) {
|
|
84
|
+
if (data instanceof ArrayBuffer) return new Uint8Array(data);
|
|
85
|
+
if (ArrayBuffer.isView(data))
|
|
86
|
+
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
function listenToFrames({
|
|
90
|
+
socket,
|
|
91
|
+
onFrame
|
|
92
|
+
}) {
|
|
93
|
+
if (!("addEventListener" in socket) || typeof socket.addEventListener !== "function") {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
socket.addEventListener("message", (event) => {
|
|
97
|
+
const frame = receivedBytes(event.data);
|
|
98
|
+
if (frame) onFrame(frame);
|
|
99
|
+
});
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
83
102
|
function attributingWebSocket({
|
|
84
103
|
Base,
|
|
85
|
-
attributionsFor
|
|
104
|
+
attributionsFor,
|
|
105
|
+
unconfirmed
|
|
86
106
|
}) {
|
|
87
107
|
if (typeof Base !== "function") return Base;
|
|
88
108
|
return new Proxy(Base, {
|
|
89
109
|
construct(target, args) {
|
|
90
110
|
const socket = Reflect.construct(target, args);
|
|
91
111
|
const send = socket.send.bind(socket);
|
|
112
|
+
let connection = null;
|
|
113
|
+
if (unconfirmed) {
|
|
114
|
+
const hears = listenToFrames({ socket, onFrame: (frame) => connection == null ? void 0 : connection.received(frame) });
|
|
115
|
+
connection = unconfirmed.connect({ send, hears });
|
|
116
|
+
}
|
|
92
117
|
socket.send = (data) => {
|
|
118
|
+
if (!(data instanceof Uint8Array)) {
|
|
119
|
+
send(data);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
93
122
|
const attributions = attributionsFor();
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
);
|
|
123
|
+
const message = attributions ? withUpdateAttributionsTrailer({ message: data, attributions }) : data;
|
|
124
|
+
if (connection) connection.send(message);
|
|
125
|
+
else send(message);
|
|
97
126
|
};
|
|
98
127
|
return socket;
|
|
99
128
|
}
|
|
@@ -106,6 +135,7 @@ export {
|
|
|
106
135
|
docUpdateAttributions,
|
|
107
136
|
readVarUint,
|
|
108
137
|
runWithDocUpdateAttributions,
|
|
109
|
-
withUpdateAttributionsTrailer
|
|
138
|
+
withUpdateAttributionsTrailer,
|
|
139
|
+
writeVarUint
|
|
110
140
|
};
|
|
111
141
|
//# sourceMappingURL=update-attributions.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update-attributions.js","sources":["../../../../keryx-http/src/update-attributions.ts"],"sourcesContent":["/**\n * Attributions one Keryx socket update carries of its own.\n *\n * A Keryx connection's attributions are fixed when its socket opens, so every update a client\n * sends is attributed to the person whose client it is. A write the client makes on its own (the\n * sheet leader re-issuing `=AOP` formulas or re-pushing `=IMPORTRANGE` values, a restored result,\n * an editor binding re-keying what an older binding stored) read as that person's edit and woke\n * \"people only\" subscriptions. Keryx reads a trailer after the update in a sync update message\n * (`readUpdateAttributions` in `patches/@y%2Fhub@0.10.1.patch`): `varUint(1)`, `varUint(n)`, then\n * `n` pairs of `varString` key and value. It accepts only `origin: background`, which it counts as\n * system work (`actorOf` in `keryx/src/compaction-activity.ts`), and the pairs that name a Quick\n * Edit step (`@athenaintel/keryx-http/quick-edit`: `session_id`/`run_kind` on the apply,\n * `quick_edit_undo`/`quick_edit_redo` with `quick_edit_op` on an undo or redo), each value in\n * exactly its shape. A Keryx without that hunk reads the update and ignores the bytes after it, so\n * a trailer never costs an update; a pair it does not allow is dropped and the rest kept.\n *\n * A writer marks its writes by making them inside {@link runWithDocUpdateAttributions} for the\n * doc; the transport's socket ({@link attributingWebSocket}) appends the trailer to each sync\n * update it sends while that scope is open. Both Keryx transports wrap their sockets with it:\n * Olympus's rooms (`KeryxYjsProviderV14`) and `@athenaintel/react/collab`'s `connectGenericDoc`\n * (Midas, the phone's editor page). y-websocket sends a local update from inside the doc's\n * `update` event, which fires inside the write itself, so the trailer always rides on the update\n * the scope wrote. An update sent later (a reconnect's sync step 2 after the socket was down)\n * carries none and stays the person's, as before.\n */\n\n/** One custom attribution, as Keryx stores it on the items an update wrote. */\nexport interface UpdateAttribution {\n k: string;\n v: string;\n}\n\n/** The pair a housekeeping write carries; Agora and Keryx spell it the same way. */\nexport const BACKGROUND_UPDATE_ATTRIBUTIONS: readonly UpdateAttribution[] = [\n { k: 'origin', v: 'background' },\n];\n\n/** The trailer format Keryx reads (`updateAttributionsTrailerV1`). */\nexport const UPDATE_ATTRIBUTIONS_TRAILER_V1 = 1;\n\n/** y-protocols: a sync message, and its update sub-type. */\nconst MESSAGE_SYNC = 0;\nconst MESSAGE_SYNC_UPDATE = 2;\n\n/**\n * The open scopes live on `globalThis`, not in module scope, because two copies of this module can\n * run in one page: `@athenaintel/react` bundles it into its dist (this package is private, so the\n * published SDK must inline it) while a host may import it from this package. A scope a writer\n * opens through one copy must be the one the transport's socket reads through the other.\n */\nconst DOC_SCOPES = Symbol.for('@athenaintel/keryx-http/update-attribution-scopes');\n\ntype DocScopesHost = typeof globalThis & {\n [DOC_SCOPES]?: WeakMap<object, readonly UpdateAttribution[]>;\n};\n\n// process-local: browser-only scopes, each keyed by the doc it guards and cleared in the same synchronous call that set it\nfunction docScopes(): WeakMap<object, readonly UpdateAttribution[]> {\n const host = globalThis as DocScopesHost;\n return (host[DOC_SCOPES] ??= new WeakMap());\n}\n\n/**\n * Run `run` with every update it writes into `doc` marked with `attributions`. Synchronous only:\n * the scope ends when `run` returns. Nested scopes restore the outer one.\n */\nexport function runWithDocUpdateAttributions<T>({\n doc,\n attributions,\n run,\n}: {\n doc: object;\n attributions: readonly UpdateAttribution[];\n run: () => T;\n}): T {\n const scopes = docScopes();\n const outer = scopes.get(doc);\n scopes.set(doc, attributions);\n try {\n return run();\n } finally {\n if (outer === undefined) scopes.delete(doc);\n else scopes.set(doc, outer);\n }\n}\n\n/** The attributions the update being written into `doc` right now carries, if any. */\nexport function docUpdateAttributions(doc: object): readonly UpdateAttribution[] | undefined {\n return docScopes().get(doc);\n}\n\n/** The lib0 varUint at `start` in `bytes` and where the next field begins; `null` past the end. */\nexport function readVarUint({\n bytes,\n start,\n}: {\n bytes: Uint8Array;\n start: number;\n}): { value: number; next: number } | null {\n let value = 0;\n let multiplier = 1;\n for (let pos = start; pos < bytes.length; pos += 1) {\n const byte = bytes[pos];\n if (byte === undefined) return null;\n value += (byte & 0x7f) * multiplier;\n if (byte < 0x80) return { value, next: pos + 1 };\n multiplier *= 0x80;\n if (multiplier > Number.MAX_SAFE_INTEGER) return null;\n }\n return null;\n}\n\n/** Append `value` to `out` as a lib0 varUint. */\nfunction writeVarUint({ out, value }: { out: number[]; value: number }): void {\n let rest = value;\n while (rest > 0x7f) {\n out.push(0x80 | (rest % 0x80));\n rest = Math.floor(rest / 0x80);\n }\n out.push(rest);\n}\n\n/** Exactly one sync update message, with nothing after the update. */\nfunction isBareSyncUpdate(message: Uint8Array): boolean {\n const type = readVarUint({ bytes: message, start: 0 });\n if (type?.value !== MESSAGE_SYNC) return false;\n const syncType = readVarUint({ bytes: message, start: type.next });\n if (syncType?.value !== MESSAGE_SYNC_UPDATE) return false;\n const length = readVarUint({ bytes: message, start: syncType.next });\n return length !== null && length.next + length.value === message.length;\n}\n\n/**\n * `message` with the trailer for `attributions` appended, when it is a bare sync update message;\n * any other message (sync step 1 or 2, awareness, one that already ends in a trailer) is returned\n * as it is.\n */\nexport function withUpdateAttributionsTrailer({\n message,\n attributions,\n}: {\n message: Uint8Array;\n attributions: readonly UpdateAttribution[];\n}): Uint8Array {\n if (attributions.length === 0 || !isBareSyncUpdate(message)) return message;\n const trailer: number[] = [];\n writeVarUint({ out: trailer, value: UPDATE_ATTRIBUTIONS_TRAILER_V1 });\n writeVarUint({ out: trailer, value: attributions.length });\n const utf8 = new TextEncoder();\n for (const { k, v } of attributions) {\n for (const text of [k, v]) {\n const bytes = utf8.encode(text);\n writeVarUint({ out: trailer, value: bytes.length });\n for (const byte of bytes) trailer.push(byte);\n }\n }\n const out = new Uint8Array(message.length + trailer.length);\n out.set(message, 0);\n out.set(trailer, message.length);\n return out;\n}\n\n/** What {@link attributingWebSocket} needs of a socket: the `send` y-websocket writes frames with. */\nexport interface SendingSocket {\n send(data: string | ArrayBufferLike | Blob | ArrayBufferView): void;\n}\n\n/**\n * `Base` with `send` appending the trailer of `attributionsFor()` to each sync update it sends.\n * A `Proxy` keeps the constructor's statics and type, so it drops in as y-websocket's\n * `WebSocketPolyfill`.\n */\nexport function attributingWebSocket<TBase extends new (...args: never[]) => SendingSocket>({\n Base,\n attributionsFor,\n}: {\n Base: TBase;\n attributionsFor: () => readonly UpdateAttribution[] | undefined;\n}): TBase {\n // No WebSocket where the provider is built (server render, a test without one): nothing to wrap,\n // and the transport fails at connect as it always did.\n if (typeof Base !== 'function') return Base;\n return new Proxy(Base, {\n construct(target, args) {\n const socket: SendingSocket = Reflect.construct(target, args);\n const send = socket.send.bind(socket);\n socket.send = (data) => {\n const attributions = attributionsFor();\n send(\n attributions && data instanceof Uint8Array\n ? withUpdateAttributionsTrailer({ message: data, attributions })\n : data\n );\n };\n return socket;\n },\n });\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"update-attributions.js","sources":["../../../../keryx-http/src/update-attributions.ts"],"sourcesContent":["/**\n * Attributions one Keryx socket update carries of its own.\n *\n * A Keryx connection's attributions are fixed when its socket opens, so every update a client\n * sends is attributed to the person whose client it is. A write the client makes on its own (the\n * sheet leader re-issuing `=AOP` formulas or re-pushing `=IMPORTRANGE` values, a restored result,\n * an editor binding re-keying what an older binding stored) read as that person's edit and woke\n * \"people only\" subscriptions. Keryx reads a trailer after the update in a sync update message\n * (`readUpdateAttributions` in `patches/@y%2Fhub@0.10.1.patch`): `varUint(1)`, `varUint(n)`, then\n * `n` pairs of `varString` key and value. It accepts only `origin: background`, which it counts as\n * system work (`actorOf` in `keryx/src/compaction-activity.ts`), and the pairs that name a Quick\n * Edit step (`@athenaintel/keryx-http/quick-edit`: `session_id`/`run_kind` on the apply,\n * `quick_edit_undo`/`quick_edit_redo` with `quick_edit_op` on an undo or redo), each value in\n * exactly its shape. A Keryx without that hunk reads the update and ignores the bytes after it, so\n * a trailer never costs an update; a pair it does not allow is dropped and the rest kept.\n *\n * A writer marks its writes by making them inside {@link runWithDocUpdateAttributions} for the\n * doc; the transport's socket ({@link attributingWebSocket}) appends the trailer to each sync\n * update it sends while that scope is open. Both Keryx transports wrap their sockets with it:\n * Olympus's rooms (`KeryxYjsProviderV14`) and `@athenaintel/react/collab`'s `connectGenericDoc`\n * (Midas, the phone's editor page). y-websocket sends a local update from inside the doc's\n * `update` event, which fires inside the write itself, so the trailer always rides on the update\n * the scope wrote. A write made while the socket is closed is not sent then: it reaches Keryx in\n * the reconnect's sync step 2, which can carry no trailer. A transport that hands its socket the\n * room's {@link UnconfirmedAttributedWrites} (`@athenaintel/keryx-http/unconfirmed-update-attributions`)\n * sends each such write ahead of that sync step 2 as its own marked update; without one it stays\n * the person's.\n */\n\n/** One custom attribution, as Keryx stores it on the items an update wrote. */\nexport interface UpdateAttribution {\n k: string;\n v: string;\n}\n\n/** The pair a housekeeping write carries; Agora and Keryx spell it the same way. */\nexport const BACKGROUND_UPDATE_ATTRIBUTIONS: readonly UpdateAttribution[] = [\n { k: 'origin', v: 'background' },\n];\n\n/** The trailer format Keryx reads (`updateAttributionsTrailerV1`). */\nexport const UPDATE_ATTRIBUTIONS_TRAILER_V1 = 1;\n\n/** y-protocols: a sync message, and its update sub-type. */\nconst MESSAGE_SYNC = 0;\nconst MESSAGE_SYNC_UPDATE = 2;\n\n/**\n * The open scopes live on `globalThis`, not in module scope, because two copies of this module can\n * run in one page: `@athenaintel/react` bundles it into its dist (this package is private, so the\n * published SDK must inline it) while a host may import it from this package. A scope a writer\n * opens through one copy must be the one the transport's socket reads through the other.\n */\nconst DOC_SCOPES = Symbol.for('@athenaintel/keryx-http/update-attribution-scopes');\n\ntype DocScopesHost = typeof globalThis & {\n [DOC_SCOPES]?: WeakMap<object, readonly UpdateAttribution[]>;\n};\n\n// process-local: browser-only scopes, each keyed by the doc it guards and cleared in the same synchronous call that set it\nfunction docScopes(): WeakMap<object, readonly UpdateAttribution[]> {\n const host = globalThis as DocScopesHost;\n return (host[DOC_SCOPES] ??= new WeakMap());\n}\n\n/**\n * Run `run` with every update it writes into `doc` marked with `attributions`. Synchronous only:\n * the scope ends when `run` returns. Nested scopes restore the outer one.\n */\nexport function runWithDocUpdateAttributions<T>({\n doc,\n attributions,\n run,\n}: {\n doc: object;\n attributions: readonly UpdateAttribution[];\n run: () => T;\n}): T {\n const scopes = docScopes();\n const outer = scopes.get(doc);\n scopes.set(doc, attributions);\n try {\n return run();\n } finally {\n if (outer === undefined) scopes.delete(doc);\n else scopes.set(doc, outer);\n }\n}\n\n/** The attributions the update being written into `doc` right now carries, if any. */\nexport function docUpdateAttributions(doc: object): readonly UpdateAttribution[] | undefined {\n return docScopes().get(doc);\n}\n\n/** The lib0 varUint at `start` in `bytes` and where the next field begins; `null` past the end. */\nexport function readVarUint({\n bytes,\n start,\n}: {\n bytes: Uint8Array;\n start: number;\n}): { value: number; next: number } | null {\n let value = 0;\n let multiplier = 1;\n for (let pos = start; pos < bytes.length; pos += 1) {\n const byte = bytes[pos];\n if (byte === undefined) return null;\n value += (byte & 0x7f) * multiplier;\n if (byte < 0x80) return { value, next: pos + 1 };\n multiplier *= 0x80;\n if (multiplier > Number.MAX_SAFE_INTEGER) return null;\n }\n return null;\n}\n\n/** Append `value` to `out` as a lib0 varUint. */\nexport function writeVarUint({ out, value }: { out: number[]; value: number }): void {\n let rest = value;\n while (rest > 0x7f) {\n out.push(0x80 | (rest % 0x80));\n rest = Math.floor(rest / 0x80);\n }\n out.push(rest);\n}\n\n/** Exactly one sync update message, with nothing after the update. */\nfunction isBareSyncUpdate(message: Uint8Array): boolean {\n const type = readVarUint({ bytes: message, start: 0 });\n if (type?.value !== MESSAGE_SYNC) return false;\n const syncType = readVarUint({ bytes: message, start: type.next });\n if (syncType?.value !== MESSAGE_SYNC_UPDATE) return false;\n const length = readVarUint({ bytes: message, start: syncType.next });\n return length !== null && length.next + length.value === message.length;\n}\n\n/**\n * `message` with the trailer for `attributions` appended, when it is a bare sync update message;\n * any other message (sync step 1 or 2, awareness, one that already ends in a trailer) is returned\n * as it is.\n */\nexport function withUpdateAttributionsTrailer({\n message,\n attributions,\n}: {\n message: Uint8Array;\n attributions: readonly UpdateAttribution[];\n}): Uint8Array {\n if (attributions.length === 0 || !isBareSyncUpdate(message)) return message;\n const trailer: number[] = [];\n writeVarUint({ out: trailer, value: UPDATE_ATTRIBUTIONS_TRAILER_V1 });\n writeVarUint({ out: trailer, value: attributions.length });\n const utf8 = new TextEncoder();\n for (const { k, v } of attributions) {\n for (const text of [k, v]) {\n const bytes = utf8.encode(text);\n writeVarUint({ out: trailer, value: bytes.length });\n for (const byte of bytes) trailer.push(byte);\n }\n }\n const out = new Uint8Array(message.length + trailer.length);\n out.set(message, 0);\n out.set(trailer, message.length);\n return out;\n}\n\n/** What {@link attributingWebSocket} needs of a socket: the `send` y-websocket writes frames with. */\nexport interface SendingSocket {\n send(data: string | ArrayBufferLike | Blob | ArrayBufferView): void;\n}\n\n/** One socket of a room, as the room's attributed writes Keryx has not acknowledged see it. */\nexport interface AttributedWritesSocket {\n /** Keryx sent `frame` on the socket: what it carries, Keryx holds. */\n received(frame: Uint8Array): void;\n /** The socket sends `frame` (now, or once Keryx has sent what it holds; see the implementation). */\n send(frame: Uint8Array): void;\n}\n\n/**\n * The room's attributed writes Keryx has not acknowledged yet, as its sockets see them\n * (`UnconfirmedUpdateAttributions` in `@athenaintel/keryx-http/unconfirmed-update-attributions`).\n */\nexport interface UnconfirmedAttributedWrites {\n /**\n * A socket opened, sending through `send`. `hears`: the socket hands it every frame Keryx sends\n * (`received`), ahead of the transport.\n */\n connect(params: { send: (frame: Uint8Array) => void; hears: boolean }): AttributedWritesSocket;\n}\n\n/** The bytes of a binary frame a socket received, or `null` for one that is not (a string, a `Blob`). */\nfunction receivedBytes(data: unknown): Uint8Array | null {\n if (data instanceof ArrayBuffer) return new Uint8Array(data);\n if (ArrayBuffer.isView(data))\n return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);\n return null;\n}\n\n/**\n * Hands `onFrame` every binary frame `socket` receives; whether it can. A listener added before\n * y-websocket sets `onmessage` runs ahead of it. A socket without `addEventListener` hands nothing.\n */\nfunction listenToFrames({\n socket,\n onFrame,\n}: {\n socket: SendingSocket;\n onFrame: (frame: Uint8Array) => void;\n}): boolean {\n if (!('addEventListener' in socket) || typeof socket.addEventListener !== 'function') {\n return false;\n }\n socket.addEventListener('message', (event: { data?: unknown }) => {\n const frame = receivedBytes(event.data);\n if (frame) onFrame(frame);\n });\n return true;\n}\n\n/**\n * `Base` with `send` appending the trailer of `attributionsFor()` to each sync update it sends.\n * With the room's `unconfirmed` attributed writes, each socket also tells them every frame Keryx\n * sends and sends its frames through them, so a sync step 2 goes out behind the kept writes it\n * carries. A `Proxy` keeps the constructor's statics and type, so it drops in as y-websocket's\n * `WebSocketPolyfill`.\n */\nexport function attributingWebSocket<TBase extends new (...args: never[]) => SendingSocket>({\n Base,\n attributionsFor,\n unconfirmed,\n}: {\n Base: TBase;\n attributionsFor: () => readonly UpdateAttribution[] | undefined;\n unconfirmed?: UnconfirmedAttributedWrites;\n}): TBase {\n // No WebSocket where the provider is built (server render, a test without one): nothing to wrap,\n // and the transport fails at connect as it always did.\n if (typeof Base !== 'function') return Base;\n return new Proxy(Base, {\n construct(target, args) {\n const socket: SendingSocket = Reflect.construct(target, args);\n const send = socket.send.bind(socket);\n let connection: AttributedWritesSocket | null = null;\n if (unconfirmed) {\n const hears = listenToFrames({ socket, onFrame: (frame) => connection?.received(frame) });\n connection = unconfirmed.connect({ send, hears });\n }\n socket.send = (data) => {\n if (!(data instanceof Uint8Array)) {\n send(data);\n return;\n }\n const attributions = attributionsFor();\n const message = attributions\n ? withUpdateAttributionsTrailer({ message: data, attributions })\n : data;\n if (connection) connection.send(message);\n else send(message);\n };\n return socket;\n },\n });\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -470,6 +470,7 @@ function AthenaStatewireWithThreadList({
|
|
|
470
470
|
initialThreadId,
|
|
471
471
|
includePullRequests,
|
|
472
472
|
additionalListAppIds,
|
|
473
|
+
listHomeAgentId,
|
|
473
474
|
offlineList,
|
|
474
475
|
...standaloneProps
|
|
475
476
|
}) {
|
|
@@ -483,6 +484,7 @@ function AthenaStatewireWithThreadList({
|
|
|
483
484
|
getToken,
|
|
484
485
|
appId,
|
|
485
486
|
additionalListAppIds,
|
|
487
|
+
homeAgentId: listHomeAgentId,
|
|
486
488
|
initialThreadId,
|
|
487
489
|
includePullRequests,
|
|
488
490
|
offlineList,
|
|
@@ -569,6 +571,7 @@ function AthenaProvider({
|
|
|
569
571
|
enableThreadList = false,
|
|
570
572
|
threadListIncludePullRequests = true,
|
|
571
573
|
threadListAdditionalAppIds,
|
|
574
|
+
threadListAgentHome = false,
|
|
572
575
|
theme,
|
|
573
576
|
linkClicks,
|
|
574
577
|
citationLinks,
|
|
@@ -841,6 +844,7 @@ function AthenaProvider({
|
|
|
841
844
|
initialThreadId: threadIdProp,
|
|
842
845
|
includePullRequests: threadListIncludePullRequests,
|
|
843
846
|
additionalListAppIds: threadListAdditionalAppIds,
|
|
847
|
+
listHomeAgentId: threadListAgentHome && agentAssetId ? agentAssetId : void 0,
|
|
844
848
|
offlineList,
|
|
845
849
|
children
|
|
846
850
|
}
|