@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.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.1";
|
|
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.1";
|
|
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 {
|
|
@@ -3488,6 +3718,11 @@ var MessageHub = class {
|
|
|
3488
3718
|
};
|
|
3489
3719
|
}
|
|
3490
3720
|
};
|
|
3721
|
+
function decodeSenderDeviceId(sender) {
|
|
3722
|
+
if (sender.length === 0) return null;
|
|
3723
|
+
const id = decodeUtf8(sender);
|
|
3724
|
+
return id.length > 0 ? id : null;
|
|
3725
|
+
}
|
|
3491
3726
|
var MessageDeliverySource = class {
|
|
3492
3727
|
constructor(rt, engine, hub, registry, messageStore, deviceId, selfUserId) {
|
|
3493
3728
|
this.rt = rt;
|
|
@@ -3617,12 +3852,15 @@ var MessageDeliverySource = class {
|
|
|
3617
3852
|
try {
|
|
3618
3853
|
const received = await this.engine.processIncoming(fromBase64(group.rfcGroupId), blob);
|
|
3619
3854
|
if (received.type === "application") {
|
|
3620
|
-
const
|
|
3855
|
+
const decoded = decodeEnvelope(received.data);
|
|
3856
|
+
const { text, clientMsgId, replyTo } = decoded;
|
|
3857
|
+
const isReaction = decoded.type === "reaction" && decoded.reaction != null;
|
|
3858
|
+
const senderDeviceId = row.sender_device_id ?? decodeSenderDeviceId(received.sender);
|
|
3621
3859
|
const stored = {
|
|
3622
3860
|
id: `${group.rfcGroupId}#${row.server_seq}`,
|
|
3623
3861
|
direction: "incoming",
|
|
3624
3862
|
text,
|
|
3625
|
-
senderDeviceId
|
|
3863
|
+
senderDeviceId,
|
|
3626
3864
|
epoch: row.epoch,
|
|
3627
3865
|
serverSeq: row.server_seq,
|
|
3628
3866
|
at: Date.now(),
|
|
@@ -3632,7 +3870,19 @@ var MessageDeliverySource = class {
|
|
|
3632
3870
|
previewBody: replyTo.preview?.body ?? null,
|
|
3633
3871
|
previewAuthorUserId: replyTo.preview?.author_user_id ?? null,
|
|
3634
3872
|
previewKind: replyTo.preview?.kind ?? "text"
|
|
3635
|
-
} : null
|
|
3873
|
+
} : null,
|
|
3874
|
+
// Thread the reaction discriminator + fields through the persisted row so
|
|
3875
|
+
// a reaction folded LIVE re-folds onto its target after a reload (the
|
|
3876
|
+
// reload-parity boundary — mirrors iOS T3). Omitted for non-reactions →
|
|
3877
|
+
// old rows hydrate as `'text'`/no-reaction (backward-compat).
|
|
3878
|
+
...isReaction && decoded.reaction ? {
|
|
3879
|
+
envelopeType: "reaction",
|
|
3880
|
+
reaction: {
|
|
3881
|
+
targetClientMsgId: decoded.reaction.targetClientMsgId,
|
|
3882
|
+
emoji: decoded.reaction.emoji,
|
|
3883
|
+
op: decoded.reaction.op
|
|
3884
|
+
}
|
|
3885
|
+
} : {}
|
|
3636
3886
|
};
|
|
3637
3887
|
try {
|
|
3638
3888
|
await this.messageStore.append(group.rfcGroupId, stored);
|
|
@@ -3643,12 +3893,14 @@ var MessageDeliverySource = class {
|
|
|
3643
3893
|
kind: "application",
|
|
3644
3894
|
group,
|
|
3645
3895
|
text,
|
|
3646
|
-
senderDeviceId
|
|
3896
|
+
senderDeviceId,
|
|
3647
3897
|
epoch: row.epoch,
|
|
3648
3898
|
serverSeq: row.server_seq,
|
|
3649
3899
|
receivedAt: /* @__PURE__ */ new Date(),
|
|
3650
3900
|
clientMsgId,
|
|
3651
|
-
replyRef: replyTo
|
|
3901
|
+
replyRef: replyTo,
|
|
3902
|
+
envelopeType: decoded.type ?? "text",
|
|
3903
|
+
reaction: isReaction ? decoded.reaction : null
|
|
3652
3904
|
});
|
|
3653
3905
|
return true;
|
|
3654
3906
|
}
|
|
@@ -5652,46 +5904,14 @@ var MessagingCoordinator = class {
|
|
|
5652
5904
|
const r = await this.resolve();
|
|
5653
5905
|
return r.groups.sendText(group, text, replyTo);
|
|
5654
5906
|
}
|
|
5907
|
+
async sendReaction(group, args) {
|
|
5908
|
+
const r = await this.resolve();
|
|
5909
|
+
return r.groups.sendReaction(group, args);
|
|
5910
|
+
}
|
|
5655
5911
|
async history(group, limit, before) {
|
|
5656
5912
|
const r = await this.resolve();
|
|
5657
5913
|
const rows = await r.messageStore.history(group.rfcGroupId, limit, before);
|
|
5658
|
-
|
|
5659
|
-
for (const s of rows) {
|
|
5660
|
-
const cid = s.clientMsgId ?? "";
|
|
5661
|
-
if (cid && s.text !== null) {
|
|
5662
|
-
const senderUserId = s.direction === "outgoing" ? this.selfUserId : "";
|
|
5663
|
-
lookup.set(cid, { text: s.text, senderUserId });
|
|
5664
|
-
}
|
|
5665
|
-
}
|
|
5666
|
-
return rows.map((s) => this.toChatMessage(group, s, (id) => lookup.get(id) ?? null));
|
|
5667
|
-
}
|
|
5668
|
-
toChatMessage(group, s, lookup) {
|
|
5669
|
-
const clientMsgId = s.clientMsgId ?? "";
|
|
5670
|
-
let replyTo = null;
|
|
5671
|
-
if (s.replyTo && lookup) {
|
|
5672
|
-
const ref = {
|
|
5673
|
-
v: 1,
|
|
5674
|
-
client_msg_id: s.replyTo.clientMsgId,
|
|
5675
|
-
preview: {
|
|
5676
|
-
kind: s.replyTo.previewKind,
|
|
5677
|
-
author_user_id: s.replyTo.previewAuthorUserId ?? "",
|
|
5678
|
-
body: s.replyTo.previewBody ?? void 0,
|
|
5679
|
-
body_truncated: false
|
|
5680
|
-
}
|
|
5681
|
-
};
|
|
5682
|
-
replyTo = resolveReply(ref, lookup);
|
|
5683
|
-
}
|
|
5684
|
-
return {
|
|
5685
|
-
id: `${group.displayId}#${s.serverSeq}`,
|
|
5686
|
-
kind: s.text != null ? "text" : "system",
|
|
5687
|
-
direction: s.direction,
|
|
5688
|
-
senderUserId: s.direction === "outgoing" ? this.selfUserId : null,
|
|
5689
|
-
text: s.text,
|
|
5690
|
-
serverSeq: s.serverSeq,
|
|
5691
|
-
sentAt: new Date(s.at),
|
|
5692
|
-
clientMsgId,
|
|
5693
|
-
replyTo
|
|
5694
|
-
};
|
|
5914
|
+
return projectHistory(group.displayId, rows, this.selfUserId);
|
|
5695
5915
|
}
|
|
5696
5916
|
async members(group) {
|
|
5697
5917
|
const r = await this.resolve();
|
|
@@ -5768,6 +5988,64 @@ var MessagingCoordinator = class {
|
|
|
5768
5988
|
return res.devices.map((d) => d.device_id);
|
|
5769
5989
|
}
|
|
5770
5990
|
};
|
|
5991
|
+
function projectHistory(displayId, rows, selfUserId, resolveActor) {
|
|
5992
|
+
const fold = new ReactionFold();
|
|
5993
|
+
for (const s of rows) {
|
|
5994
|
+
if (s.envelopeType !== "reaction" || !s.reaction) continue;
|
|
5995
|
+
const actor = s.direction === "outgoing" ? selfUserId : resolveActor?.(s.senderDeviceId ?? null) ?? null;
|
|
5996
|
+
if (actor === null) continue;
|
|
5997
|
+
fold.ingest({
|
|
5998
|
+
targetClientMsgId: s.reaction.targetClientMsgId,
|
|
5999
|
+
actorUserId: actor,
|
|
6000
|
+
emoji: s.reaction.emoji,
|
|
6001
|
+
op: s.reaction.op,
|
|
6002
|
+
epoch: s.epoch,
|
|
6003
|
+
serverSeq: s.serverSeq,
|
|
6004
|
+
eventClientMsgId: s.clientMsgId ?? `${s.id}`
|
|
6005
|
+
});
|
|
6006
|
+
}
|
|
6007
|
+
const lookup = /* @__PURE__ */ new Map();
|
|
6008
|
+
for (const s of rows) {
|
|
6009
|
+
if (s.envelopeType === "reaction") continue;
|
|
6010
|
+
const cid = s.clientMsgId ?? "";
|
|
6011
|
+
if (cid && s.text !== null) {
|
|
6012
|
+
const senderUserId = s.direction === "outgoing" ? selfUserId : "";
|
|
6013
|
+
lookup.set(cid, { text: s.text, senderUserId });
|
|
6014
|
+
}
|
|
6015
|
+
}
|
|
6016
|
+
const out = [];
|
|
6017
|
+
for (const s of rows) {
|
|
6018
|
+
if (s.envelopeType === "reaction") continue;
|
|
6019
|
+
const clientMsgId = s.clientMsgId ?? "";
|
|
6020
|
+
let replyTo = null;
|
|
6021
|
+
if (s.replyTo) {
|
|
6022
|
+
const ref = {
|
|
6023
|
+
v: 1,
|
|
6024
|
+
client_msg_id: s.replyTo.clientMsgId,
|
|
6025
|
+
preview: {
|
|
6026
|
+
kind: s.replyTo.previewKind,
|
|
6027
|
+
author_user_id: s.replyTo.previewAuthorUserId ?? "",
|
|
6028
|
+
body: s.replyTo.previewBody ?? void 0,
|
|
6029
|
+
body_truncated: false
|
|
6030
|
+
}
|
|
6031
|
+
};
|
|
6032
|
+
replyTo = resolveReply(ref, (id) => lookup.get(id) ?? null);
|
|
6033
|
+
}
|
|
6034
|
+
out.push({
|
|
6035
|
+
id: `${displayId}#${s.serverSeq}`,
|
|
6036
|
+
kind: s.text != null ? "text" : "system",
|
|
6037
|
+
direction: s.direction,
|
|
6038
|
+
senderUserId: s.direction === "outgoing" ? selfUserId : null,
|
|
6039
|
+
text: s.text,
|
|
6040
|
+
serverSeq: s.serverSeq,
|
|
6041
|
+
sentAt: new Date(s.at),
|
|
6042
|
+
clientMsgId,
|
|
6043
|
+
replyTo,
|
|
6044
|
+
reactions: clientMsgId ? fold.tally(clientMsgId) : {}
|
|
6045
|
+
});
|
|
6046
|
+
}
|
|
6047
|
+
return out;
|
|
6048
|
+
}
|
|
5771
6049
|
|
|
5772
6050
|
// src/messaging/facade.ts
|
|
5773
6051
|
var PalbeMessaging = class {
|
|
@@ -6501,7 +6779,7 @@ function defaultSessionStorage(key) {
|
|
|
6501
6779
|
}
|
|
6502
6780
|
|
|
6503
6781
|
// src/version.ts
|
|
6504
|
-
var VERSION = "1.
|
|
6782
|
+
var VERSION = "1.2.1";
|
|
6505
6783
|
|
|
6506
6784
|
// src/runtime.ts
|
|
6507
6785
|
function buildRuntime(config) {
|