@palbase/web 1.1.1 → 1.2.1
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/dist/{analytics-facade-DLH-KivI.d.ts → analytics-facade-C93tr7dA.d.ts} +55 -0
- package/dist/{analytics-facade-CAKBIH_U.d.cts → analytics-facade-Ct3A1zop.d.cts} +55 -0
- package/dist/{chunk-EMQGOKW6.js → chunk-KJXRY4S3.js} +327 -49
- package/dist/chunk-KJXRY4S3.js.map +1 -0
- package/dist/index.cjs +326 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/internal.cjs +326 -48
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +3 -3
- package/dist/internal.d.ts +3 -3
- package/dist/internal.js +1 -1
- package/dist/next/client.cjs +326 -48
- package/dist/next/client.cjs.map +1 -1
- package/dist/next/client.js +1 -1
- package/dist/next/index.cjs +326 -48
- package/dist/next/index.cjs.map +1 -1
- package/dist/next/index.d.cts +2 -2
- package/dist/next/index.d.ts +2 -2
- package/dist/next/index.js +1 -1
- package/dist/{pb-DioxNuEV.d.cts → pb-BlIfgBG-.d.cts} +1 -1
- package/dist/{pb-HegMSSk-.d.ts → pb-BtYdWClg.d.ts} +1 -1
- package/dist/react/index.cjs +1 -1
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +1 -1
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-EMQGOKW6.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1746,6 +1746,18 @@ async function listDevices(rt, userId) {
|
|
|
1746
1746
|
}
|
|
1747
1747
|
|
|
1748
1748
|
// src/messaging/group-messaging.ts
|
|
1749
|
+
function encodeReaction(args) {
|
|
1750
|
+
return encodeUtf8(
|
|
1751
|
+
JSON.stringify({
|
|
1752
|
+
v: 1,
|
|
1753
|
+
type: "reaction",
|
|
1754
|
+
client_msg_id: args.clientMsgId,
|
|
1755
|
+
target_client_msg_id: args.targetClientMsgId,
|
|
1756
|
+
emoji: args.emoji,
|
|
1757
|
+
op: args.op
|
|
1758
|
+
})
|
|
1759
|
+
);
|
|
1760
|
+
}
|
|
1749
1761
|
function encodeEnvelope(args) {
|
|
1750
1762
|
const env = {
|
|
1751
1763
|
v: 1,
|
|
@@ -1760,18 +1772,32 @@ function decodeEnvelope(bytes) {
|
|
|
1760
1772
|
const s = decodeUtf8(bytes);
|
|
1761
1773
|
try {
|
|
1762
1774
|
const o = JSON.parse(s);
|
|
1775
|
+
if (typeof o === "object" && o !== null && o.type === "reaction") {
|
|
1776
|
+
return {
|
|
1777
|
+
type: "reaction",
|
|
1778
|
+
text: null,
|
|
1779
|
+
clientMsgId: o.client_msg_id ?? "",
|
|
1780
|
+
replyTo: null,
|
|
1781
|
+
reaction: {
|
|
1782
|
+
targetClientMsgId: o.target_client_msg_id ?? "",
|
|
1783
|
+
emoji: o.emoji ?? "",
|
|
1784
|
+
op: o.op ?? "add"
|
|
1785
|
+
}
|
|
1786
|
+
};
|
|
1787
|
+
}
|
|
1763
1788
|
if (typeof o === "object" && o !== null && o.type === "text" && typeof o.v === "number") {
|
|
1764
1789
|
return {
|
|
1790
|
+
type: "text",
|
|
1765
1791
|
text: o.text ?? null,
|
|
1766
1792
|
clientMsgId: o.client_msg_id ?? "",
|
|
1767
1793
|
replyTo: o.reply_to ?? null
|
|
1768
1794
|
};
|
|
1769
1795
|
}
|
|
1770
1796
|
if (typeof o === "object" && o !== null && o.type === "text") {
|
|
1771
|
-
return { text: o.text ?? null, clientMsgId: "", replyTo: null };
|
|
1797
|
+
return { type: "text", text: o.text ?? null, clientMsgId: "", replyTo: null };
|
|
1772
1798
|
}
|
|
1773
1799
|
if (typeof o === "object" && o !== null && o.type === "media")
|
|
1774
|
-
return { text: null, clientMsgId: "", replyTo: null };
|
|
1800
|
+
return { type: "media", text: null, clientMsgId: "", replyTo: null };
|
|
1775
1801
|
} catch {
|
|
1776
1802
|
}
|
|
1777
1803
|
return { text: s, clientMsgId: "", replyTo: null };
|
|
@@ -2023,6 +2049,56 @@ var GroupMessaging = class {
|
|
|
2023
2049
|
}
|
|
2024
2050
|
return { receipt: { serverSeq: wire.server_seq, epoch: wire.epoch }, clientMsgId };
|
|
2025
2051
|
}
|
|
2052
|
+
/** Send a reaction (add/remove of an emoji on a target message). Encrypts a
|
|
2053
|
+
* `type:'reaction'` envelope at the current epoch and sends through the SAME
|
|
2054
|
+
* MLS application path as `sendText` (the server stays blind — a reaction is
|
|
2055
|
+
* just another application message). Persists the outgoing reaction row so it
|
|
2056
|
+
* re-folds onto its target after a reload (the own-send half of the reload
|
|
2057
|
+
* parity). NEVER rebases (epoch-bound like any application message). */
|
|
2058
|
+
async sendReaction(group, args) {
|
|
2059
|
+
const plaintext = encodeReaction({
|
|
2060
|
+
clientMsgId: args.clientMsgId,
|
|
2061
|
+
targetClientMsgId: args.targetClientMsgId,
|
|
2062
|
+
emoji: args.emoji,
|
|
2063
|
+
op: args.op
|
|
2064
|
+
});
|
|
2065
|
+
const ct = await this.engine.encryptApplication(fromBase64(group.rfcGroupId), plaintext);
|
|
2066
|
+
const body = {
|
|
2067
|
+
ciphertext_b64: toBase64(ct),
|
|
2068
|
+
client_idem_key: randomId()
|
|
2069
|
+
};
|
|
2070
|
+
const wire = await palbeRequest(
|
|
2071
|
+
this.rt,
|
|
2072
|
+
"POST",
|
|
2073
|
+
MessagingPaths.groupMessages(group.displayId),
|
|
2074
|
+
{ body }
|
|
2075
|
+
);
|
|
2076
|
+
const stored = {
|
|
2077
|
+
id: `${group.rfcGroupId}#${wire.server_seq}`,
|
|
2078
|
+
direction: "outgoing",
|
|
2079
|
+
text: null,
|
|
2080
|
+
senderDeviceId: this.selfDeviceId,
|
|
2081
|
+
epoch: wire.epoch,
|
|
2082
|
+
serverSeq: wire.server_seq,
|
|
2083
|
+
at: Date.now(),
|
|
2084
|
+
clientMsgId: args.clientMsgId,
|
|
2085
|
+
replyTo: null,
|
|
2086
|
+
envelopeType: "reaction",
|
|
2087
|
+
reaction: {
|
|
2088
|
+
targetClientMsgId: args.targetClientMsgId,
|
|
2089
|
+
emoji: args.emoji,
|
|
2090
|
+
op: args.op
|
|
2091
|
+
}
|
|
2092
|
+
};
|
|
2093
|
+
try {
|
|
2094
|
+
await this.messageStore.append(group.rfcGroupId, stored);
|
|
2095
|
+
} catch {
|
|
2096
|
+
}
|
|
2097
|
+
return {
|
|
2098
|
+
receipt: { serverSeq: wire.server_seq, epoch: wire.epoch },
|
|
2099
|
+
clientMsgId: args.clientMsgId
|
|
2100
|
+
};
|
|
2101
|
+
}
|
|
2026
2102
|
// ── The rebase loop ──
|
|
2027
2103
|
async commitWithRebase(rfcGroupId, build) {
|
|
2028
2104
|
const gidBytes = fromBase64(rfcGroupId);
|
|
@@ -2084,6 +2160,61 @@ var GroupMessaging = class {
|
|
|
2084
2160
|
}
|
|
2085
2161
|
};
|
|
2086
2162
|
|
|
2163
|
+
// src/messaging/reaction-fold.ts
|
|
2164
|
+
function orderLte(aEpoch, aSeq, bEpoch, bSeq) {
|
|
2165
|
+
if (aEpoch !== bEpoch) return aEpoch < bEpoch;
|
|
2166
|
+
return aSeq <= bSeq;
|
|
2167
|
+
}
|
|
2168
|
+
var ReactionFold = class {
|
|
2169
|
+
// Map<targetClientMsgId, Map<actorUserId, UserState>>
|
|
2170
|
+
states = /* @__PURE__ */ new Map();
|
|
2171
|
+
// Dedup set: eventClientMsgId values already applied.
|
|
2172
|
+
seenEvents = /* @__PURE__ */ new Set();
|
|
2173
|
+
ingest(e) {
|
|
2174
|
+
if (this.seenEvents.has(e.eventClientMsgId)) return;
|
|
2175
|
+
this.seenEvents.add(e.eventClientMsgId);
|
|
2176
|
+
let byUser = this.states.get(e.targetClientMsgId);
|
|
2177
|
+
if (byUser === void 0) {
|
|
2178
|
+
byUser = /* @__PURE__ */ new Map();
|
|
2179
|
+
this.states.set(e.targetClientMsgId, byUser);
|
|
2180
|
+
}
|
|
2181
|
+
const prev = byUser.get(e.actorUserId);
|
|
2182
|
+
if (prev !== void 0) {
|
|
2183
|
+
if (orderLte(e.epoch, e.serverSeq, prev.orderEpoch, prev.orderSeq)) return;
|
|
2184
|
+
}
|
|
2185
|
+
byUser.set(e.actorUserId, {
|
|
2186
|
+
orderEpoch: e.epoch,
|
|
2187
|
+
orderSeq: e.serverSeq,
|
|
2188
|
+
emoji: e.op === "add" ? e.emoji : null
|
|
2189
|
+
});
|
|
2190
|
+
}
|
|
2191
|
+
/**
|
|
2192
|
+
* Returns a tally of `{ emoji → sorted userIds[] }` for the given target
|
|
2193
|
+
* message. Users whose last event was a `remove` (emoji === null) are
|
|
2194
|
+
* excluded. userId arrays are sorted for deterministic output (render
|
|
2195
|
+
* stability + golden tests).
|
|
2196
|
+
*/
|
|
2197
|
+
tally(targetClientMsgId) {
|
|
2198
|
+
const byUser = this.states.get(targetClientMsgId);
|
|
2199
|
+
if (byUser === void 0) return {};
|
|
2200
|
+
const out = /* @__PURE__ */ new Map();
|
|
2201
|
+
for (const [userId, st] of byUser) {
|
|
2202
|
+
if (st.emoji === null) continue;
|
|
2203
|
+
let users = out.get(st.emoji);
|
|
2204
|
+
if (users === void 0) {
|
|
2205
|
+
users = [];
|
|
2206
|
+
out.set(st.emoji, users);
|
|
2207
|
+
}
|
|
2208
|
+
users.push(userId);
|
|
2209
|
+
}
|
|
2210
|
+
const result = {};
|
|
2211
|
+
for (const [emoji, users] of out) {
|
|
2212
|
+
result[emoji] = users.sort();
|
|
2213
|
+
}
|
|
2214
|
+
return result;
|
|
2215
|
+
}
|
|
2216
|
+
};
|
|
2217
|
+
|
|
2087
2218
|
// src/messaging/chat.ts
|
|
2088
2219
|
var Chat = class {
|
|
2089
2220
|
/** Stable, URL/log-safe id (the grp_ display id once active; a reserved local id while draft). */
|
|
@@ -2102,6 +2233,8 @@ var Chat = class {
|
|
|
2102
2233
|
seenKeys = /* @__PURE__ */ new Set();
|
|
2103
2234
|
/** Index: clientMsgId → { text, senderUserId } for resolveReply lookups. */
|
|
2104
2235
|
byClientMsgId = /* @__PURE__ */ new Map();
|
|
2236
|
+
/** The single authoritative reaction fold for this chat (live + own-send + history). */
|
|
2237
|
+
reactionFold = new ReactionFold();
|
|
2105
2238
|
loadedEarliestSeq = null;
|
|
2106
2239
|
historyLoaded = false;
|
|
2107
2240
|
wired = false;
|
|
@@ -2208,7 +2341,7 @@ var Chat = class {
|
|
|
2208
2341
|
const key = this.internalKey(m.serverSeq);
|
|
2209
2342
|
if (this.seenKeys.has(key)) continue;
|
|
2210
2343
|
this.seenKeys.add(key);
|
|
2211
|
-
this.messageList.push(m);
|
|
2344
|
+
this.messageList.push(this.applyReactionTally(m));
|
|
2212
2345
|
changed = true;
|
|
2213
2346
|
this.loadedEarliestSeq = Math.min(this.loadedEarliestSeq ?? m.serverSeq, m.serverSeq);
|
|
2214
2347
|
}
|
|
@@ -2233,6 +2366,22 @@ var Chat = class {
|
|
|
2233
2366
|
senderUser = await this.backend.userIdForDevice(this._group, incoming.senderDeviceId);
|
|
2234
2367
|
}
|
|
2235
2368
|
const direction = senderUser !== null && senderUser === this.backend.selfUserId ? "outgoing" : "incoming";
|
|
2369
|
+
if (incoming.envelopeType === "reaction" && incoming.reaction) {
|
|
2370
|
+
const actorUserId = direction === "outgoing" ? this.backend.selfUserId : senderUser;
|
|
2371
|
+
if (actorUserId !== null) {
|
|
2372
|
+
this.reactionFold.ingest({
|
|
2373
|
+
targetClientMsgId: incoming.reaction.targetClientMsgId,
|
|
2374
|
+
actorUserId,
|
|
2375
|
+
emoji: incoming.reaction.emoji,
|
|
2376
|
+
op: incoming.reaction.op,
|
|
2377
|
+
epoch: incoming.epoch,
|
|
2378
|
+
serverSeq: incoming.serverSeq,
|
|
2379
|
+
eventClientMsgId: incoming.clientMsgId
|
|
2380
|
+
});
|
|
2381
|
+
this.recomputeReactions(incoming.reaction.targetClientMsgId);
|
|
2382
|
+
}
|
|
2383
|
+
return;
|
|
2384
|
+
}
|
|
2236
2385
|
const incomingClientMsgId = incoming.clientMsgId;
|
|
2237
2386
|
const incomingReplyRef = incoming.replyRef;
|
|
2238
2387
|
let resolvedReplyTo = null;
|
|
@@ -2248,7 +2397,10 @@ var Chat = class {
|
|
|
2248
2397
|
serverSeq: incoming.serverSeq,
|
|
2249
2398
|
sentAt: incoming.receivedAt,
|
|
2250
2399
|
clientMsgId: incomingClientMsgId,
|
|
2251
|
-
replyTo: resolvedReplyTo
|
|
2400
|
+
replyTo: resolvedReplyTo,
|
|
2401
|
+
// Attach any tally already folded for this message (a reaction that arrived
|
|
2402
|
+
// BEFORE its target — the dangling case — renders the moment the target lands).
|
|
2403
|
+
reactions: incomingClientMsgId ? this.reactionFold.tally(incomingClientMsgId) : {}
|
|
2252
2404
|
};
|
|
2253
2405
|
if (incomingClientMsgId && incoming.text !== null) {
|
|
2254
2406
|
this.byClientMsgId.set(incomingClientMsgId, {
|
|
@@ -2264,6 +2416,35 @@ var Chat = class {
|
|
|
2264
2416
|
);
|
|
2265
2417
|
this.emit();
|
|
2266
2418
|
}
|
|
2419
|
+
/**
|
|
2420
|
+
* Rebuild the target message's `reactions` from the authoritative fold and
|
|
2421
|
+
* re-emit. No-op when the target isn't present yet (its tally is attached the
|
|
2422
|
+
* moment it lands, via the append/merge paths) or when the tally is unchanged.
|
|
2423
|
+
*/
|
|
2424
|
+
recomputeReactions(targetClientMsgId) {
|
|
2425
|
+
if (!targetClientMsgId) return;
|
|
2426
|
+
const tally = this.reactionFold.tally(targetClientMsgId);
|
|
2427
|
+
let changed = false;
|
|
2428
|
+
this.messageList = this.messageList.map((m) => {
|
|
2429
|
+
if (m.clientMsgId !== targetClientMsgId) return m;
|
|
2430
|
+
if (sameReactions(m.reactions, tally)) return m;
|
|
2431
|
+
changed = true;
|
|
2432
|
+
return { ...m, reactions: tally };
|
|
2433
|
+
});
|
|
2434
|
+
if (changed) this.emit();
|
|
2435
|
+
}
|
|
2436
|
+
/**
|
|
2437
|
+
* Overlay the authoritative fold's tally onto a message as it is appended/merged.
|
|
2438
|
+
* The Chat fold WINS when it has a non-empty tally; otherwise the tally already
|
|
2439
|
+
* attached upstream (the coordinator's page-local history fold) is preserved.
|
|
2440
|
+
*/
|
|
2441
|
+
applyReactionTally(m) {
|
|
2442
|
+
if (!m.clientMsgId) return m;
|
|
2443
|
+
const tally = this.reactionFold.tally(m.clientMsgId);
|
|
2444
|
+
if (Object.keys(tally).length === 0) return m;
|
|
2445
|
+
if (sameReactions(m.reactions, tally)) return m;
|
|
2446
|
+
return { ...m, reactions: tally };
|
|
2447
|
+
}
|
|
2267
2448
|
/** @internal — called by the backend's conv subscription. */
|
|
2268
2449
|
applyConv(event, payload) {
|
|
2269
2450
|
const userId = typeof payload.user_id === "string" ? payload.user_id : null;
|
|
@@ -2406,7 +2587,10 @@ var Chat = class {
|
|
|
2406
2587
|
serverSeq: receipt.serverSeq,
|
|
2407
2588
|
sentAt: /* @__PURE__ */ new Date(),
|
|
2408
2589
|
clientMsgId,
|
|
2409
|
-
replyTo: resolvedReplyTo
|
|
2590
|
+
replyTo: resolvedReplyTo,
|
|
2591
|
+
// Attach any tally already folded for this own-sent message (rare, but keeps
|
|
2592
|
+
// the dangling-target invariant uniform across every append path).
|
|
2593
|
+
reactions: clientMsgId ? this.reactionFold.tally(clientMsgId) : {}
|
|
2410
2594
|
});
|
|
2411
2595
|
this.messageList.sort((a, b) => a.serverSeq - b.serverSeq);
|
|
2412
2596
|
this.emit();
|
|
@@ -2453,7 +2637,53 @@ var Chat = class {
|
|
|
2453
2637
|
this.readWatermark = Math.max(this.readWatermark, message.serverSeq);
|
|
2454
2638
|
this.emit();
|
|
2455
2639
|
}
|
|
2640
|
+
// ── Reactions ──
|
|
2641
|
+
/** Add an emoji reaction to a message. No-op if the message isn't reactable
|
|
2642
|
+
* (empty clientMsgId — a legacy/system row). The reaction folds locally with
|
|
2643
|
+
* the server receipt's `(epoch, serverSeq)` so the target's tally updates
|
|
2644
|
+
* instantly; the durable echo on the next pump is a fold no-op (dedup on the
|
|
2645
|
+
* SAME wire clientMsgId). Never appends a bubble. */
|
|
2646
|
+
async react(message, emoji) {
|
|
2647
|
+
await this.sendReaction(message, emoji, "add");
|
|
2648
|
+
}
|
|
2649
|
+
/** Remove this user's emoji reaction from a message (op:'remove'). */
|
|
2650
|
+
async unreact(message, emoji) {
|
|
2651
|
+
await this.sendReaction(message, emoji, "remove");
|
|
2652
|
+
}
|
|
2653
|
+
async sendReaction(message, emoji, op) {
|
|
2654
|
+
if (!message.clientMsgId) return;
|
|
2655
|
+
const group = await this.materializeIfNeeded();
|
|
2656
|
+
const clientMsgId = mintClientMsgId();
|
|
2657
|
+
const { receipt } = await this.backend.sendReaction(group, {
|
|
2658
|
+
clientMsgId,
|
|
2659
|
+
targetClientMsgId: message.clientMsgId,
|
|
2660
|
+
emoji,
|
|
2661
|
+
op
|
|
2662
|
+
});
|
|
2663
|
+
this.reactionFold.ingest({
|
|
2664
|
+
targetClientMsgId: message.clientMsgId,
|
|
2665
|
+
actorUserId: this.backend.selfUserId,
|
|
2666
|
+
emoji,
|
|
2667
|
+
op,
|
|
2668
|
+
epoch: receipt.epoch,
|
|
2669
|
+
serverSeq: receipt.serverSeq,
|
|
2670
|
+
eventClientMsgId: clientMsgId
|
|
2671
|
+
});
|
|
2672
|
+
this.recomputeReactions(message.clientMsgId);
|
|
2673
|
+
}
|
|
2456
2674
|
};
|
|
2675
|
+
function sameReactions(a, b) {
|
|
2676
|
+
const ak = Object.keys(a);
|
|
2677
|
+
const bk = Object.keys(b);
|
|
2678
|
+
if (ak.length !== bk.length) return false;
|
|
2679
|
+
for (const k of ak) {
|
|
2680
|
+
const av = a[k];
|
|
2681
|
+
const bv = b[k];
|
|
2682
|
+
if (!bv || av === void 0 || av.length !== bv.length) return false;
|
|
2683
|
+
for (let i = 0; i < av.length; i++) if (av[i] !== bv[i]) return false;
|
|
2684
|
+
}
|
|
2685
|
+
return true;
|
|
2686
|
+
}
|
|
2457
2687
|
|
|
2458
2688
|
// src/messaging/delivery-source.ts
|
|
2459
2689
|
var MessageHub = class {
|
|
@@ -2483,6 +2713,11 @@ var MessageHub = class {
|
|
|
2483
2713
|
};
|
|
2484
2714
|
}
|
|
2485
2715
|
};
|
|
2716
|
+
function decodeSenderDeviceId(sender) {
|
|
2717
|
+
if (sender.length === 0) return null;
|
|
2718
|
+
const id = decodeUtf8(sender);
|
|
2719
|
+
return id.length > 0 ? id : null;
|
|
2720
|
+
}
|
|
2486
2721
|
var MessageDeliverySource = class {
|
|
2487
2722
|
constructor(rt, engine, hub, registry, messageStore, deviceId, selfUserId) {
|
|
2488
2723
|
this.rt = rt;
|
|
@@ -2612,12 +2847,15 @@ var MessageDeliverySource = class {
|
|
|
2612
2847
|
try {
|
|
2613
2848
|
const received = await this.engine.processIncoming(fromBase64(group.rfcGroupId), blob);
|
|
2614
2849
|
if (received.type === "application") {
|
|
2615
|
-
const
|
|
2850
|
+
const decoded = decodeEnvelope(received.data);
|
|
2851
|
+
const { text, clientMsgId, replyTo } = decoded;
|
|
2852
|
+
const isReaction = decoded.type === "reaction" && decoded.reaction != null;
|
|
2853
|
+
const senderDeviceId = row.sender_device_id ?? decodeSenderDeviceId(received.sender);
|
|
2616
2854
|
const stored = {
|
|
2617
2855
|
id: `${group.rfcGroupId}#${row.server_seq}`,
|
|
2618
2856
|
direction: "incoming",
|
|
2619
2857
|
text,
|
|
2620
|
-
senderDeviceId
|
|
2858
|
+
senderDeviceId,
|
|
2621
2859
|
epoch: row.epoch,
|
|
2622
2860
|
serverSeq: row.server_seq,
|
|
2623
2861
|
at: Date.now(),
|
|
@@ -2627,7 +2865,19 @@ var MessageDeliverySource = class {
|
|
|
2627
2865
|
previewBody: replyTo.preview?.body ?? null,
|
|
2628
2866
|
previewAuthorUserId: replyTo.preview?.author_user_id ?? null,
|
|
2629
2867
|
previewKind: replyTo.preview?.kind ?? "text"
|
|
2630
|
-
} : null
|
|
2868
|
+
} : null,
|
|
2869
|
+
// Thread the reaction discriminator + fields through the persisted row so
|
|
2870
|
+
// a reaction folded LIVE re-folds onto its target after a reload (the
|
|
2871
|
+
// reload-parity boundary — mirrors iOS T3). Omitted for non-reactions →
|
|
2872
|
+
// old rows hydrate as `'text'`/no-reaction (backward-compat).
|
|
2873
|
+
...isReaction && decoded.reaction ? {
|
|
2874
|
+
envelopeType: "reaction",
|
|
2875
|
+
reaction: {
|
|
2876
|
+
targetClientMsgId: decoded.reaction.targetClientMsgId,
|
|
2877
|
+
emoji: decoded.reaction.emoji,
|
|
2878
|
+
op: decoded.reaction.op
|
|
2879
|
+
}
|
|
2880
|
+
} : {}
|
|
2631
2881
|
};
|
|
2632
2882
|
try {
|
|
2633
2883
|
await this.messageStore.append(group.rfcGroupId, stored);
|
|
@@ -2638,12 +2888,14 @@ var MessageDeliverySource = class {
|
|
|
2638
2888
|
kind: "application",
|
|
2639
2889
|
group,
|
|
2640
2890
|
text,
|
|
2641
|
-
senderDeviceId
|
|
2891
|
+
senderDeviceId,
|
|
2642
2892
|
epoch: row.epoch,
|
|
2643
2893
|
serverSeq: row.server_seq,
|
|
2644
2894
|
receivedAt: /* @__PURE__ */ new Date(),
|
|
2645
2895
|
clientMsgId,
|
|
2646
|
-
replyRef: replyTo
|
|
2896
|
+
replyRef: replyTo,
|
|
2897
|
+
envelopeType: decoded.type ?? "text",
|
|
2898
|
+
reaction: isReaction ? decoded.reaction : null
|
|
2647
2899
|
});
|
|
2648
2900
|
return true;
|
|
2649
2901
|
}
|
|
@@ -4647,46 +4899,14 @@ var MessagingCoordinator = class {
|
|
|
4647
4899
|
const r = await this.resolve();
|
|
4648
4900
|
return r.groups.sendText(group, text, replyTo);
|
|
4649
4901
|
}
|
|
4902
|
+
async sendReaction(group, args) {
|
|
4903
|
+
const r = await this.resolve();
|
|
4904
|
+
return r.groups.sendReaction(group, args);
|
|
4905
|
+
}
|
|
4650
4906
|
async history(group, limit, before) {
|
|
4651
4907
|
const r = await this.resolve();
|
|
4652
4908
|
const rows = await r.messageStore.history(group.rfcGroupId, limit, before);
|
|
4653
|
-
|
|
4654
|
-
for (const s of rows) {
|
|
4655
|
-
const cid = s.clientMsgId ?? "";
|
|
4656
|
-
if (cid && s.text !== null) {
|
|
4657
|
-
const senderUserId = s.direction === "outgoing" ? this.selfUserId : "";
|
|
4658
|
-
lookup.set(cid, { text: s.text, senderUserId });
|
|
4659
|
-
}
|
|
4660
|
-
}
|
|
4661
|
-
return rows.map((s) => this.toChatMessage(group, s, (id) => lookup.get(id) ?? null));
|
|
4662
|
-
}
|
|
4663
|
-
toChatMessage(group, s, lookup) {
|
|
4664
|
-
const clientMsgId = s.clientMsgId ?? "";
|
|
4665
|
-
let replyTo = null;
|
|
4666
|
-
if (s.replyTo && lookup) {
|
|
4667
|
-
const ref = {
|
|
4668
|
-
v: 1,
|
|
4669
|
-
client_msg_id: s.replyTo.clientMsgId,
|
|
4670
|
-
preview: {
|
|
4671
|
-
kind: s.replyTo.previewKind,
|
|
4672
|
-
author_user_id: s.replyTo.previewAuthorUserId ?? "",
|
|
4673
|
-
body: s.replyTo.previewBody ?? void 0,
|
|
4674
|
-
body_truncated: false
|
|
4675
|
-
}
|
|
4676
|
-
};
|
|
4677
|
-
replyTo = resolveReply(ref, lookup);
|
|
4678
|
-
}
|
|
4679
|
-
return {
|
|
4680
|
-
id: `${group.displayId}#${s.serverSeq}`,
|
|
4681
|
-
kind: s.text != null ? "text" : "system",
|
|
4682
|
-
direction: s.direction,
|
|
4683
|
-
senderUserId: s.direction === "outgoing" ? this.selfUserId : null,
|
|
4684
|
-
text: s.text,
|
|
4685
|
-
serverSeq: s.serverSeq,
|
|
4686
|
-
sentAt: new Date(s.at),
|
|
4687
|
-
clientMsgId,
|
|
4688
|
-
replyTo
|
|
4689
|
-
};
|
|
4909
|
+
return projectHistory(group.displayId, rows, this.selfUserId);
|
|
4690
4910
|
}
|
|
4691
4911
|
async members(group) {
|
|
4692
4912
|
const r = await this.resolve();
|
|
@@ -4763,6 +4983,64 @@ var MessagingCoordinator = class {
|
|
|
4763
4983
|
return res.devices.map((d) => d.device_id);
|
|
4764
4984
|
}
|
|
4765
4985
|
};
|
|
4986
|
+
function projectHistory(displayId, rows, selfUserId, resolveActor) {
|
|
4987
|
+
const fold = new ReactionFold();
|
|
4988
|
+
for (const s of rows) {
|
|
4989
|
+
if (s.envelopeType !== "reaction" || !s.reaction) continue;
|
|
4990
|
+
const actor = s.direction === "outgoing" ? selfUserId : resolveActor?.(s.senderDeviceId ?? null) ?? null;
|
|
4991
|
+
if (actor === null) continue;
|
|
4992
|
+
fold.ingest({
|
|
4993
|
+
targetClientMsgId: s.reaction.targetClientMsgId,
|
|
4994
|
+
actorUserId: actor,
|
|
4995
|
+
emoji: s.reaction.emoji,
|
|
4996
|
+
op: s.reaction.op,
|
|
4997
|
+
epoch: s.epoch,
|
|
4998
|
+
serverSeq: s.serverSeq,
|
|
4999
|
+
eventClientMsgId: s.clientMsgId ?? `${s.id}`
|
|
5000
|
+
});
|
|
5001
|
+
}
|
|
5002
|
+
const lookup = /* @__PURE__ */ new Map();
|
|
5003
|
+
for (const s of rows) {
|
|
5004
|
+
if (s.envelopeType === "reaction") continue;
|
|
5005
|
+
const cid = s.clientMsgId ?? "";
|
|
5006
|
+
if (cid && s.text !== null) {
|
|
5007
|
+
const senderUserId = s.direction === "outgoing" ? selfUserId : "";
|
|
5008
|
+
lookup.set(cid, { text: s.text, senderUserId });
|
|
5009
|
+
}
|
|
5010
|
+
}
|
|
5011
|
+
const out = [];
|
|
5012
|
+
for (const s of rows) {
|
|
5013
|
+
if (s.envelopeType === "reaction") continue;
|
|
5014
|
+
const clientMsgId = s.clientMsgId ?? "";
|
|
5015
|
+
let replyTo = null;
|
|
5016
|
+
if (s.replyTo) {
|
|
5017
|
+
const ref = {
|
|
5018
|
+
v: 1,
|
|
5019
|
+
client_msg_id: s.replyTo.clientMsgId,
|
|
5020
|
+
preview: {
|
|
5021
|
+
kind: s.replyTo.previewKind,
|
|
5022
|
+
author_user_id: s.replyTo.previewAuthorUserId ?? "",
|
|
5023
|
+
body: s.replyTo.previewBody ?? void 0,
|
|
5024
|
+
body_truncated: false
|
|
5025
|
+
}
|
|
5026
|
+
};
|
|
5027
|
+
replyTo = resolveReply(ref, (id) => lookup.get(id) ?? null);
|
|
5028
|
+
}
|
|
5029
|
+
out.push({
|
|
5030
|
+
id: `${displayId}#${s.serverSeq}`,
|
|
5031
|
+
kind: s.text != null ? "text" : "system",
|
|
5032
|
+
direction: s.direction,
|
|
5033
|
+
senderUserId: s.direction === "outgoing" ? selfUserId : null,
|
|
5034
|
+
text: s.text,
|
|
5035
|
+
serverSeq: s.serverSeq,
|
|
5036
|
+
sentAt: new Date(s.at),
|
|
5037
|
+
clientMsgId,
|
|
5038
|
+
replyTo,
|
|
5039
|
+
reactions: clientMsgId ? fold.tally(clientMsgId) : {}
|
|
5040
|
+
});
|
|
5041
|
+
}
|
|
5042
|
+
return out;
|
|
5043
|
+
}
|
|
4766
5044
|
|
|
4767
5045
|
// src/messaging/facade.ts
|
|
4768
5046
|
var PalbeMessaging = class {
|
|
@@ -5615,7 +5893,7 @@ function localStorageSessionStorage(key = DEFAULT_KEY) {
|
|
|
5615
5893
|
}
|
|
5616
5894
|
|
|
5617
5895
|
// src/version.ts
|
|
5618
|
-
var VERSION = "1.
|
|
5896
|
+
var VERSION = "1.2.1";
|
|
5619
5897
|
|
|
5620
5898
|
// src/internal.ts
|
|
5621
5899
|
function getRuntime() {
|