@palbase/web 1.1.0 → 1.2.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/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-XVLR3HGD.js → chunk-OZLVL7G2.js} +319 -48
- package/dist/chunk-OZLVL7G2.js.map +1 -0
- package/dist/index.cjs +318 -47
- 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 +318 -47
- 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 +318 -47
- package/dist/next/client.cjs.map +1 -1
- package/dist/next/client.js +1 -1
- package/dist/next/index.cjs +318 -47
- 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 +2 -2
- package/dist/chunk-XVLR3HGD.js.map +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { A as AnalyticsProperties, a as AuthChangeEvent, b as AuthState, c as AuthSuccess, d as AuthUser, C as Call, e as CallChangeCallback, f as CallParticipant, g as CallState, h as Chat, i as ChatBackend, j as ChatDraft, k as ChatKind, l as ChatMember, m as ChatMessage, n as ChatMessageKind, o as ChatRole, p as ChatState, q as CommsPrefs, F as FlagsView, M as MagicLinkResult, r as MessageDirection, O as OAuthExchangeResult, P as PalbeAnalytics, s as PalbeAuth, t as PalbeCalls, u as PalbeConfig, v as PalbeFlags, w as PalbeMessaging, x as PalbeOAuthConfig, y as PalbeRealtime, z as PresenceState, R as RealtimeChannel, B as RealtimeConnectionState, D as RealtimeHandler, E as RealtimePayload, G as RealtimeStatus, H as RealtimeStatusSnapshot, I as RealtimeSubscription, S as SentReceipt, U as Unsubscribe } from './analytics-facade-
|
|
1
|
+
export { A as AnalyticsProperties, a as AuthChangeEvent, b as AuthState, c as AuthSuccess, d as AuthUser, C as Call, e as CallChangeCallback, f as CallParticipant, g as CallState, h as Chat, i as ChatBackend, j as ChatDraft, k as ChatKind, l as ChatMember, m as ChatMessage, n as ChatMessageKind, o as ChatRole, p as ChatState, q as CommsPrefs, F as FlagsView, M as MagicLinkResult, r as MessageDirection, O as OAuthExchangeResult, P as PalbeAnalytics, s as PalbeAuth, t as PalbeCalls, u as PalbeConfig, v as PalbeFlags, w as PalbeMessaging, x as PalbeOAuthConfig, y as PalbeRealtime, z as PresenceState, R as RealtimeChannel, B as RealtimeConnectionState, D as RealtimeHandler, E as RealtimePayload, G as RealtimeStatus, H as RealtimeStatusSnapshot, I as RealtimeSubscription, S as SentReceipt, U as Unsubscribe } from './analytics-facade-Ct3A1zop.cjs';
|
|
2
2
|
export { B as BackendError, a as BackendErrorKind, F as FieldError, i as isBackendError } from './errors-fDoNdTrJ.cjs';
|
|
3
|
-
export { C as CallOptions, P as PB, U as UploadOptions, a as UploadProgress, p as pb } from './pb-
|
|
3
|
+
export { C as CallOptions, P as PB, U as UploadOptions, a as UploadProgress, p as pb } from './pb-BlIfgBG-.cjs';
|
|
4
4
|
export { P as PersistedSession, S as SessionStorageAdapter, l as localStorageSessionStorage, m as memorySessionStorage } from './storage-BPaeSG8K.cjs';
|
|
5
5
|
export { F as FlagValue } from './pooled-flags-Bwq4usn0.js';
|
|
6
6
|
import 'livekit-client';
|
|
@@ -207,6 +207,6 @@ interface MlsGlue {
|
|
|
207
207
|
*/
|
|
208
208
|
declare function initMls(): Promise<MlsGlue>;
|
|
209
209
|
|
|
210
|
-
declare const VERSION = "1.
|
|
210
|
+
declare const VERSION = "1.2.0";
|
|
211
211
|
|
|
212
212
|
export { VERSION, initMls };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { A as AnalyticsProperties, a as AuthChangeEvent, b as AuthState, c as AuthSuccess, d as AuthUser, C as Call, e as CallChangeCallback, f as CallParticipant, g as CallState, h as Chat, i as ChatBackend, j as ChatDraft, k as ChatKind, l as ChatMember, m as ChatMessage, n as ChatMessageKind, o as ChatRole, p as ChatState, q as CommsPrefs, F as FlagsView, M as MagicLinkResult, r as MessageDirection, O as OAuthExchangeResult, P as PalbeAnalytics, s as PalbeAuth, t as PalbeCalls, u as PalbeConfig, v as PalbeFlags, w as PalbeMessaging, x as PalbeOAuthConfig, y as PalbeRealtime, z as PresenceState, R as RealtimeChannel, B as RealtimeConnectionState, D as RealtimeHandler, E as RealtimePayload, G as RealtimeStatus, H as RealtimeStatusSnapshot, I as RealtimeSubscription, S as SentReceipt, U as Unsubscribe } from './analytics-facade-
|
|
1
|
+
export { A as AnalyticsProperties, a as AuthChangeEvent, b as AuthState, c as AuthSuccess, d as AuthUser, C as Call, e as CallChangeCallback, f as CallParticipant, g as CallState, h as Chat, i as ChatBackend, j as ChatDraft, k as ChatKind, l as ChatMember, m as ChatMessage, n as ChatMessageKind, o as ChatRole, p as ChatState, q as CommsPrefs, F as FlagsView, M as MagicLinkResult, r as MessageDirection, O as OAuthExchangeResult, P as PalbeAnalytics, s as PalbeAuth, t as PalbeCalls, u as PalbeConfig, v as PalbeFlags, w as PalbeMessaging, x as PalbeOAuthConfig, y as PalbeRealtime, z as PresenceState, R as RealtimeChannel, B as RealtimeConnectionState, D as RealtimeHandler, E as RealtimePayload, G as RealtimeStatus, H as RealtimeStatusSnapshot, I as RealtimeSubscription, S as SentReceipt, U as Unsubscribe } from './analytics-facade-C93tr7dA.js';
|
|
2
2
|
export { B as BackendError, a as BackendErrorKind, F as FieldError, i as isBackendError } from './errors-fDoNdTrJ.js';
|
|
3
|
-
export { C as CallOptions, P as PB, U as UploadOptions, a as UploadProgress, p as pb } from './pb-
|
|
3
|
+
export { C as CallOptions, P as PB, U as UploadOptions, a as UploadProgress, p as pb } from './pb-BtYdWClg.js';
|
|
4
4
|
export { P as PersistedSession, S as SessionStorageAdapter, l as localStorageSessionStorage, m as memorySessionStorage } from './storage-BPaeSG8K.js';
|
|
5
5
|
export { F as FlagValue } from './pooled-flags-Bwq4usn0.js';
|
|
6
6
|
import 'livekit-client';
|
|
@@ -207,6 +207,6 @@ interface MlsGlue {
|
|
|
207
207
|
*/
|
|
208
208
|
declare function initMls(): Promise<MlsGlue>;
|
|
209
209
|
|
|
210
|
-
declare const VERSION = "1.
|
|
210
|
+
declare const VERSION = "1.2.0";
|
|
211
211
|
|
|
212
212
|
export { VERSION, initMls };
|
package/dist/index.js
CHANGED
package/dist/internal.cjs
CHANGED
|
@@ -2751,6 +2751,18 @@ async function listDevices(rt, userId) {
|
|
|
2751
2751
|
}
|
|
2752
2752
|
|
|
2753
2753
|
// src/messaging/group-messaging.ts
|
|
2754
|
+
function encodeReaction(args) {
|
|
2755
|
+
return encodeUtf8(
|
|
2756
|
+
JSON.stringify({
|
|
2757
|
+
v: 1,
|
|
2758
|
+
type: "reaction",
|
|
2759
|
+
client_msg_id: args.clientMsgId,
|
|
2760
|
+
target_client_msg_id: args.targetClientMsgId,
|
|
2761
|
+
emoji: args.emoji,
|
|
2762
|
+
op: args.op
|
|
2763
|
+
})
|
|
2764
|
+
);
|
|
2765
|
+
}
|
|
2754
2766
|
function encodeEnvelope(args) {
|
|
2755
2767
|
const env = {
|
|
2756
2768
|
v: 1,
|
|
@@ -2765,18 +2777,32 @@ function decodeEnvelope(bytes) {
|
|
|
2765
2777
|
const s = decodeUtf8(bytes);
|
|
2766
2778
|
try {
|
|
2767
2779
|
const o = JSON.parse(s);
|
|
2780
|
+
if (typeof o === "object" && o !== null && o.type === "reaction") {
|
|
2781
|
+
return {
|
|
2782
|
+
type: "reaction",
|
|
2783
|
+
text: null,
|
|
2784
|
+
clientMsgId: o.client_msg_id ?? "",
|
|
2785
|
+
replyTo: null,
|
|
2786
|
+
reaction: {
|
|
2787
|
+
targetClientMsgId: o.target_client_msg_id ?? "",
|
|
2788
|
+
emoji: o.emoji ?? "",
|
|
2789
|
+
op: o.op ?? "add"
|
|
2790
|
+
}
|
|
2791
|
+
};
|
|
2792
|
+
}
|
|
2768
2793
|
if (typeof o === "object" && o !== null && o.type === "text" && typeof o.v === "number") {
|
|
2769
2794
|
return {
|
|
2795
|
+
type: "text",
|
|
2770
2796
|
text: o.text ?? null,
|
|
2771
2797
|
clientMsgId: o.client_msg_id ?? "",
|
|
2772
2798
|
replyTo: o.reply_to ?? null
|
|
2773
2799
|
};
|
|
2774
2800
|
}
|
|
2775
2801
|
if (typeof o === "object" && o !== null && o.type === "text") {
|
|
2776
|
-
return { text: o.text ?? null, clientMsgId: "", replyTo: null };
|
|
2802
|
+
return { type: "text", text: o.text ?? null, clientMsgId: "", replyTo: null };
|
|
2777
2803
|
}
|
|
2778
2804
|
if (typeof o === "object" && o !== null && o.type === "media")
|
|
2779
|
-
return { text: null, clientMsgId: "", replyTo: null };
|
|
2805
|
+
return { type: "media", text: null, clientMsgId: "", replyTo: null };
|
|
2780
2806
|
} catch {
|
|
2781
2807
|
}
|
|
2782
2808
|
return { text: s, clientMsgId: "", replyTo: null };
|
|
@@ -3028,6 +3054,56 @@ var GroupMessaging = class {
|
|
|
3028
3054
|
}
|
|
3029
3055
|
return { receipt: { serverSeq: wire.server_seq, epoch: wire.epoch }, clientMsgId };
|
|
3030
3056
|
}
|
|
3057
|
+
/** Send a reaction (add/remove of an emoji on a target message). Encrypts a
|
|
3058
|
+
* `type:'reaction'` envelope at the current epoch and sends through the SAME
|
|
3059
|
+
* MLS application path as `sendText` (the server stays blind — a reaction is
|
|
3060
|
+
* just another application message). Persists the outgoing reaction row so it
|
|
3061
|
+
* re-folds onto its target after a reload (the own-send half of the reload
|
|
3062
|
+
* parity). NEVER rebases (epoch-bound like any application message). */
|
|
3063
|
+
async sendReaction(group, args) {
|
|
3064
|
+
const plaintext = encodeReaction({
|
|
3065
|
+
clientMsgId: args.clientMsgId,
|
|
3066
|
+
targetClientMsgId: args.targetClientMsgId,
|
|
3067
|
+
emoji: args.emoji,
|
|
3068
|
+
op: args.op
|
|
3069
|
+
});
|
|
3070
|
+
const ct = await this.engine.encryptApplication(fromBase64(group.rfcGroupId), plaintext);
|
|
3071
|
+
const body = {
|
|
3072
|
+
ciphertext_b64: toBase64(ct),
|
|
3073
|
+
client_idem_key: randomId()
|
|
3074
|
+
};
|
|
3075
|
+
const wire = await palbeRequest(
|
|
3076
|
+
this.rt,
|
|
3077
|
+
"POST",
|
|
3078
|
+
MessagingPaths.groupMessages(group.displayId),
|
|
3079
|
+
{ body }
|
|
3080
|
+
);
|
|
3081
|
+
const stored = {
|
|
3082
|
+
id: `${group.rfcGroupId}#${wire.server_seq}`,
|
|
3083
|
+
direction: "outgoing",
|
|
3084
|
+
text: null,
|
|
3085
|
+
senderDeviceId: this.selfDeviceId,
|
|
3086
|
+
epoch: wire.epoch,
|
|
3087
|
+
serverSeq: wire.server_seq,
|
|
3088
|
+
at: Date.now(),
|
|
3089
|
+
clientMsgId: args.clientMsgId,
|
|
3090
|
+
replyTo: null,
|
|
3091
|
+
envelopeType: "reaction",
|
|
3092
|
+
reaction: {
|
|
3093
|
+
targetClientMsgId: args.targetClientMsgId,
|
|
3094
|
+
emoji: args.emoji,
|
|
3095
|
+
op: args.op
|
|
3096
|
+
}
|
|
3097
|
+
};
|
|
3098
|
+
try {
|
|
3099
|
+
await this.messageStore.append(group.rfcGroupId, stored);
|
|
3100
|
+
} catch {
|
|
3101
|
+
}
|
|
3102
|
+
return {
|
|
3103
|
+
receipt: { serverSeq: wire.server_seq, epoch: wire.epoch },
|
|
3104
|
+
clientMsgId: args.clientMsgId
|
|
3105
|
+
};
|
|
3106
|
+
}
|
|
3031
3107
|
// ── The rebase loop ──
|
|
3032
3108
|
async commitWithRebase(rfcGroupId, build) {
|
|
3033
3109
|
const gidBytes = fromBase64(rfcGroupId);
|
|
@@ -3089,6 +3165,61 @@ var GroupMessaging = class {
|
|
|
3089
3165
|
}
|
|
3090
3166
|
};
|
|
3091
3167
|
|
|
3168
|
+
// src/messaging/reaction-fold.ts
|
|
3169
|
+
function orderLte(aEpoch, aSeq, bEpoch, bSeq) {
|
|
3170
|
+
if (aEpoch !== bEpoch) return aEpoch < bEpoch;
|
|
3171
|
+
return aSeq <= bSeq;
|
|
3172
|
+
}
|
|
3173
|
+
var ReactionFold = class {
|
|
3174
|
+
// Map<targetClientMsgId, Map<actorUserId, UserState>>
|
|
3175
|
+
states = /* @__PURE__ */ new Map();
|
|
3176
|
+
// Dedup set: eventClientMsgId values already applied.
|
|
3177
|
+
seenEvents = /* @__PURE__ */ new Set();
|
|
3178
|
+
ingest(e) {
|
|
3179
|
+
if (this.seenEvents.has(e.eventClientMsgId)) return;
|
|
3180
|
+
this.seenEvents.add(e.eventClientMsgId);
|
|
3181
|
+
let byUser = this.states.get(e.targetClientMsgId);
|
|
3182
|
+
if (byUser === void 0) {
|
|
3183
|
+
byUser = /* @__PURE__ */ new Map();
|
|
3184
|
+
this.states.set(e.targetClientMsgId, byUser);
|
|
3185
|
+
}
|
|
3186
|
+
const prev = byUser.get(e.actorUserId);
|
|
3187
|
+
if (prev !== void 0) {
|
|
3188
|
+
if (orderLte(e.epoch, e.serverSeq, prev.orderEpoch, prev.orderSeq)) return;
|
|
3189
|
+
}
|
|
3190
|
+
byUser.set(e.actorUserId, {
|
|
3191
|
+
orderEpoch: e.epoch,
|
|
3192
|
+
orderSeq: e.serverSeq,
|
|
3193
|
+
emoji: e.op === "add" ? e.emoji : null
|
|
3194
|
+
});
|
|
3195
|
+
}
|
|
3196
|
+
/**
|
|
3197
|
+
* Returns a tally of `{ emoji → sorted userIds[] }` for the given target
|
|
3198
|
+
* message. Users whose last event was a `remove` (emoji === null) are
|
|
3199
|
+
* excluded. userId arrays are sorted for deterministic output (render
|
|
3200
|
+
* stability + golden tests).
|
|
3201
|
+
*/
|
|
3202
|
+
tally(targetClientMsgId) {
|
|
3203
|
+
const byUser = this.states.get(targetClientMsgId);
|
|
3204
|
+
if (byUser === void 0) return {};
|
|
3205
|
+
const out = /* @__PURE__ */ new Map();
|
|
3206
|
+
for (const [userId, st] of byUser) {
|
|
3207
|
+
if (st.emoji === null) continue;
|
|
3208
|
+
let users = out.get(st.emoji);
|
|
3209
|
+
if (users === void 0) {
|
|
3210
|
+
users = [];
|
|
3211
|
+
out.set(st.emoji, users);
|
|
3212
|
+
}
|
|
3213
|
+
users.push(userId);
|
|
3214
|
+
}
|
|
3215
|
+
const result = {};
|
|
3216
|
+
for (const [emoji, users] of out) {
|
|
3217
|
+
result[emoji] = users.sort();
|
|
3218
|
+
}
|
|
3219
|
+
return result;
|
|
3220
|
+
}
|
|
3221
|
+
};
|
|
3222
|
+
|
|
3092
3223
|
// src/messaging/chat.ts
|
|
3093
3224
|
var Chat = class {
|
|
3094
3225
|
/** Stable, URL/log-safe id (the grp_ display id once active; a reserved local id while draft). */
|
|
@@ -3107,6 +3238,8 @@ var Chat = class {
|
|
|
3107
3238
|
seenKeys = /* @__PURE__ */ new Set();
|
|
3108
3239
|
/** Index: clientMsgId → { text, senderUserId } for resolveReply lookups. */
|
|
3109
3240
|
byClientMsgId = /* @__PURE__ */ new Map();
|
|
3241
|
+
/** The single authoritative reaction fold for this chat (live + own-send + history). */
|
|
3242
|
+
reactionFold = new ReactionFold();
|
|
3110
3243
|
loadedEarliestSeq = null;
|
|
3111
3244
|
historyLoaded = false;
|
|
3112
3245
|
wired = false;
|
|
@@ -3213,7 +3346,7 @@ var Chat = class {
|
|
|
3213
3346
|
const key = this.internalKey(m.serverSeq);
|
|
3214
3347
|
if (this.seenKeys.has(key)) continue;
|
|
3215
3348
|
this.seenKeys.add(key);
|
|
3216
|
-
this.messageList.push(m);
|
|
3349
|
+
this.messageList.push(this.applyReactionTally(m));
|
|
3217
3350
|
changed = true;
|
|
3218
3351
|
this.loadedEarliestSeq = Math.min(this.loadedEarliestSeq ?? m.serverSeq, m.serverSeq);
|
|
3219
3352
|
}
|
|
@@ -3238,6 +3371,22 @@ var Chat = class {
|
|
|
3238
3371
|
senderUser = await this.backend.userIdForDevice(this._group, incoming.senderDeviceId);
|
|
3239
3372
|
}
|
|
3240
3373
|
const direction = senderUser !== null && senderUser === this.backend.selfUserId ? "outgoing" : "incoming";
|
|
3374
|
+
if (incoming.envelopeType === "reaction" && incoming.reaction) {
|
|
3375
|
+
const actorUserId = direction === "outgoing" ? this.backend.selfUserId : senderUser;
|
|
3376
|
+
if (actorUserId !== null) {
|
|
3377
|
+
this.reactionFold.ingest({
|
|
3378
|
+
targetClientMsgId: incoming.reaction.targetClientMsgId,
|
|
3379
|
+
actorUserId,
|
|
3380
|
+
emoji: incoming.reaction.emoji,
|
|
3381
|
+
op: incoming.reaction.op,
|
|
3382
|
+
epoch: incoming.epoch,
|
|
3383
|
+
serverSeq: incoming.serverSeq,
|
|
3384
|
+
eventClientMsgId: incoming.clientMsgId
|
|
3385
|
+
});
|
|
3386
|
+
this.recomputeReactions(incoming.reaction.targetClientMsgId);
|
|
3387
|
+
}
|
|
3388
|
+
return;
|
|
3389
|
+
}
|
|
3241
3390
|
const incomingClientMsgId = incoming.clientMsgId;
|
|
3242
3391
|
const incomingReplyRef = incoming.replyRef;
|
|
3243
3392
|
let resolvedReplyTo = null;
|
|
@@ -3253,7 +3402,10 @@ var Chat = class {
|
|
|
3253
3402
|
serverSeq: incoming.serverSeq,
|
|
3254
3403
|
sentAt: incoming.receivedAt,
|
|
3255
3404
|
clientMsgId: incomingClientMsgId,
|
|
3256
|
-
replyTo: resolvedReplyTo
|
|
3405
|
+
replyTo: resolvedReplyTo,
|
|
3406
|
+
// Attach any tally already folded for this message (a reaction that arrived
|
|
3407
|
+
// BEFORE its target — the dangling case — renders the moment the target lands).
|
|
3408
|
+
reactions: incomingClientMsgId ? this.reactionFold.tally(incomingClientMsgId) : {}
|
|
3257
3409
|
};
|
|
3258
3410
|
if (incomingClientMsgId && incoming.text !== null) {
|
|
3259
3411
|
this.byClientMsgId.set(incomingClientMsgId, {
|
|
@@ -3269,6 +3421,35 @@ var Chat = class {
|
|
|
3269
3421
|
);
|
|
3270
3422
|
this.emit();
|
|
3271
3423
|
}
|
|
3424
|
+
/**
|
|
3425
|
+
* Rebuild the target message's `reactions` from the authoritative fold and
|
|
3426
|
+
* re-emit. No-op when the target isn't present yet (its tally is attached the
|
|
3427
|
+
* moment it lands, via the append/merge paths) or when the tally is unchanged.
|
|
3428
|
+
*/
|
|
3429
|
+
recomputeReactions(targetClientMsgId) {
|
|
3430
|
+
if (!targetClientMsgId) return;
|
|
3431
|
+
const tally = this.reactionFold.tally(targetClientMsgId);
|
|
3432
|
+
let changed = false;
|
|
3433
|
+
this.messageList = this.messageList.map((m) => {
|
|
3434
|
+
if (m.clientMsgId !== targetClientMsgId) return m;
|
|
3435
|
+
if (sameReactions(m.reactions, tally)) return m;
|
|
3436
|
+
changed = true;
|
|
3437
|
+
return { ...m, reactions: tally };
|
|
3438
|
+
});
|
|
3439
|
+
if (changed) this.emit();
|
|
3440
|
+
}
|
|
3441
|
+
/**
|
|
3442
|
+
* Overlay the authoritative fold's tally onto a message as it is appended/merged.
|
|
3443
|
+
* The Chat fold WINS when it has a non-empty tally; otherwise the tally already
|
|
3444
|
+
* attached upstream (the coordinator's page-local history fold) is preserved.
|
|
3445
|
+
*/
|
|
3446
|
+
applyReactionTally(m) {
|
|
3447
|
+
if (!m.clientMsgId) return m;
|
|
3448
|
+
const tally = this.reactionFold.tally(m.clientMsgId);
|
|
3449
|
+
if (Object.keys(tally).length === 0) return m;
|
|
3450
|
+
if (sameReactions(m.reactions, tally)) return m;
|
|
3451
|
+
return { ...m, reactions: tally };
|
|
3452
|
+
}
|
|
3272
3453
|
/** @internal — called by the backend's conv subscription. */
|
|
3273
3454
|
applyConv(event, payload) {
|
|
3274
3455
|
const userId = typeof payload.user_id === "string" ? payload.user_id : null;
|
|
@@ -3411,7 +3592,10 @@ var Chat = class {
|
|
|
3411
3592
|
serverSeq: receipt.serverSeq,
|
|
3412
3593
|
sentAt: /* @__PURE__ */ new Date(),
|
|
3413
3594
|
clientMsgId,
|
|
3414
|
-
replyTo: resolvedReplyTo
|
|
3595
|
+
replyTo: resolvedReplyTo,
|
|
3596
|
+
// Attach any tally already folded for this own-sent message (rare, but keeps
|
|
3597
|
+
// the dangling-target invariant uniform across every append path).
|
|
3598
|
+
reactions: clientMsgId ? this.reactionFold.tally(clientMsgId) : {}
|
|
3415
3599
|
});
|
|
3416
3600
|
this.messageList.sort((a, b) => a.serverSeq - b.serverSeq);
|
|
3417
3601
|
this.emit();
|
|
@@ -3458,7 +3642,53 @@ var Chat = class {
|
|
|
3458
3642
|
this.readWatermark = Math.max(this.readWatermark, message.serverSeq);
|
|
3459
3643
|
this.emit();
|
|
3460
3644
|
}
|
|
3645
|
+
// ── Reactions ──
|
|
3646
|
+
/** Add an emoji reaction to a message. No-op if the message isn't reactable
|
|
3647
|
+
* (empty clientMsgId — a legacy/system row). The reaction folds locally with
|
|
3648
|
+
* the server receipt's `(epoch, serverSeq)` so the target's tally updates
|
|
3649
|
+
* instantly; the durable echo on the next pump is a fold no-op (dedup on the
|
|
3650
|
+
* SAME wire clientMsgId). Never appends a bubble. */
|
|
3651
|
+
async react(message, emoji) {
|
|
3652
|
+
await this.sendReaction(message, emoji, "add");
|
|
3653
|
+
}
|
|
3654
|
+
/** Remove this user's emoji reaction from a message (op:'remove'). */
|
|
3655
|
+
async unreact(message, emoji) {
|
|
3656
|
+
await this.sendReaction(message, emoji, "remove");
|
|
3657
|
+
}
|
|
3658
|
+
async sendReaction(message, emoji, op) {
|
|
3659
|
+
if (!message.clientMsgId) return;
|
|
3660
|
+
const group = await this.materializeIfNeeded();
|
|
3661
|
+
const clientMsgId = mintClientMsgId();
|
|
3662
|
+
const { receipt } = await this.backend.sendReaction(group, {
|
|
3663
|
+
clientMsgId,
|
|
3664
|
+
targetClientMsgId: message.clientMsgId,
|
|
3665
|
+
emoji,
|
|
3666
|
+
op
|
|
3667
|
+
});
|
|
3668
|
+
this.reactionFold.ingest({
|
|
3669
|
+
targetClientMsgId: message.clientMsgId,
|
|
3670
|
+
actorUserId: this.backend.selfUserId,
|
|
3671
|
+
emoji,
|
|
3672
|
+
op,
|
|
3673
|
+
epoch: receipt.epoch,
|
|
3674
|
+
serverSeq: receipt.serverSeq,
|
|
3675
|
+
eventClientMsgId: clientMsgId
|
|
3676
|
+
});
|
|
3677
|
+
this.recomputeReactions(message.clientMsgId);
|
|
3678
|
+
}
|
|
3461
3679
|
};
|
|
3680
|
+
function sameReactions(a, b) {
|
|
3681
|
+
const ak = Object.keys(a);
|
|
3682
|
+
const bk = Object.keys(b);
|
|
3683
|
+
if (ak.length !== bk.length) return false;
|
|
3684
|
+
for (const k of ak) {
|
|
3685
|
+
const av = a[k];
|
|
3686
|
+
const bv = b[k];
|
|
3687
|
+
if (!bv || av === void 0 || av.length !== bv.length) return false;
|
|
3688
|
+
for (let i = 0; i < av.length; i++) if (av[i] !== bv[i]) return false;
|
|
3689
|
+
}
|
|
3690
|
+
return true;
|
|
3691
|
+
}
|
|
3462
3692
|
|
|
3463
3693
|
// src/messaging/delivery-source.ts
|
|
3464
3694
|
var MessageHub = class {
|
|
@@ -3617,7 +3847,9 @@ var MessageDeliverySource = class {
|
|
|
3617
3847
|
try {
|
|
3618
3848
|
const received = await this.engine.processIncoming(fromBase64(group.rfcGroupId), blob);
|
|
3619
3849
|
if (received.type === "application") {
|
|
3620
|
-
const
|
|
3850
|
+
const decoded = decodeEnvelope(received.data);
|
|
3851
|
+
const { text, clientMsgId, replyTo } = decoded;
|
|
3852
|
+
const isReaction = decoded.type === "reaction" && decoded.reaction != null;
|
|
3621
3853
|
const stored = {
|
|
3622
3854
|
id: `${group.rfcGroupId}#${row.server_seq}`,
|
|
3623
3855
|
direction: "incoming",
|
|
@@ -3632,7 +3864,19 @@ var MessageDeliverySource = class {
|
|
|
3632
3864
|
previewBody: replyTo.preview?.body ?? null,
|
|
3633
3865
|
previewAuthorUserId: replyTo.preview?.author_user_id ?? null,
|
|
3634
3866
|
previewKind: replyTo.preview?.kind ?? "text"
|
|
3635
|
-
} : null
|
|
3867
|
+
} : null,
|
|
3868
|
+
// Thread the reaction discriminator + fields through the persisted row so
|
|
3869
|
+
// a reaction folded LIVE re-folds onto its target after a reload (the
|
|
3870
|
+
// reload-parity boundary — mirrors iOS T3). Omitted for non-reactions →
|
|
3871
|
+
// old rows hydrate as `'text'`/no-reaction (backward-compat).
|
|
3872
|
+
...isReaction && decoded.reaction ? {
|
|
3873
|
+
envelopeType: "reaction",
|
|
3874
|
+
reaction: {
|
|
3875
|
+
targetClientMsgId: decoded.reaction.targetClientMsgId,
|
|
3876
|
+
emoji: decoded.reaction.emoji,
|
|
3877
|
+
op: decoded.reaction.op
|
|
3878
|
+
}
|
|
3879
|
+
} : {}
|
|
3636
3880
|
};
|
|
3637
3881
|
try {
|
|
3638
3882
|
await this.messageStore.append(group.rfcGroupId, stored);
|
|
@@ -3648,7 +3892,9 @@ var MessageDeliverySource = class {
|
|
|
3648
3892
|
serverSeq: row.server_seq,
|
|
3649
3893
|
receivedAt: /* @__PURE__ */ new Date(),
|
|
3650
3894
|
clientMsgId,
|
|
3651
|
-
replyRef: replyTo
|
|
3895
|
+
replyRef: replyTo,
|
|
3896
|
+
envelopeType: decoded.type ?? "text",
|
|
3897
|
+
reaction: isReaction ? decoded.reaction : null
|
|
3652
3898
|
});
|
|
3653
3899
|
return true;
|
|
3654
3900
|
}
|
|
@@ -5638,7 +5884,6 @@ var MessagingCoordinator = class {
|
|
|
5638
5884
|
await r.groups.addMember(group, userId);
|
|
5639
5885
|
}
|
|
5640
5886
|
}
|
|
5641
|
-
r.registry.register(group);
|
|
5642
5887
|
void r.catalog.upsert({
|
|
5643
5888
|
displayId: group.displayId,
|
|
5644
5889
|
rfcGroupId: group.rfcGroupId,
|
|
@@ -5653,46 +5898,14 @@ var MessagingCoordinator = class {
|
|
|
5653
5898
|
const r = await this.resolve();
|
|
5654
5899
|
return r.groups.sendText(group, text, replyTo);
|
|
5655
5900
|
}
|
|
5901
|
+
async sendReaction(group, args) {
|
|
5902
|
+
const r = await this.resolve();
|
|
5903
|
+
return r.groups.sendReaction(group, args);
|
|
5904
|
+
}
|
|
5656
5905
|
async history(group, limit, before) {
|
|
5657
5906
|
const r = await this.resolve();
|
|
5658
5907
|
const rows = await r.messageStore.history(group.rfcGroupId, limit, before);
|
|
5659
|
-
|
|
5660
|
-
for (const s of rows) {
|
|
5661
|
-
const cid = s.clientMsgId ?? "";
|
|
5662
|
-
if (cid && s.text !== null) {
|
|
5663
|
-
const senderUserId = s.direction === "outgoing" ? this.selfUserId : "";
|
|
5664
|
-
lookup.set(cid, { text: s.text, senderUserId });
|
|
5665
|
-
}
|
|
5666
|
-
}
|
|
5667
|
-
return rows.map((s) => this.toChatMessage(group, s, (id) => lookup.get(id) ?? null));
|
|
5668
|
-
}
|
|
5669
|
-
toChatMessage(group, s, lookup) {
|
|
5670
|
-
const clientMsgId = s.clientMsgId ?? "";
|
|
5671
|
-
let replyTo = null;
|
|
5672
|
-
if (s.replyTo && lookup) {
|
|
5673
|
-
const ref = {
|
|
5674
|
-
v: 1,
|
|
5675
|
-
client_msg_id: s.replyTo.clientMsgId,
|
|
5676
|
-
preview: {
|
|
5677
|
-
kind: s.replyTo.previewKind,
|
|
5678
|
-
author_user_id: s.replyTo.previewAuthorUserId ?? "",
|
|
5679
|
-
body: s.replyTo.previewBody ?? void 0,
|
|
5680
|
-
body_truncated: false
|
|
5681
|
-
}
|
|
5682
|
-
};
|
|
5683
|
-
replyTo = resolveReply(ref, lookup);
|
|
5684
|
-
}
|
|
5685
|
-
return {
|
|
5686
|
-
id: `${group.displayId}#${s.serverSeq}`,
|
|
5687
|
-
kind: s.text != null ? "text" : "system",
|
|
5688
|
-
direction: s.direction,
|
|
5689
|
-
senderUserId: s.direction === "outgoing" ? this.selfUserId : null,
|
|
5690
|
-
text: s.text,
|
|
5691
|
-
serverSeq: s.serverSeq,
|
|
5692
|
-
sentAt: new Date(s.at),
|
|
5693
|
-
clientMsgId,
|
|
5694
|
-
replyTo
|
|
5695
|
-
};
|
|
5908
|
+
return projectHistory(group.displayId, rows, this.selfUserId);
|
|
5696
5909
|
}
|
|
5697
5910
|
async members(group) {
|
|
5698
5911
|
const r = await this.resolve();
|
|
@@ -5769,6 +5982,64 @@ var MessagingCoordinator = class {
|
|
|
5769
5982
|
return res.devices.map((d) => d.device_id);
|
|
5770
5983
|
}
|
|
5771
5984
|
};
|
|
5985
|
+
function projectHistory(displayId, rows, selfUserId, resolveActor) {
|
|
5986
|
+
const fold = new ReactionFold();
|
|
5987
|
+
for (const s of rows) {
|
|
5988
|
+
if (s.envelopeType !== "reaction" || !s.reaction) continue;
|
|
5989
|
+
const actor = s.direction === "outgoing" ? selfUserId : resolveActor?.(s.senderDeviceId ?? null) ?? null;
|
|
5990
|
+
if (actor === null) continue;
|
|
5991
|
+
fold.ingest({
|
|
5992
|
+
targetClientMsgId: s.reaction.targetClientMsgId,
|
|
5993
|
+
actorUserId: actor,
|
|
5994
|
+
emoji: s.reaction.emoji,
|
|
5995
|
+
op: s.reaction.op,
|
|
5996
|
+
epoch: s.epoch,
|
|
5997
|
+
serverSeq: s.serverSeq,
|
|
5998
|
+
eventClientMsgId: s.clientMsgId ?? `${s.id}`
|
|
5999
|
+
});
|
|
6000
|
+
}
|
|
6001
|
+
const lookup = /* @__PURE__ */ new Map();
|
|
6002
|
+
for (const s of rows) {
|
|
6003
|
+
if (s.envelopeType === "reaction") continue;
|
|
6004
|
+
const cid = s.clientMsgId ?? "";
|
|
6005
|
+
if (cid && s.text !== null) {
|
|
6006
|
+
const senderUserId = s.direction === "outgoing" ? selfUserId : "";
|
|
6007
|
+
lookup.set(cid, { text: s.text, senderUserId });
|
|
6008
|
+
}
|
|
6009
|
+
}
|
|
6010
|
+
const out = [];
|
|
6011
|
+
for (const s of rows) {
|
|
6012
|
+
if (s.envelopeType === "reaction") continue;
|
|
6013
|
+
const clientMsgId = s.clientMsgId ?? "";
|
|
6014
|
+
let replyTo = null;
|
|
6015
|
+
if (s.replyTo) {
|
|
6016
|
+
const ref = {
|
|
6017
|
+
v: 1,
|
|
6018
|
+
client_msg_id: s.replyTo.clientMsgId,
|
|
6019
|
+
preview: {
|
|
6020
|
+
kind: s.replyTo.previewKind,
|
|
6021
|
+
author_user_id: s.replyTo.previewAuthorUserId ?? "",
|
|
6022
|
+
body: s.replyTo.previewBody ?? void 0,
|
|
6023
|
+
body_truncated: false
|
|
6024
|
+
}
|
|
6025
|
+
};
|
|
6026
|
+
replyTo = resolveReply(ref, (id) => lookup.get(id) ?? null);
|
|
6027
|
+
}
|
|
6028
|
+
out.push({
|
|
6029
|
+
id: `${displayId}#${s.serverSeq}`,
|
|
6030
|
+
kind: s.text != null ? "text" : "system",
|
|
6031
|
+
direction: s.direction,
|
|
6032
|
+
senderUserId: s.direction === "outgoing" ? selfUserId : null,
|
|
6033
|
+
text: s.text,
|
|
6034
|
+
serverSeq: s.serverSeq,
|
|
6035
|
+
sentAt: new Date(s.at),
|
|
6036
|
+
clientMsgId,
|
|
6037
|
+
replyTo,
|
|
6038
|
+
reactions: clientMsgId ? fold.tally(clientMsgId) : {}
|
|
6039
|
+
});
|
|
6040
|
+
}
|
|
6041
|
+
return out;
|
|
6042
|
+
}
|
|
5772
6043
|
|
|
5773
6044
|
// src/messaging/facade.ts
|
|
5774
6045
|
var PalbeMessaging = class {
|
|
@@ -6502,7 +6773,7 @@ function defaultSessionStorage(key) {
|
|
|
6502
6773
|
}
|
|
6503
6774
|
|
|
6504
6775
|
// src/version.ts
|
|
6505
|
-
var VERSION = "1.
|
|
6776
|
+
var VERSION = "1.2.0";
|
|
6506
6777
|
|
|
6507
6778
|
// src/runtime.ts
|
|
6508
6779
|
function buildRuntime(config) {
|