@openmarket/rooms-client 0.4.1 → 0.5.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/agent/sidebar-model.d.ts +3 -0
- package/dist/agent/sidebar-model.js +31 -0
- package/dist/client.d.ts +29 -6
- package/dist/client.js +104 -22
- package/dist/shared/rooms-protocol.d.ts +80 -1
- package/dist/shared/rooms-protocol.js +6 -0
- package/dist/social-client.d.ts +6 -0
- package/dist/social-client.js +8 -0
- package/dist/social-types.d.ts +2 -0
- package/dist/types.d.ts +1 -1
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/package.json +7 -2
- package/src/agent/sidebar-model.ts +28 -0
- package/src/client.ts +175 -29
- package/src/shared/rooms-protocol.ts +95 -1
- package/src/social-client.ts +12 -0
- package/src/social-types.ts +2 -0
- package/src/types.ts +8 -0
- package/src/version.ts +2 -2
|
@@ -74,6 +74,9 @@ export declare class SidebarModel {
|
|
|
74
74
|
constructor(deps: SidebarModelDeps);
|
|
75
75
|
snapshot(): SidebarSnapshot;
|
|
76
76
|
onUpdate(listener: () => void): () => void;
|
|
77
|
+
/** Apply a same-user realtime DM counter without refetching the full room
|
|
78
|
+
* directory. Username is the stable DM key used by the sidebar model. */
|
|
79
|
+
applyDmUnread(username: string, unread: number): void;
|
|
77
80
|
/**
|
|
78
81
|
* Post-mutation refresh: if a poll-triggered load is already in flight it
|
|
79
82
|
* may predate the caller's writes, so wait it out and load AGAIN. Readers
|
|
@@ -112,6 +112,34 @@ export class SidebarModel {
|
|
|
112
112
|
this.listeners.delete(listener);
|
|
113
113
|
};
|
|
114
114
|
}
|
|
115
|
+
/** Apply a same-user realtime DM counter without refetching the full room
|
|
116
|
+
* directory. Username is the stable DM key used by the sidebar model. */
|
|
117
|
+
applyDmUnread(username, unread) {
|
|
118
|
+
const key = normalizeDmUsername(username);
|
|
119
|
+
if (!key)
|
|
120
|
+
return;
|
|
121
|
+
const normalizedUnread = Number.isFinite(unread) ? Math.max(0, Math.floor(unread)) : 0;
|
|
122
|
+
let found = false;
|
|
123
|
+
let changed = false;
|
|
124
|
+
const next = this.snapshotValue.dms.map((dm) => {
|
|
125
|
+
if (normalizeDmUsername(dm.username) !== key)
|
|
126
|
+
return dm;
|
|
127
|
+
found = true;
|
|
128
|
+
if (dm.unread === normalizedUnread)
|
|
129
|
+
return dm;
|
|
130
|
+
changed = true;
|
|
131
|
+
return { ...dm, unread: normalizedUnread };
|
|
132
|
+
});
|
|
133
|
+
if (!found && normalizedUnread > 0) {
|
|
134
|
+
next.push({ username: username.replace(/^@+/, ""), unread: normalizedUnread });
|
|
135
|
+
changed = true;
|
|
136
|
+
}
|
|
137
|
+
if (!changed)
|
|
138
|
+
return;
|
|
139
|
+
this.snapshotValue = { ...this.snapshotValue, dms: next };
|
|
140
|
+
for (const listener of this.listeners)
|
|
141
|
+
listener();
|
|
142
|
+
}
|
|
115
143
|
/**
|
|
116
144
|
* Post-mutation refresh: if a poll-triggered load is already in flight it
|
|
117
145
|
* may predate the caller's writes, so wait it out and load AGAIN. Readers
|
|
@@ -160,3 +188,6 @@ export class SidebarModel {
|
|
|
160
188
|
});
|
|
161
189
|
}
|
|
162
190
|
}
|
|
191
|
+
function normalizeDmUsername(username) {
|
|
192
|
+
return username.trim().replace(/^@+/, "").toLowerCase();
|
|
193
|
+
}
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type RoomAccessMode, type RoomAttachmentInput, type RoomAuditLogPayload, type RoomAuthSuccessPayload, type RoomBackfillPayload, type RoomClientPlatform, type RoomDmHistoryResultPayload, type RoomDmInboxUpdatedPayload, type RoomDmMessagePayload, type RoomDmOpenedPayload, type RoomFriendPresence, type RoomInvitedPayload, type RoomLedgerEntry, type RoomMemberRemovedPayload, type RoomMemberRoleChangedPayload, type RoomMeta, type RoomPinsPayload, type RoomPresencePayload, type RoomReactorsPayload, type RoomReadStatePayload, type RoomReportedPayload, type RoomRole, type RoomSearchResultsPayload, type RoomSearchScope, type RoomSpace, type RoomThreadPayload, type RoomTopic, type RoomTopicAttention, type RoomTopicResultPayload, type RoomTopicsResultPayload, type RoomUserStatus, type RoomWhoPayload } from "./types.js";
|
|
1
|
+
import { type RoomAccessMode, type RoomAttachmentInput, type RoomAuditLogPayload, type RoomAuthSuccessPayload, type RoomBackfillPayload, type RoomClientPlatform, type RoomDmHistoryResultPayload, type RoomDmInboxUpdatedPayload, type RoomDmMessageDeletedPayload, type RoomDmMessageEditedPayload, type RoomDmMessagePayload, type RoomDmOpenedPayload, type RoomDmPinsResultPayload, type RoomDmReactionUpdatedPayload, type RoomForwardMetadata, type RoomFriendPresence, type RoomInvitedPayload, type RoomLedgerEntry, type RoomMemberRemovedPayload, type RoomMemberRoleChangedPayload, type RoomMeta, type RoomPinsPayload, type RoomPresencePayload, type RoomReactorsPayload, type RoomReadStatePayload, type RoomReportedPayload, type RoomRole, type RoomSearchResultsPayload, type RoomSearchScope, type RoomSpace, type RoomThreadPayload, type RoomTopic, type RoomTopicAttention, type RoomTopicResultPayload, type RoomTopicsResultPayload, type RoomUserStatus, type RoomWhoPayload } from "./types.js";
|
|
2
2
|
import { type RoomWebSocketCtor, RoomWsClient } from "./ws-client.js";
|
|
3
3
|
export interface RoomClientConfig {
|
|
4
4
|
url: string;
|
|
@@ -212,6 +212,10 @@ export interface RoomDmSendRequest {
|
|
|
212
212
|
* URL. Stored on the message's imageUrl; the client signs keys on render.
|
|
213
213
|
* Additive — omitted for text-only sends. */
|
|
214
214
|
imageUrl?: string;
|
|
215
|
+
metadata?: RoomForwardMetadata;
|
|
216
|
+
}
|
|
217
|
+
export interface RoomDmReplyRequest extends RoomDmSendRequest {
|
|
218
|
+
replyTo: string;
|
|
215
219
|
}
|
|
216
220
|
export interface RoomDmReadRequest {
|
|
217
221
|
username?: string;
|
|
@@ -238,6 +242,9 @@ export { workspaceRoomName } from "./names.js";
|
|
|
238
242
|
export declare function listRooms(config: RoomClientConfig, request?: RoomListRequest): Promise<RoomListResult>;
|
|
239
243
|
export declare function createRoom(config: RoomClientConfig, request: RoomCreateRequest): Promise<RoomCreateResult>;
|
|
240
244
|
export declare function inviteRoom(config: RoomClientConfig, request: RoomInviteRequest): Promise<RoomInviteResult>;
|
|
245
|
+
/** Invite over an already-authenticated socket. Long-lived GUI/TUI sessions
|
|
246
|
+
* use this path so a one-shot invite cannot create a second connection. */
|
|
247
|
+
export declare function inviteRoomOnClient(client: RoomWsClient, request: RoomInviteRequest, timeoutMs?: number): Promise<RoomInvitedPayload>;
|
|
241
248
|
export declare function removeRoomMember(config: RoomClientConfig, request: RoomRemoveMemberRequest): Promise<RoomRemoveMemberResult>;
|
|
242
249
|
export declare function reportRoomTarget(config: RoomClientConfig, request: RoomReportRequest): Promise<RoomReportResult>;
|
|
243
250
|
export declare function muteRoomMember(config: RoomClientConfig, request: RoomMuteRequest): Promise<RoomMuteResult>;
|
|
@@ -260,20 +267,20 @@ export declare function openRoomDmOnClient(client: RoomWsClient, request: RoomDm
|
|
|
260
267
|
export declare function requestRoomDmHistoryOnClient(client: RoomWsClient, request: RoomDmHistoryRequest, timeoutMs?: number): Promise<RoomDmHistoryResultPayload>;
|
|
261
268
|
export declare function markRoomDmReadOnClient(client: RoomWsClient, request: RoomDmReadRequest, timeoutMs?: number): Promise<RoomDmInboxUpdatedPayload>;
|
|
262
269
|
export declare function sendRoomDmOnClient(client: RoomWsClient, request: RoomDmSendRequest, timeoutMs?: number): Promise<RoomDmMessagePayload>;
|
|
263
|
-
|
|
264
|
-
|
|
270
|
+
export declare function replyRoomDmOnClient(client: RoomWsClient, request: RoomDmReplyRequest, timeoutMs?: number): Promise<RoomDmMessagePayload>;
|
|
271
|
+
/** Edit one of my DM messages and wait for the correlated relay update. */
|
|
265
272
|
export declare function editRoomDmOnClient(client: RoomWsClient, request: {
|
|
266
273
|
conversationId?: string;
|
|
267
274
|
username?: string;
|
|
268
275
|
messageId: string;
|
|
269
276
|
content: string;
|
|
270
|
-
}):
|
|
277
|
+
}, timeoutMs?: number): Promise<RoomDmMessageEditedPayload>;
|
|
271
278
|
/** Soft-delete one of my DM messages. The relay fans out DM_MESSAGE_DELETED. */
|
|
272
279
|
export declare function deleteRoomDmOnClient(client: RoomWsClient, request: {
|
|
273
280
|
conversationId?: string;
|
|
274
281
|
username?: string;
|
|
275
282
|
messageId: string;
|
|
276
|
-
}):
|
|
283
|
+
}, timeoutMs?: number): Promise<RoomDmMessageDeletedPayload>;
|
|
277
284
|
/** Toggle a reaction on a DM message. The relay fans out DM_REACTION_UPDATED. */
|
|
278
285
|
export declare function reactRoomDmOnClient(client: RoomWsClient, request: {
|
|
279
286
|
conversationId?: string;
|
|
@@ -281,7 +288,23 @@ export declare function reactRoomDmOnClient(client: RoomWsClient, request: {
|
|
|
281
288
|
messageId: string;
|
|
282
289
|
emoji: string;
|
|
283
290
|
op: "add" | "remove";
|
|
291
|
+
}, timeoutMs?: number): Promise<RoomDmReactionUpdatedPayload>;
|
|
292
|
+
/** Set shared pin state for a DM message. The relay fans out DM_PIN_UPDATED. */
|
|
293
|
+
export declare function pinRoomDmOnClient(client: RoomWsClient, request: {
|
|
294
|
+
conversationId?: string;
|
|
295
|
+
username?: string;
|
|
296
|
+
messageId: string;
|
|
284
297
|
}): void;
|
|
298
|
+
export declare function unpinRoomDmOnClient(client: RoomWsClient, request: {
|
|
299
|
+
conversationId?: string;
|
|
300
|
+
username?: string;
|
|
301
|
+
messageId: string;
|
|
302
|
+
}): void;
|
|
303
|
+
export declare function requestRoomDmPinsOnClient(client: RoomWsClient, request: {
|
|
304
|
+
conversationId?: string;
|
|
305
|
+
username?: string;
|
|
306
|
+
limit?: number;
|
|
307
|
+
}, timeoutMs?: number): Promise<RoomDmPinsResultPayload>;
|
|
285
308
|
export declare function peekRoom(config: RoomClientConfig, options: RoomPeekOptions): Promise<RoomBackfillPayload>;
|
|
286
309
|
export declare function sayRoom(config: RoomClientConfig, options: RoomSayOptions): Promise<RoomSayResult>;
|
|
287
310
|
export declare function joinRoom(config: RoomClientConfig, options: RoomJoinOptions): Promise<RoomJoinedSession>;
|
|
@@ -297,7 +320,7 @@ export declare function attachRejoinOnReconnect(client: RoomWsClient, options: R
|
|
|
297
320
|
* (1..128 chars after trim); dedupe on (userId, clientMsgId) is server-side
|
|
298
321
|
* work and NOT implied here.
|
|
299
322
|
*/
|
|
300
|
-
export declare function postRoomMessage(client: RoomWsClient, room: string, text: string, inReplyTo?: number, attachments?: RoomAttachmentInput[], topicId?: string, clientMsgId?: string): string;
|
|
323
|
+
export declare function postRoomMessage(client: RoomWsClient, room: string, text: string, inReplyTo?: number, attachments?: RoomAttachmentInput[], topicId?: string, clientMsgId?: string, metadata?: RoomForwardMetadata): string;
|
|
301
324
|
export declare function sendRoomTyping(client: RoomWsClient, room: string, state: "start" | "stop"): void;
|
|
302
325
|
export declare function sendRoomStatus(client: RoomWsClient, status: RoomUserStatus, statusText?: string | null, platform?: RoomClientPlatform): void;
|
|
303
326
|
/**
|
package/dist/client.js
CHANGED
|
@@ -101,9 +101,7 @@ async function createRoomViaWebSocket(config, request) {
|
|
|
101
101
|
export async function inviteRoom(config, request) {
|
|
102
102
|
// `@user` and `user` are the same person: strip the typing syntax once,
|
|
103
103
|
// before either transport (the relay resolves the bare name).
|
|
104
|
-
const normalized = request
|
|
105
|
-
? { ...request, username: request.username.trim().replace(/^@+/, "") }
|
|
106
|
-
: request;
|
|
104
|
+
const normalized = normalizeRoomInviteRequest(request);
|
|
107
105
|
if (!config.webSocketCtor) {
|
|
108
106
|
try {
|
|
109
107
|
return await inviteRoomViaHttp(config, normalized);
|
|
@@ -119,24 +117,35 @@ async function inviteRoomViaWebSocket(config, request) {
|
|
|
119
117
|
const client = new RoomWsClient(config);
|
|
120
118
|
try {
|
|
121
119
|
const auth = await client.connect();
|
|
122
|
-
|
|
123
|
-
client.send({
|
|
124
|
-
type: RoomClientMessageType.INVITE,
|
|
125
|
-
payload: {
|
|
126
|
-
requestId,
|
|
127
|
-
room: request.room,
|
|
128
|
-
...(request.username ? { username: request.username } : {}),
|
|
129
|
-
...(request.userId ? { userId: request.userId } : {}),
|
|
130
|
-
},
|
|
131
|
-
timestamp: Date.now(),
|
|
132
|
-
});
|
|
133
|
-
const invited = await client.waitFor(RoomServerMessageType.ROOM_INVITED, (msg) => msg.payload.requestId === requestId, undefined, requestId);
|
|
134
|
-
return { auth, invite: invited.payload };
|
|
120
|
+
return { auth, invite: await inviteRoomOnClient(client, request) };
|
|
135
121
|
}
|
|
136
122
|
finally {
|
|
137
123
|
client.close();
|
|
138
124
|
}
|
|
139
125
|
}
|
|
126
|
+
/** Invite over an already-authenticated socket. Long-lived GUI/TUI sessions
|
|
127
|
+
* use this path so a one-shot invite cannot create a second connection. */
|
|
128
|
+
export async function inviteRoomOnClient(client, request, timeoutMs) {
|
|
129
|
+
const normalized = normalizeRoomInviteRequest(request);
|
|
130
|
+
const requestId = randomUUID();
|
|
131
|
+
client.send({
|
|
132
|
+
type: RoomClientMessageType.INVITE,
|
|
133
|
+
payload: {
|
|
134
|
+
requestId,
|
|
135
|
+
room: normalized.room,
|
|
136
|
+
...(normalized.username ? { username: normalized.username } : {}),
|
|
137
|
+
...(normalized.userId ? { userId: normalized.userId } : {}),
|
|
138
|
+
},
|
|
139
|
+
timestamp: Date.now(),
|
|
140
|
+
});
|
|
141
|
+
const invited = await client.waitFor(RoomServerMessageType.ROOM_INVITED, (msg) => msg.payload.requestId === requestId, timeoutMs, requestId);
|
|
142
|
+
return invited.payload;
|
|
143
|
+
}
|
|
144
|
+
function normalizeRoomInviteRequest(request) {
|
|
145
|
+
return request.username
|
|
146
|
+
? { ...request, username: request.username.trim().replace(/^@+/, "") }
|
|
147
|
+
: request;
|
|
148
|
+
}
|
|
140
149
|
export async function removeRoomMember(config, request) {
|
|
141
150
|
if (!config.webSocketCtor) {
|
|
142
151
|
try {
|
|
@@ -480,6 +489,7 @@ export async function sendRoomDmOnClient(client, request, timeoutMs) {
|
|
|
480
489
|
...(request.username ? { username: request.username } : {}),
|
|
481
490
|
...(request.conversationId ? { conversationId: request.conversationId } : {}),
|
|
482
491
|
...(request.imageUrl ? { imageUrl: request.imageUrl } : {}),
|
|
492
|
+
...(request.metadata ? { metadata: request.metadata } : {}),
|
|
483
493
|
},
|
|
484
494
|
timestamp: Date.now(),
|
|
485
495
|
});
|
|
@@ -488,12 +498,35 @@ export async function sendRoomDmOnClient(client, request, timeoutMs) {
|
|
|
488
498
|
msg.payload.message.clientMsgId === clientMsgId, timeoutMs, requestId);
|
|
489
499
|
return sent.payload;
|
|
490
500
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
501
|
+
export async function replyRoomDmOnClient(client, request, timeoutMs) {
|
|
502
|
+
const requestId = randomUUID();
|
|
503
|
+
const clientMsgId = request.clientMsgId ?? randomUUID();
|
|
504
|
+
client.send({
|
|
505
|
+
type: RoomClientMessageType.DM_REPLY,
|
|
506
|
+
payload: {
|
|
507
|
+
requestId,
|
|
508
|
+
content: request.content,
|
|
509
|
+
replyTo: request.replyTo,
|
|
510
|
+
clientMsgId,
|
|
511
|
+
...(request.username ? { username: request.username } : {}),
|
|
512
|
+
...(request.conversationId ? { conversationId: request.conversationId } : {}),
|
|
513
|
+
...(request.imageUrl ? { imageUrl: request.imageUrl } : {}),
|
|
514
|
+
...(request.metadata ? { metadata: request.metadata } : {}),
|
|
515
|
+
},
|
|
516
|
+
timestamp: Date.now(),
|
|
517
|
+
});
|
|
518
|
+
const sent = await client.waitFor(RoomServerMessageType.DM_MESSAGE, (msg) => msg.payload.requestId === requestId ||
|
|
519
|
+
msg.payload.clientMsgId === clientMsgId ||
|
|
520
|
+
msg.payload.message.clientMsgId === clientMsgId, timeoutMs, requestId);
|
|
521
|
+
return sent.payload;
|
|
522
|
+
}
|
|
523
|
+
/** Edit one of my DM messages and wait for the correlated relay update. */
|
|
524
|
+
export async function editRoomDmOnClient(client, request, timeoutMs) {
|
|
525
|
+
const requestId = randomUUID();
|
|
494
526
|
client.send({
|
|
495
527
|
type: RoomClientMessageType.DM_EDIT,
|
|
496
528
|
payload: {
|
|
529
|
+
requestId,
|
|
497
530
|
messageId: request.messageId,
|
|
498
531
|
content: request.content,
|
|
499
532
|
...(request.conversationId ? { conversationId: request.conversationId } : {}),
|
|
@@ -501,24 +534,32 @@ export function editRoomDmOnClient(client, request) {
|
|
|
501
534
|
},
|
|
502
535
|
timestamp: Date.now(),
|
|
503
536
|
});
|
|
537
|
+
const updated = await client.waitFor(RoomServerMessageType.DM_MESSAGE_EDITED, (msg) => msg.payload.requestId === requestId, timeoutMs, requestId);
|
|
538
|
+
return updated.payload;
|
|
504
539
|
}
|
|
505
540
|
/** Soft-delete one of my DM messages. The relay fans out DM_MESSAGE_DELETED. */
|
|
506
|
-
export function deleteRoomDmOnClient(client, request) {
|
|
541
|
+
export async function deleteRoomDmOnClient(client, request, timeoutMs) {
|
|
542
|
+
const requestId = randomUUID();
|
|
507
543
|
client.send({
|
|
508
544
|
type: RoomClientMessageType.DM_DELETE,
|
|
509
545
|
payload: {
|
|
546
|
+
requestId,
|
|
510
547
|
messageId: request.messageId,
|
|
511
548
|
...(request.conversationId ? { conversationId: request.conversationId } : {}),
|
|
512
549
|
...(request.username ? { username: request.username } : {}),
|
|
513
550
|
},
|
|
514
551
|
timestamp: Date.now(),
|
|
515
552
|
});
|
|
553
|
+
const updated = await client.waitFor(RoomServerMessageType.DM_MESSAGE_DELETED, (msg) => msg.payload.requestId === requestId, timeoutMs, requestId);
|
|
554
|
+
return updated.payload;
|
|
516
555
|
}
|
|
517
556
|
/** Toggle a reaction on a DM message. The relay fans out DM_REACTION_UPDATED. */
|
|
518
|
-
export function reactRoomDmOnClient(client, request) {
|
|
557
|
+
export async function reactRoomDmOnClient(client, request, timeoutMs) {
|
|
558
|
+
const requestId = randomUUID();
|
|
519
559
|
client.send({
|
|
520
560
|
type: RoomClientMessageType.DM_REACT,
|
|
521
561
|
payload: {
|
|
562
|
+
requestId,
|
|
522
563
|
messageId: request.messageId,
|
|
523
564
|
emoji: request.emoji,
|
|
524
565
|
op: request.op,
|
|
@@ -527,6 +568,46 @@ export function reactRoomDmOnClient(client, request) {
|
|
|
527
568
|
},
|
|
528
569
|
timestamp: Date.now(),
|
|
529
570
|
});
|
|
571
|
+
const updated = await client.waitFor(RoomServerMessageType.DM_REACTION_UPDATED, (msg) => msg.payload.requestId === requestId, timeoutMs, requestId);
|
|
572
|
+
return updated.payload;
|
|
573
|
+
}
|
|
574
|
+
/** Set shared pin state for a DM message. The relay fans out DM_PIN_UPDATED. */
|
|
575
|
+
export function pinRoomDmOnClient(client, request) {
|
|
576
|
+
client.send({
|
|
577
|
+
type: RoomClientMessageType.DM_PIN,
|
|
578
|
+
payload: {
|
|
579
|
+
messageId: request.messageId,
|
|
580
|
+
...(request.conversationId ? { conversationId: request.conversationId } : {}),
|
|
581
|
+
...(request.username ? { username: request.username } : {}),
|
|
582
|
+
},
|
|
583
|
+
timestamp: Date.now(),
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
export function unpinRoomDmOnClient(client, request) {
|
|
587
|
+
client.send({
|
|
588
|
+
type: RoomClientMessageType.DM_UNPIN,
|
|
589
|
+
payload: {
|
|
590
|
+
messageId: request.messageId,
|
|
591
|
+
...(request.conversationId ? { conversationId: request.conversationId } : {}),
|
|
592
|
+
...(request.username ? { username: request.username } : {}),
|
|
593
|
+
},
|
|
594
|
+
timestamp: Date.now(),
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
export async function requestRoomDmPinsOnClient(client, request, timeoutMs) {
|
|
598
|
+
const requestId = randomUUID();
|
|
599
|
+
client.send({
|
|
600
|
+
type: RoomClientMessageType.DM_PINS,
|
|
601
|
+
payload: {
|
|
602
|
+
requestId,
|
|
603
|
+
...(request.conversationId ? { conversationId: request.conversationId } : {}),
|
|
604
|
+
...(request.username ? { username: request.username } : {}),
|
|
605
|
+
...(request.limit ? { limit: request.limit } : {}),
|
|
606
|
+
},
|
|
607
|
+
timestamp: Date.now(),
|
|
608
|
+
});
|
|
609
|
+
const result = await client.waitFor(RoomServerMessageType.DM_PINS, (message) => message.payload.requestId === requestId, timeoutMs, requestId);
|
|
610
|
+
return result.payload;
|
|
530
611
|
}
|
|
531
612
|
export async function peekRoom(config, options) {
|
|
532
613
|
if (!config.webSocketCtor) {
|
|
@@ -641,7 +722,7 @@ export function attachRejoinOnReconnect(client, options, latestSeq, onRejoined,
|
|
|
641
722
|
* (1..128 chars after trim); dedupe on (userId, clientMsgId) is server-side
|
|
642
723
|
* work and NOT implied here.
|
|
643
724
|
*/
|
|
644
|
-
export function postRoomMessage(client, room, text, inReplyTo, attachments, topicId, clientMsgId) {
|
|
725
|
+
export function postRoomMessage(client, room, text, inReplyTo, attachments, topicId, clientMsgId, metadata) {
|
|
645
726
|
const msgId = clientMsgId ?? randomUUID();
|
|
646
727
|
client.send({
|
|
647
728
|
type: RoomClientMessageType.POST,
|
|
@@ -652,6 +733,7 @@ export function postRoomMessage(client, room, text, inReplyTo, attachments, topi
|
|
|
652
733
|
clientMsgId: msgId,
|
|
653
734
|
...(inReplyTo !== undefined ? { inReplyTo } : {}),
|
|
654
735
|
...(topicId ? { topicId } : {}),
|
|
736
|
+
...(metadata ? { metadata } : {}),
|
|
655
737
|
},
|
|
656
738
|
timestamp: Date.now(),
|
|
657
739
|
});
|
|
@@ -37,10 +37,14 @@ export declare const RoomClientMessageType: {
|
|
|
37
37
|
readonly DM_OPEN: "DM_OPEN";
|
|
38
38
|
readonly DM_HISTORY: "DM_HISTORY";
|
|
39
39
|
readonly DM_SEND: "DM_SEND";
|
|
40
|
+
readonly DM_REPLY: "DM_REPLY";
|
|
40
41
|
readonly DM_READ: "DM_READ";
|
|
41
42
|
readonly DM_EDIT: "DM_EDIT";
|
|
42
43
|
readonly DM_DELETE: "DM_DELETE";
|
|
43
44
|
readonly DM_REACT: "DM_REACT";
|
|
45
|
+
readonly DM_PIN: "DM_PIN";
|
|
46
|
+
readonly DM_UNPIN: "DM_UNPIN";
|
|
47
|
+
readonly DM_PINS: "DM_PINS";
|
|
44
48
|
readonly TOPIC_CREATE: "TOPIC_CREATE";
|
|
45
49
|
readonly TOPIC_RENAME: "TOPIC_RENAME";
|
|
46
50
|
readonly TOPIC_MOVE: "TOPIC_MOVE";
|
|
@@ -94,6 +98,8 @@ export declare const RoomServerMessageType: {
|
|
|
94
98
|
readonly DM_MESSAGE_EDITED: "DM_MESSAGE_EDITED";
|
|
95
99
|
readonly DM_MESSAGE_DELETED: "DM_MESSAGE_DELETED";
|
|
96
100
|
readonly DM_REACTION_UPDATED: "DM_REACTION_UPDATED";
|
|
101
|
+
readonly DM_PIN_UPDATED: "DM_PIN_UPDATED";
|
|
102
|
+
readonly DM_PINS: "DM_PINS";
|
|
97
103
|
readonly TOPIC: "TOPIC";
|
|
98
104
|
readonly TOPICS: "TOPICS";
|
|
99
105
|
readonly TOPIC_UPDATED: "TOPIC_UPDATED";
|
|
@@ -225,6 +231,19 @@ export interface RoomLedgerEntry {
|
|
|
225
231
|
pinnedAt?: string | number | Date | null | undefined;
|
|
226
232
|
attachments?: RoomAttachment[] | undefined;
|
|
227
233
|
roomRole?: RoomRole | null | undefined;
|
|
234
|
+
metadata?: (Record<string, unknown> & Partial<RoomForwardMetadata>) | undefined;
|
|
235
|
+
}
|
|
236
|
+
export type RoomForwardedFrom = {
|
|
237
|
+
kind: "room";
|
|
238
|
+
room: string;
|
|
239
|
+
seq: number;
|
|
240
|
+
handle: string;
|
|
241
|
+
} | {
|
|
242
|
+
kind: "dm";
|
|
243
|
+
handle: string;
|
|
244
|
+
};
|
|
245
|
+
export interface RoomForwardMetadata {
|
|
246
|
+
forwardedFrom: RoomForwardedFrom;
|
|
228
247
|
}
|
|
229
248
|
export interface RoomAttachment {
|
|
230
249
|
/**
|
|
@@ -472,12 +491,14 @@ export interface RoomDmMessage {
|
|
|
472
491
|
createdAt?: string | number | undefined;
|
|
473
492
|
/** Sealed DM: content is '' and the opaque envelope rides here. */
|
|
474
493
|
secret?: RoomLedgerSecretPayload | null | undefined;
|
|
475
|
-
metadata?: Record<string, unknown> | undefined;
|
|
494
|
+
metadata?: (Record<string, unknown> & Partial<RoomForwardMetadata>) | undefined;
|
|
476
495
|
/** Legacy public chat-image URL (renders as-is, no signing). */
|
|
477
496
|
imageUrl?: string | null | undefined;
|
|
478
497
|
/** Private, signed, scanned DM attachments (dm-attachments/ keyspace).
|
|
479
498
|
* Additive: old messages carry none and render unchanged. */
|
|
480
499
|
attachments?: RoomAttachment[] | undefined;
|
|
500
|
+
/** Shared pin state. Legacy messages omit it and read as unpinned. */
|
|
501
|
+
pinned?: boolean | undefined;
|
|
481
502
|
}
|
|
482
503
|
export interface RoomDmOpenPayload {
|
|
483
504
|
requestId?: string;
|
|
@@ -502,6 +523,11 @@ export interface RoomDmSendPayload {
|
|
|
502
523
|
/** DM-attachment key or legacy public image URL; stored on the message's
|
|
503
524
|
* imageUrl. Additive — text-only sends omit it. */
|
|
504
525
|
imageUrl?: string;
|
|
526
|
+
metadata?: RoomForwardMetadata | undefined;
|
|
527
|
+
}
|
|
528
|
+
/** Client → server: send a DM whose parent is another message in the same conversation. */
|
|
529
|
+
export interface RoomDmReplyPayload extends RoomDmSendPayload {
|
|
530
|
+
replyTo: string;
|
|
505
531
|
}
|
|
506
532
|
/** Client → server: edit one of my DM messages (sealed messages are rejected). */
|
|
507
533
|
export interface RoomDmEditPayload {
|
|
@@ -520,6 +546,7 @@ export interface RoomDmDeletePayload {
|
|
|
520
546
|
}
|
|
521
547
|
/** Server → both participants: a DM message was edited. */
|
|
522
548
|
export interface RoomDmMessageEditedPayload {
|
|
549
|
+
requestId?: string;
|
|
523
550
|
conversationId: string;
|
|
524
551
|
messageId: string;
|
|
525
552
|
content: string;
|
|
@@ -528,6 +555,7 @@ export interface RoomDmMessageEditedPayload {
|
|
|
528
555
|
}
|
|
529
556
|
/** Server → both participants: a DM message was soft-deleted (tombstone). */
|
|
530
557
|
export interface RoomDmMessageDeletedPayload {
|
|
558
|
+
requestId?: string;
|
|
531
559
|
conversationId: string;
|
|
532
560
|
messageId: string;
|
|
533
561
|
deletedAt: string | number;
|
|
@@ -544,6 +572,7 @@ export interface RoomDmReactPayload {
|
|
|
544
572
|
}
|
|
545
573
|
/** Server → both participants: a DM message's aggregated reactions changed. */
|
|
546
574
|
export interface RoomDmReactionUpdatedPayload {
|
|
575
|
+
requestId?: string;
|
|
547
576
|
conversationId: string;
|
|
548
577
|
messageId: string;
|
|
549
578
|
reactions: Array<{
|
|
@@ -552,6 +581,31 @@ export interface RoomDmReactionUpdatedPayload {
|
|
|
552
581
|
}>;
|
|
553
582
|
participant: RoomDmParticipant;
|
|
554
583
|
}
|
|
584
|
+
export interface RoomDmPinPayload {
|
|
585
|
+
requestId?: string;
|
|
586
|
+
conversationId?: string;
|
|
587
|
+
username?: string;
|
|
588
|
+
messageId: string;
|
|
589
|
+
}
|
|
590
|
+
export interface RoomDmPinsPayload {
|
|
591
|
+
requestId?: string;
|
|
592
|
+
conversationId?: string;
|
|
593
|
+
username?: string;
|
|
594
|
+
limit?: number;
|
|
595
|
+
}
|
|
596
|
+
export interface RoomDmPinUpdatedPayload {
|
|
597
|
+
requestId?: string;
|
|
598
|
+
conversationId: string;
|
|
599
|
+
participant: RoomDmParticipant;
|
|
600
|
+
messageId: string;
|
|
601
|
+
pinned: boolean;
|
|
602
|
+
}
|
|
603
|
+
export interface RoomDmPinsResultPayload {
|
|
604
|
+
requestId?: string;
|
|
605
|
+
conversationId: string;
|
|
606
|
+
participant: RoomDmParticipant;
|
|
607
|
+
pins: RoomDmMessage[];
|
|
608
|
+
}
|
|
555
609
|
export interface RoomDmReadPayload {
|
|
556
610
|
requestId?: string;
|
|
557
611
|
username?: string;
|
|
@@ -889,6 +943,7 @@ export type RoomClientMessage = {
|
|
|
889
943
|
clientMsgId?: string;
|
|
890
944
|
reason?: string;
|
|
891
945
|
requestId?: string;
|
|
946
|
+
metadata?: RoomForwardMetadata;
|
|
892
947
|
};
|
|
893
948
|
timestamp?: number;
|
|
894
949
|
} | {
|
|
@@ -1074,6 +1129,10 @@ export type RoomClientMessage = {
|
|
|
1074
1129
|
type: typeof RoomClientMessageType.DM_SEND;
|
|
1075
1130
|
payload: RoomDmSendPayload;
|
|
1076
1131
|
timestamp?: number;
|
|
1132
|
+
} | {
|
|
1133
|
+
type: typeof RoomClientMessageType.DM_REPLY;
|
|
1134
|
+
payload: RoomDmReplyPayload;
|
|
1135
|
+
timestamp?: number;
|
|
1077
1136
|
} | {
|
|
1078
1137
|
type: typeof RoomClientMessageType.DM_READ;
|
|
1079
1138
|
payload: RoomDmReadPayload;
|
|
@@ -1090,6 +1149,18 @@ export type RoomClientMessage = {
|
|
|
1090
1149
|
type: typeof RoomClientMessageType.DM_REACT;
|
|
1091
1150
|
payload: RoomDmReactPayload;
|
|
1092
1151
|
timestamp?: number;
|
|
1152
|
+
} | {
|
|
1153
|
+
type: typeof RoomClientMessageType.DM_PIN;
|
|
1154
|
+
payload: RoomDmPinPayload;
|
|
1155
|
+
timestamp?: number;
|
|
1156
|
+
} | {
|
|
1157
|
+
type: typeof RoomClientMessageType.DM_UNPIN;
|
|
1158
|
+
payload: RoomDmPinPayload;
|
|
1159
|
+
timestamp?: number;
|
|
1160
|
+
} | {
|
|
1161
|
+
type: typeof RoomClientMessageType.DM_PINS;
|
|
1162
|
+
payload: RoomDmPinsPayload;
|
|
1163
|
+
timestamp?: number;
|
|
1093
1164
|
} | {
|
|
1094
1165
|
type: typeof RoomClientMessageType.TOPIC_CREATE;
|
|
1095
1166
|
payload: RoomTopicCreatePayload;
|
|
@@ -1296,6 +1367,14 @@ export type RoomServerMessage = {
|
|
|
1296
1367
|
type: typeof RoomServerMessageType.DM_REACTION_UPDATED;
|
|
1297
1368
|
payload: RoomDmReactionUpdatedPayload;
|
|
1298
1369
|
timestamp: number;
|
|
1370
|
+
} | {
|
|
1371
|
+
type: typeof RoomServerMessageType.DM_PIN_UPDATED;
|
|
1372
|
+
payload: RoomDmPinUpdatedPayload;
|
|
1373
|
+
timestamp: number;
|
|
1374
|
+
} | {
|
|
1375
|
+
type: typeof RoomServerMessageType.DM_PINS;
|
|
1376
|
+
payload: RoomDmPinsResultPayload;
|
|
1377
|
+
timestamp: number;
|
|
1299
1378
|
} | {
|
|
1300
1379
|
type: typeof RoomServerMessageType.TOPIC;
|
|
1301
1380
|
payload: RoomTopicResultPayload;
|
|
@@ -37,10 +37,14 @@ export const RoomClientMessageType = {
|
|
|
37
37
|
DM_OPEN: "DM_OPEN",
|
|
38
38
|
DM_HISTORY: "DM_HISTORY",
|
|
39
39
|
DM_SEND: "DM_SEND",
|
|
40
|
+
DM_REPLY: "DM_REPLY",
|
|
40
41
|
DM_READ: "DM_READ",
|
|
41
42
|
DM_EDIT: "DM_EDIT",
|
|
42
43
|
DM_DELETE: "DM_DELETE",
|
|
43
44
|
DM_REACT: "DM_REACT",
|
|
45
|
+
DM_PIN: "DM_PIN",
|
|
46
|
+
DM_UNPIN: "DM_UNPIN",
|
|
47
|
+
DM_PINS: "DM_PINS",
|
|
44
48
|
TOPIC_CREATE: "TOPIC_CREATE",
|
|
45
49
|
TOPIC_RENAME: "TOPIC_RENAME",
|
|
46
50
|
TOPIC_MOVE: "TOPIC_MOVE",
|
|
@@ -93,6 +97,8 @@ export const RoomServerMessageType = {
|
|
|
93
97
|
DM_MESSAGE_EDITED: "DM_MESSAGE_EDITED",
|
|
94
98
|
DM_MESSAGE_DELETED: "DM_MESSAGE_DELETED",
|
|
95
99
|
DM_REACTION_UPDATED: "DM_REACTION_UPDATED",
|
|
100
|
+
DM_PIN_UPDATED: "DM_PIN_UPDATED",
|
|
101
|
+
DM_PINS: "DM_PINS",
|
|
96
102
|
TOPIC: "TOPIC",
|
|
97
103
|
TOPICS: "TOPICS",
|
|
98
104
|
TOPIC_UPDATED: "TOPIC_UPDATED",
|
package/dist/social-client.d.ts
CHANGED
|
@@ -330,6 +330,12 @@ export declare function listRoomChannelMembers(config: RoomSocialConfig, roomId:
|
|
|
330
330
|
export declare function removeSpaceMember(config: RoomSocialConfig, spaceId: string, targetUserId: string): Promise<{
|
|
331
331
|
removed?: boolean;
|
|
332
332
|
}>;
|
|
333
|
+
/** Leave a server as the authenticated caller. This is deliberately separate
|
|
334
|
+
* from the manager-only member-removal route so clients never send their own
|
|
335
|
+
* userId as authorization data. */
|
|
336
|
+
export declare function leaveSpace(config: RoomSocialConfig, spaceId: string): Promise<{
|
|
337
|
+
left?: boolean;
|
|
338
|
+
}>;
|
|
333
339
|
export declare function changeSpaceMemberRole(config: RoomSocialConfig, spaceId: string, targetUserId: string, role: "admin" | "librarian" | "member"): Promise<RoomSpaceMember>;
|
|
334
340
|
/** Set (string) or clear (null) a member's per-space nickname; the store validates and enforces uniqueness. */
|
|
335
341
|
export declare function setSpaceMemberNickname(config: RoomSocialConfig, spaceId: string, targetUserId: string, nickname: string | null): Promise<RoomSpaceMember>;
|
package/dist/social-client.js
CHANGED
|
@@ -500,6 +500,14 @@ export async function listRoomChannelMembers(config, roomId, options = {}) {
|
|
|
500
500
|
export async function removeSpaceMember(config, spaceId, targetUserId) {
|
|
501
501
|
return socialRequest(config, `/spaces/${encodeURIComponent(spaceId)}/members/${encodeURIComponent(targetUserId)}`, { method: "DELETE" });
|
|
502
502
|
}
|
|
503
|
+
/** Leave a server as the authenticated caller. This is deliberately separate
|
|
504
|
+
* from the manager-only member-removal route so clients never send their own
|
|
505
|
+
* userId as authorization data. */
|
|
506
|
+
export async function leaveSpace(config, spaceId) {
|
|
507
|
+
return socialRequest(config, `/spaces/${encodeURIComponent(spaceId)}/membership`, {
|
|
508
|
+
method: "DELETE",
|
|
509
|
+
});
|
|
510
|
+
}
|
|
503
511
|
export async function changeSpaceMemberRole(config, spaceId, targetUserId, role) {
|
|
504
512
|
const result = await socialRequest(config, `/spaces/${encodeURIComponent(spaceId)}/members/${encodeURIComponent(targetUserId)}/role`, { method: "PATCH", body: { role } });
|
|
505
513
|
return result.member;
|
package/dist/social-types.d.ts
CHANGED
|
@@ -130,6 +130,8 @@ export interface RoomDmMessage {
|
|
|
130
130
|
emoji: string;
|
|
131
131
|
users: string[];
|
|
132
132
|
}>;
|
|
133
|
+
/** Shared pin state. Additive; absent means unpinned. */
|
|
134
|
+
pinned?: boolean;
|
|
133
135
|
/** Client-minted idempotency tag echoed back on DM sends, so a sender's
|
|
134
136
|
* durable queue can settle its optimistic row. Additive (0.4.0). */
|
|
135
137
|
clientMsgId?: string;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type { RoomAccessMode, RoomAttachment, RoomAttachmentInput, RoomAuditEntry, RoomAuditLogPayload, RoomAuditLogRequestPayload, RoomAuthSuccessPayload, RoomBackfillPayload, RoomClientMessage, RoomClientPlatform, RoomCreatedPayload, RoomCreatePayload, RoomDmHistoryPayload, RoomDmHistoryResultPayload, RoomDmInboxUpdatedPayload, RoomDmMessage, RoomDmMessagePayload, RoomDmOpenedPayload, RoomDmOpenPayload, RoomDmParticipant, RoomDmReadPayload, RoomDmSendPayload, RoomEntryUpdatedPayload, RoomErrorPayload, RoomFriendPresence, RoomInvitedPayload, RoomInvitePayload, RoomLedgerEntry, RoomMember, RoomMemberRemovedPayload, RoomMemberRoleChangedPayload, RoomMemberTier, RoomMessagePayload, RoomMeta, RoomModerateMemberPayload, RoomMutedPayload, RoomPinsPayload, RoomPresenceDeltaPayload, RoomPresencePayload, RoomReactorsPayload, RoomReadStatePayload, RoomRemoveMemberPayload, RoomReportedPayload, RoomReportPayload, RoomResyncPayload, RoomRole, RoomRoleChangePayload, RoomSearchPayload, RoomSearchResultsPayload, RoomSearchScope, RoomServerMessage, RoomServerMessageOf, RoomSetAttentionPayload, RoomSetReadOnlyPayload, RoomSpace, RoomSpaceJoinedPayload, RoomSpacesPayload, RoomThreadPayload, RoomTopic, RoomTopicAttention, RoomTopicBucketState, RoomTopicCreatePayload, RoomTopicListPayload, RoomTopicMergePayload, RoomTopicMovePayload, RoomTopicRenamePayload, RoomTopicResolvePayload, RoomTopicResultPayload, RoomTopicSetAttentionPayload, RoomTopicsPolicy, RoomTopicsResultPayload, RoomTopicUpdatedPayload, RoomTypingPayload, RoomUserStatus, RoomWhoPayload, } from "./shared/rooms-protocol.js";
|
|
1
|
+
export type { RoomAccessMode, RoomAttachment, RoomAttachmentInput, RoomAuditEntry, RoomAuditLogPayload, RoomAuditLogRequestPayload, RoomAuthSuccessPayload, RoomBackfillPayload, RoomClientMessage, RoomClientPlatform, RoomCreatedPayload, RoomCreatePayload, RoomDmHistoryPayload, RoomDmHistoryResultPayload, RoomDmInboxUpdatedPayload, RoomDmMessage, RoomDmMessageDeletedPayload, RoomDmMessageEditedPayload, RoomDmMessagePayload, RoomDmOpenedPayload, RoomDmOpenPayload, RoomDmParticipant, RoomDmPinsResultPayload, RoomDmPinUpdatedPayload, RoomDmReactionUpdatedPayload, RoomDmReadPayload, RoomDmReplyPayload, RoomDmSendPayload, RoomEntryUpdatedPayload, RoomErrorPayload, RoomForwardedFrom, RoomForwardMetadata, RoomFriendPresence, RoomInvitedPayload, RoomInvitePayload, RoomLedgerEntry, RoomMember, RoomMemberRemovedPayload, RoomMemberRoleChangedPayload, RoomMemberTier, RoomMessagePayload, RoomMeta, RoomModerateMemberPayload, RoomMutedPayload, RoomPinsPayload, RoomPresenceDeltaPayload, RoomPresencePayload, RoomReactorsPayload, RoomReadStatePayload, RoomRemoveMemberPayload, RoomReportedPayload, RoomReportPayload, RoomResyncPayload, RoomRole, RoomRoleChangePayload, RoomSearchPayload, RoomSearchResultsPayload, RoomSearchScope, RoomServerMessage, RoomServerMessageOf, RoomSetAttentionPayload, RoomSetReadOnlyPayload, RoomSpace, RoomSpaceJoinedPayload, RoomSpacesPayload, RoomThreadPayload, RoomTopic, RoomTopicAttention, RoomTopicBucketState, RoomTopicCreatePayload, RoomTopicListPayload, RoomTopicMergePayload, RoomTopicMovePayload, RoomTopicRenamePayload, RoomTopicResolvePayload, RoomTopicResultPayload, RoomTopicSetAttentionPayload, RoomTopicsPolicy, RoomTopicsResultPayload, RoomTopicUpdatedPayload, RoomTypingPayload, RoomUserStatus, RoomWhoPayload, } from "./shared/rooms-protocol.js";
|
|
2
2
|
export { applyRoomPresenceDelta, isSystemRoomLedgerEntryType, ROOM_PRESENCE_SAMPLE_LIMIT, RoomClientMessageType, RoomLedgerEntryType, RoomServerMessageType, SUPPORTED_ROOM_PROTOCOL_VERSION, } from "./shared/rooms-protocol.js";
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.
|
|
2
|
-
export declare const RUNNER_VERSION = "0.
|
|
1
|
+
export declare const VERSION = "0.7.0";
|
|
2
|
+
export declare const RUNNER_VERSION = "0.7.0";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const VERSION = "0.
|
|
2
|
-
export const RUNNER_VERSION = "0.
|
|
1
|
+
export const VERSION = "0.7.0";
|
|
2
|
+
export const RUNNER_VERSION = "0.7.0";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openmarket/rooms-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "OM Rooms protocol client: wire types, WebSocket + REST clients, and chat view-models. Browser-safe (no node:/bun: imports).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -17,7 +17,12 @@
|
|
|
17
17
|
"bun": ">=1.3.0",
|
|
18
18
|
"node": ">=22"
|
|
19
19
|
},
|
|
20
|
-
"files": [
|
|
20
|
+
"files": [
|
|
21
|
+
"src",
|
|
22
|
+
"dist",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE"
|
|
25
|
+
],
|
|
21
26
|
"publishConfig": {
|
|
22
27
|
"access": "public"
|
|
23
28
|
},
|
|
@@ -186,6 +186,30 @@ export class SidebarModel {
|
|
|
186
186
|
};
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
+
/** Apply a same-user realtime DM counter without refetching the full room
|
|
190
|
+
* directory. Username is the stable DM key used by the sidebar model. */
|
|
191
|
+
applyDmUnread(username: string, unread: number): void {
|
|
192
|
+
const key = normalizeDmUsername(username);
|
|
193
|
+
if (!key) return;
|
|
194
|
+
const normalizedUnread = Number.isFinite(unread) ? Math.max(0, Math.floor(unread)) : 0;
|
|
195
|
+
let found = false;
|
|
196
|
+
let changed = false;
|
|
197
|
+
const next = this.snapshotValue.dms.map((dm) => {
|
|
198
|
+
if (normalizeDmUsername(dm.username) !== key) return dm;
|
|
199
|
+
found = true;
|
|
200
|
+
if (dm.unread === normalizedUnread) return dm;
|
|
201
|
+
changed = true;
|
|
202
|
+
return { ...dm, unread: normalizedUnread };
|
|
203
|
+
});
|
|
204
|
+
if (!found && normalizedUnread > 0) {
|
|
205
|
+
next.push({ username: username.replace(/^@+/, ""), unread: normalizedUnread });
|
|
206
|
+
changed = true;
|
|
207
|
+
}
|
|
208
|
+
if (!changed) return;
|
|
209
|
+
this.snapshotValue = { ...this.snapshotValue, dms: next };
|
|
210
|
+
for (const listener of this.listeners) listener();
|
|
211
|
+
}
|
|
212
|
+
|
|
189
213
|
/**
|
|
190
214
|
* Post-mutation refresh: if a poll-triggered load is already in flight it
|
|
191
215
|
* may predate the caller's writes, so wait it out and load AGAIN. Readers
|
|
@@ -233,3 +257,7 @@ export class SidebarModel {
|
|
|
233
257
|
});
|
|
234
258
|
}
|
|
235
259
|
}
|
|
260
|
+
|
|
261
|
+
function normalizeDmUsername(username: string): string {
|
|
262
|
+
return username.trim().replace(/^@+/, "").toLowerCase();
|
|
263
|
+
}
|
package/src/client.ts
CHANGED
|
@@ -16,8 +16,13 @@ import {
|
|
|
16
16
|
type RoomClientPlatform,
|
|
17
17
|
type RoomDmHistoryResultPayload,
|
|
18
18
|
type RoomDmInboxUpdatedPayload,
|
|
19
|
+
type RoomDmMessageDeletedPayload,
|
|
20
|
+
type RoomDmMessageEditedPayload,
|
|
19
21
|
type RoomDmMessagePayload,
|
|
20
22
|
type RoomDmOpenedPayload,
|
|
23
|
+
type RoomDmPinsResultPayload,
|
|
24
|
+
type RoomDmReactionUpdatedPayload,
|
|
25
|
+
type RoomForwardMetadata,
|
|
21
26
|
type RoomFriendPresence,
|
|
22
27
|
type RoomInvitedPayload,
|
|
23
28
|
type RoomLedgerEntry,
|
|
@@ -292,6 +297,11 @@ export interface RoomDmSendRequest {
|
|
|
292
297
|
* URL. Stored on the message's imageUrl; the client signs keys on render.
|
|
293
298
|
* Additive — omitted for text-only sends. */
|
|
294
299
|
imageUrl?: string;
|
|
300
|
+
metadata?: RoomForwardMetadata;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export interface RoomDmReplyRequest extends RoomDmSendRequest {
|
|
304
|
+
replyTo: string;
|
|
295
305
|
}
|
|
296
306
|
|
|
297
307
|
export interface RoomDmReadRequest {
|
|
@@ -433,9 +443,7 @@ export async function inviteRoom(
|
|
|
433
443
|
): Promise<RoomInviteResult> {
|
|
434
444
|
// `@user` and `user` are the same person: strip the typing syntax once,
|
|
435
445
|
// before either transport (the relay resolves the bare name).
|
|
436
|
-
const normalized
|
|
437
|
-
? { ...request, username: request.username.trim().replace(/^@+/, "") }
|
|
438
|
-
: request;
|
|
446
|
+
const normalized = normalizeRoomInviteRequest(request);
|
|
439
447
|
if (!config.webSocketCtor) {
|
|
440
448
|
try {
|
|
441
449
|
return await inviteRoomViaHttp(config, normalized);
|
|
@@ -453,29 +461,46 @@ async function inviteRoomViaWebSocket(
|
|
|
453
461
|
const client = new RoomWsClient(config);
|
|
454
462
|
try {
|
|
455
463
|
const auth = await client.connect();
|
|
456
|
-
|
|
457
|
-
client.send({
|
|
458
|
-
type: RoomClientMessageType.INVITE,
|
|
459
|
-
payload: {
|
|
460
|
-
requestId,
|
|
461
|
-
room: request.room,
|
|
462
|
-
...(request.username ? { username: request.username } : {}),
|
|
463
|
-
...(request.userId ? { userId: request.userId } : {}),
|
|
464
|
-
},
|
|
465
|
-
timestamp: Date.now(),
|
|
466
|
-
});
|
|
467
|
-
const invited = await client.waitFor(
|
|
468
|
-
RoomServerMessageType.ROOM_INVITED,
|
|
469
|
-
(msg) => msg.payload.requestId === requestId,
|
|
470
|
-
undefined,
|
|
471
|
-
requestId,
|
|
472
|
-
);
|
|
473
|
-
return { auth, invite: invited.payload };
|
|
464
|
+
return { auth, invite: await inviteRoomOnClient(client, request) };
|
|
474
465
|
} finally {
|
|
475
466
|
client.close();
|
|
476
467
|
}
|
|
477
468
|
}
|
|
478
469
|
|
|
470
|
+
/** Invite over an already-authenticated socket. Long-lived GUI/TUI sessions
|
|
471
|
+
* use this path so a one-shot invite cannot create a second connection. */
|
|
472
|
+
export async function inviteRoomOnClient(
|
|
473
|
+
client: RoomWsClient,
|
|
474
|
+
request: RoomInviteRequest,
|
|
475
|
+
timeoutMs?: number,
|
|
476
|
+
): Promise<RoomInvitedPayload> {
|
|
477
|
+
const normalized = normalizeRoomInviteRequest(request);
|
|
478
|
+
const requestId = randomUUID();
|
|
479
|
+
client.send({
|
|
480
|
+
type: RoomClientMessageType.INVITE,
|
|
481
|
+
payload: {
|
|
482
|
+
requestId,
|
|
483
|
+
room: normalized.room,
|
|
484
|
+
...(normalized.username ? { username: normalized.username } : {}),
|
|
485
|
+
...(normalized.userId ? { userId: normalized.userId } : {}),
|
|
486
|
+
},
|
|
487
|
+
timestamp: Date.now(),
|
|
488
|
+
});
|
|
489
|
+
const invited = await client.waitFor(
|
|
490
|
+
RoomServerMessageType.ROOM_INVITED,
|
|
491
|
+
(msg) => msg.payload.requestId === requestId,
|
|
492
|
+
timeoutMs,
|
|
493
|
+
requestId,
|
|
494
|
+
);
|
|
495
|
+
return invited.payload;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function normalizeRoomInviteRequest(request: RoomInviteRequest): RoomInviteRequest {
|
|
499
|
+
return request.username
|
|
500
|
+
? { ...request, username: request.username.trim().replace(/^@+/, "") }
|
|
501
|
+
: request;
|
|
502
|
+
}
|
|
503
|
+
|
|
479
504
|
export async function removeRoomMember(
|
|
480
505
|
config: RoomClientConfig,
|
|
481
506
|
request: RoomRemoveMemberRequest,
|
|
@@ -969,6 +994,7 @@ export async function sendRoomDmOnClient(
|
|
|
969
994
|
...(request.username ? { username: request.username } : {}),
|
|
970
995
|
...(request.conversationId ? { conversationId: request.conversationId } : {}),
|
|
971
996
|
...(request.imageUrl ? { imageUrl: request.imageUrl } : {}),
|
|
997
|
+
...(request.metadata ? { metadata: request.metadata } : {}),
|
|
972
998
|
},
|
|
973
999
|
timestamp: Date.now(),
|
|
974
1000
|
});
|
|
@@ -984,15 +1010,50 @@ export async function sendRoomDmOnClient(
|
|
|
984
1010
|
return sent.payload;
|
|
985
1011
|
}
|
|
986
1012
|
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
1013
|
+
export async function replyRoomDmOnClient(
|
|
1014
|
+
client: RoomWsClient,
|
|
1015
|
+
request: RoomDmReplyRequest,
|
|
1016
|
+
timeoutMs?: number,
|
|
1017
|
+
): Promise<RoomDmMessagePayload> {
|
|
1018
|
+
const requestId = randomUUID();
|
|
1019
|
+
const clientMsgId = request.clientMsgId ?? randomUUID();
|
|
1020
|
+
client.send({
|
|
1021
|
+
type: RoomClientMessageType.DM_REPLY,
|
|
1022
|
+
payload: {
|
|
1023
|
+
requestId,
|
|
1024
|
+
content: request.content,
|
|
1025
|
+
replyTo: request.replyTo,
|
|
1026
|
+
clientMsgId,
|
|
1027
|
+
...(request.username ? { username: request.username } : {}),
|
|
1028
|
+
...(request.conversationId ? { conversationId: request.conversationId } : {}),
|
|
1029
|
+
...(request.imageUrl ? { imageUrl: request.imageUrl } : {}),
|
|
1030
|
+
...(request.metadata ? { metadata: request.metadata } : {}),
|
|
1031
|
+
},
|
|
1032
|
+
timestamp: Date.now(),
|
|
1033
|
+
});
|
|
1034
|
+
const sent = await client.waitFor(
|
|
1035
|
+
RoomServerMessageType.DM_MESSAGE,
|
|
1036
|
+
(msg) =>
|
|
1037
|
+
msg.payload.requestId === requestId ||
|
|
1038
|
+
msg.payload.clientMsgId === clientMsgId ||
|
|
1039
|
+
msg.payload.message.clientMsgId === clientMsgId,
|
|
1040
|
+
timeoutMs,
|
|
1041
|
+
requestId,
|
|
1042
|
+
);
|
|
1043
|
+
return sent.payload;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
/** Edit one of my DM messages and wait for the correlated relay update. */
|
|
1047
|
+
export async function editRoomDmOnClient(
|
|
990
1048
|
client: RoomWsClient,
|
|
991
1049
|
request: { conversationId?: string; username?: string; messageId: string; content: string },
|
|
992
|
-
|
|
1050
|
+
timeoutMs?: number,
|
|
1051
|
+
): Promise<RoomDmMessageEditedPayload> {
|
|
1052
|
+
const requestId = randomUUID();
|
|
993
1053
|
client.send({
|
|
994
1054
|
type: RoomClientMessageType.DM_EDIT,
|
|
995
1055
|
payload: {
|
|
1056
|
+
requestId,
|
|
996
1057
|
messageId: request.messageId,
|
|
997
1058
|
content: request.content,
|
|
998
1059
|
...(request.conversationId ? { conversationId: request.conversationId } : {}),
|
|
@@ -1000,26 +1061,43 @@ export function editRoomDmOnClient(
|
|
|
1000
1061
|
},
|
|
1001
1062
|
timestamp: Date.now(),
|
|
1002
1063
|
});
|
|
1064
|
+
const updated = await client.waitFor(
|
|
1065
|
+
RoomServerMessageType.DM_MESSAGE_EDITED,
|
|
1066
|
+
(msg) => msg.payload.requestId === requestId,
|
|
1067
|
+
timeoutMs,
|
|
1068
|
+
requestId,
|
|
1069
|
+
);
|
|
1070
|
+
return updated.payload;
|
|
1003
1071
|
}
|
|
1004
1072
|
|
|
1005
1073
|
/** Soft-delete one of my DM messages. The relay fans out DM_MESSAGE_DELETED. */
|
|
1006
|
-
export function deleteRoomDmOnClient(
|
|
1074
|
+
export async function deleteRoomDmOnClient(
|
|
1007
1075
|
client: RoomWsClient,
|
|
1008
1076
|
request: { conversationId?: string; username?: string; messageId: string },
|
|
1009
|
-
|
|
1077
|
+
timeoutMs?: number,
|
|
1078
|
+
): Promise<RoomDmMessageDeletedPayload> {
|
|
1079
|
+
const requestId = randomUUID();
|
|
1010
1080
|
client.send({
|
|
1011
1081
|
type: RoomClientMessageType.DM_DELETE,
|
|
1012
1082
|
payload: {
|
|
1083
|
+
requestId,
|
|
1013
1084
|
messageId: request.messageId,
|
|
1014
1085
|
...(request.conversationId ? { conversationId: request.conversationId } : {}),
|
|
1015
1086
|
...(request.username ? { username: request.username } : {}),
|
|
1016
1087
|
},
|
|
1017
1088
|
timestamp: Date.now(),
|
|
1018
1089
|
});
|
|
1090
|
+
const updated = await client.waitFor(
|
|
1091
|
+
RoomServerMessageType.DM_MESSAGE_DELETED,
|
|
1092
|
+
(msg) => msg.payload.requestId === requestId,
|
|
1093
|
+
timeoutMs,
|
|
1094
|
+
requestId,
|
|
1095
|
+
);
|
|
1096
|
+
return updated.payload;
|
|
1019
1097
|
}
|
|
1020
1098
|
|
|
1021
1099
|
/** Toggle a reaction on a DM message. The relay fans out DM_REACTION_UPDATED. */
|
|
1022
|
-
export function reactRoomDmOnClient(
|
|
1100
|
+
export async function reactRoomDmOnClient(
|
|
1023
1101
|
client: RoomWsClient,
|
|
1024
1102
|
request: {
|
|
1025
1103
|
conversationId?: string;
|
|
@@ -1028,10 +1106,13 @@ export function reactRoomDmOnClient(
|
|
|
1028
1106
|
emoji: string;
|
|
1029
1107
|
op: "add" | "remove";
|
|
1030
1108
|
},
|
|
1031
|
-
|
|
1109
|
+
timeoutMs?: number,
|
|
1110
|
+
): Promise<RoomDmReactionUpdatedPayload> {
|
|
1111
|
+
const requestId = randomUUID();
|
|
1032
1112
|
client.send({
|
|
1033
1113
|
type: RoomClientMessageType.DM_REACT,
|
|
1034
1114
|
payload: {
|
|
1115
|
+
requestId,
|
|
1035
1116
|
messageId: request.messageId,
|
|
1036
1117
|
emoji: request.emoji,
|
|
1037
1118
|
op: request.op,
|
|
@@ -1040,6 +1121,69 @@ export function reactRoomDmOnClient(
|
|
|
1040
1121
|
},
|
|
1041
1122
|
timestamp: Date.now(),
|
|
1042
1123
|
});
|
|
1124
|
+
const updated = await client.waitFor(
|
|
1125
|
+
RoomServerMessageType.DM_REACTION_UPDATED,
|
|
1126
|
+
(msg) => msg.payload.requestId === requestId,
|
|
1127
|
+
timeoutMs,
|
|
1128
|
+
requestId,
|
|
1129
|
+
);
|
|
1130
|
+
return updated.payload;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
/** Set shared pin state for a DM message. The relay fans out DM_PIN_UPDATED. */
|
|
1134
|
+
export function pinRoomDmOnClient(
|
|
1135
|
+
client: RoomWsClient,
|
|
1136
|
+
request: { conversationId?: string; username?: string; messageId: string },
|
|
1137
|
+
): void {
|
|
1138
|
+
client.send({
|
|
1139
|
+
type: RoomClientMessageType.DM_PIN,
|
|
1140
|
+
payload: {
|
|
1141
|
+
messageId: request.messageId,
|
|
1142
|
+
...(request.conversationId ? { conversationId: request.conversationId } : {}),
|
|
1143
|
+
...(request.username ? { username: request.username } : {}),
|
|
1144
|
+
},
|
|
1145
|
+
timestamp: Date.now(),
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
export function unpinRoomDmOnClient(
|
|
1150
|
+
client: RoomWsClient,
|
|
1151
|
+
request: { conversationId?: string; username?: string; messageId: string },
|
|
1152
|
+
): void {
|
|
1153
|
+
client.send({
|
|
1154
|
+
type: RoomClientMessageType.DM_UNPIN,
|
|
1155
|
+
payload: {
|
|
1156
|
+
messageId: request.messageId,
|
|
1157
|
+
...(request.conversationId ? { conversationId: request.conversationId } : {}),
|
|
1158
|
+
...(request.username ? { username: request.username } : {}),
|
|
1159
|
+
},
|
|
1160
|
+
timestamp: Date.now(),
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
export async function requestRoomDmPinsOnClient(
|
|
1165
|
+
client: RoomWsClient,
|
|
1166
|
+
request: { conversationId?: string; username?: string; limit?: number },
|
|
1167
|
+
timeoutMs?: number,
|
|
1168
|
+
): Promise<RoomDmPinsResultPayload> {
|
|
1169
|
+
const requestId = randomUUID();
|
|
1170
|
+
client.send({
|
|
1171
|
+
type: RoomClientMessageType.DM_PINS,
|
|
1172
|
+
payload: {
|
|
1173
|
+
requestId,
|
|
1174
|
+
...(request.conversationId ? { conversationId: request.conversationId } : {}),
|
|
1175
|
+
...(request.username ? { username: request.username } : {}),
|
|
1176
|
+
...(request.limit ? { limit: request.limit } : {}),
|
|
1177
|
+
},
|
|
1178
|
+
timestamp: Date.now(),
|
|
1179
|
+
});
|
|
1180
|
+
const result = await client.waitFor(
|
|
1181
|
+
RoomServerMessageType.DM_PINS,
|
|
1182
|
+
(message) => message.payload.requestId === requestId,
|
|
1183
|
+
timeoutMs,
|
|
1184
|
+
requestId,
|
|
1185
|
+
);
|
|
1186
|
+
return result.payload;
|
|
1043
1187
|
}
|
|
1044
1188
|
|
|
1045
1189
|
export async function peekRoom(
|
|
@@ -1195,6 +1339,7 @@ export function postRoomMessage(
|
|
|
1195
1339
|
attachments?: RoomAttachmentInput[],
|
|
1196
1340
|
topicId?: string,
|
|
1197
1341
|
clientMsgId?: string,
|
|
1342
|
+
metadata?: RoomForwardMetadata,
|
|
1198
1343
|
): string {
|
|
1199
1344
|
const msgId = clientMsgId ?? randomUUID();
|
|
1200
1345
|
client.send({
|
|
@@ -1206,6 +1351,7 @@ export function postRoomMessage(
|
|
|
1206
1351
|
clientMsgId: msgId,
|
|
1207
1352
|
...(inReplyTo !== undefined ? { inReplyTo } : {}),
|
|
1208
1353
|
...(topicId ? { topicId } : {}),
|
|
1354
|
+
...(metadata ? { metadata } : {}),
|
|
1209
1355
|
},
|
|
1210
1356
|
timestamp: Date.now(),
|
|
1211
1357
|
});
|
|
@@ -37,10 +37,14 @@ export const RoomClientMessageType = {
|
|
|
37
37
|
DM_OPEN: "DM_OPEN",
|
|
38
38
|
DM_HISTORY: "DM_HISTORY",
|
|
39
39
|
DM_SEND: "DM_SEND",
|
|
40
|
+
DM_REPLY: "DM_REPLY",
|
|
40
41
|
DM_READ: "DM_READ",
|
|
41
42
|
DM_EDIT: "DM_EDIT",
|
|
42
43
|
DM_DELETE: "DM_DELETE",
|
|
43
44
|
DM_REACT: "DM_REACT",
|
|
45
|
+
DM_PIN: "DM_PIN",
|
|
46
|
+
DM_UNPIN: "DM_UNPIN",
|
|
47
|
+
DM_PINS: "DM_PINS",
|
|
44
48
|
TOPIC_CREATE: "TOPIC_CREATE",
|
|
45
49
|
TOPIC_RENAME: "TOPIC_RENAME",
|
|
46
50
|
TOPIC_MOVE: "TOPIC_MOVE",
|
|
@@ -97,6 +101,8 @@ export const RoomServerMessageType = {
|
|
|
97
101
|
DM_MESSAGE_EDITED: "DM_MESSAGE_EDITED",
|
|
98
102
|
DM_MESSAGE_DELETED: "DM_MESSAGE_DELETED",
|
|
99
103
|
DM_REACTION_UPDATED: "DM_REACTION_UPDATED",
|
|
104
|
+
DM_PIN_UPDATED: "DM_PIN_UPDATED",
|
|
105
|
+
DM_PINS: "DM_PINS",
|
|
100
106
|
TOPIC: "TOPIC",
|
|
101
107
|
TOPICS: "TOPICS",
|
|
102
108
|
TOPIC_UPDATED: "TOPIC_UPDATED",
|
|
@@ -253,6 +259,23 @@ export interface RoomLedgerEntry {
|
|
|
253
259
|
pinnedAt?: string | number | Date | null | undefined;
|
|
254
260
|
attachments?: RoomAttachment[] | undefined;
|
|
255
261
|
roomRole?: RoomRole | null | undefined;
|
|
262
|
+
metadata?: (Record<string, unknown> & Partial<RoomForwardMetadata>) | undefined;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export type RoomForwardedFrom =
|
|
266
|
+
| {
|
|
267
|
+
kind: "room";
|
|
268
|
+
room: string;
|
|
269
|
+
seq: number;
|
|
270
|
+
handle: string;
|
|
271
|
+
}
|
|
272
|
+
| {
|
|
273
|
+
kind: "dm";
|
|
274
|
+
handle: string;
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
export interface RoomForwardMetadata {
|
|
278
|
+
forwardedFrom: RoomForwardedFrom;
|
|
256
279
|
}
|
|
257
280
|
|
|
258
281
|
export interface RoomAttachment {
|
|
@@ -560,12 +583,14 @@ export interface RoomDmMessage {
|
|
|
560
583
|
createdAt?: string | number | undefined;
|
|
561
584
|
/** Sealed DM: content is '' and the opaque envelope rides here. */
|
|
562
585
|
secret?: RoomLedgerSecretPayload | null | undefined;
|
|
563
|
-
metadata?: Record<string, unknown> | undefined;
|
|
586
|
+
metadata?: (Record<string, unknown> & Partial<RoomForwardMetadata>) | undefined;
|
|
564
587
|
/** Legacy public chat-image URL (renders as-is, no signing). */
|
|
565
588
|
imageUrl?: string | null | undefined;
|
|
566
589
|
/** Private, signed, scanned DM attachments (dm-attachments/ keyspace).
|
|
567
590
|
* Additive: old messages carry none and render unchanged. */
|
|
568
591
|
attachments?: RoomAttachment[] | undefined;
|
|
592
|
+
/** Shared pin state. Legacy messages omit it and read as unpinned. */
|
|
593
|
+
pinned?: boolean | undefined;
|
|
569
594
|
}
|
|
570
595
|
|
|
571
596
|
export interface RoomDmOpenPayload {
|
|
@@ -593,6 +618,12 @@ export interface RoomDmSendPayload {
|
|
|
593
618
|
/** DM-attachment key or legacy public image URL; stored on the message's
|
|
594
619
|
* imageUrl. Additive — text-only sends omit it. */
|
|
595
620
|
imageUrl?: string;
|
|
621
|
+
metadata?: RoomForwardMetadata | undefined;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/** Client → server: send a DM whose parent is another message in the same conversation. */
|
|
625
|
+
export interface RoomDmReplyPayload extends RoomDmSendPayload {
|
|
626
|
+
replyTo: string;
|
|
596
627
|
}
|
|
597
628
|
|
|
598
629
|
/** Client → server: edit one of my DM messages (sealed messages are rejected). */
|
|
@@ -614,6 +645,7 @@ export interface RoomDmDeletePayload {
|
|
|
614
645
|
|
|
615
646
|
/** Server → both participants: a DM message was edited. */
|
|
616
647
|
export interface RoomDmMessageEditedPayload {
|
|
648
|
+
requestId?: string;
|
|
617
649
|
conversationId: string;
|
|
618
650
|
messageId: string;
|
|
619
651
|
content: string;
|
|
@@ -623,6 +655,7 @@ export interface RoomDmMessageEditedPayload {
|
|
|
623
655
|
|
|
624
656
|
/** Server → both participants: a DM message was soft-deleted (tombstone). */
|
|
625
657
|
export interface RoomDmMessageDeletedPayload {
|
|
658
|
+
requestId?: string;
|
|
626
659
|
conversationId: string;
|
|
627
660
|
messageId: string;
|
|
628
661
|
deletedAt: string | number;
|
|
@@ -641,12 +674,42 @@ export interface RoomDmReactPayload {
|
|
|
641
674
|
|
|
642
675
|
/** Server → both participants: a DM message's aggregated reactions changed. */
|
|
643
676
|
export interface RoomDmReactionUpdatedPayload {
|
|
677
|
+
requestId?: string;
|
|
644
678
|
conversationId: string;
|
|
645
679
|
messageId: string;
|
|
646
680
|
reactions: Array<{ emoji: string; users: string[] }>;
|
|
647
681
|
participant: RoomDmParticipant;
|
|
648
682
|
}
|
|
649
683
|
|
|
684
|
+
export interface RoomDmPinPayload {
|
|
685
|
+
requestId?: string;
|
|
686
|
+
conversationId?: string;
|
|
687
|
+
username?: string;
|
|
688
|
+
messageId: string;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
export interface RoomDmPinsPayload {
|
|
692
|
+
requestId?: string;
|
|
693
|
+
conversationId?: string;
|
|
694
|
+
username?: string;
|
|
695
|
+
limit?: number;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
export interface RoomDmPinUpdatedPayload {
|
|
699
|
+
requestId?: string;
|
|
700
|
+
conversationId: string;
|
|
701
|
+
participant: RoomDmParticipant;
|
|
702
|
+
messageId: string;
|
|
703
|
+
pinned: boolean;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
export interface RoomDmPinsResultPayload {
|
|
707
|
+
requestId?: string;
|
|
708
|
+
conversationId: string;
|
|
709
|
+
participant: RoomDmParticipant;
|
|
710
|
+
pins: RoomDmMessage[];
|
|
711
|
+
}
|
|
712
|
+
|
|
650
713
|
export interface RoomDmReadPayload {
|
|
651
714
|
requestId?: string;
|
|
652
715
|
username?: string;
|
|
@@ -1014,6 +1077,7 @@ export type RoomClientMessage =
|
|
|
1014
1077
|
clientMsgId?: string;
|
|
1015
1078
|
reason?: string;
|
|
1016
1079
|
requestId?: string;
|
|
1080
|
+
metadata?: RoomForwardMetadata;
|
|
1017
1081
|
};
|
|
1018
1082
|
timestamp?: number;
|
|
1019
1083
|
}
|
|
@@ -1189,6 +1253,11 @@ export type RoomClientMessage =
|
|
|
1189
1253
|
payload: RoomDmSendPayload;
|
|
1190
1254
|
timestamp?: number;
|
|
1191
1255
|
}
|
|
1256
|
+
| {
|
|
1257
|
+
type: typeof RoomClientMessageType.DM_REPLY;
|
|
1258
|
+
payload: RoomDmReplyPayload;
|
|
1259
|
+
timestamp?: number;
|
|
1260
|
+
}
|
|
1192
1261
|
| {
|
|
1193
1262
|
type: typeof RoomClientMessageType.DM_READ;
|
|
1194
1263
|
payload: RoomDmReadPayload;
|
|
@@ -1209,6 +1278,21 @@ export type RoomClientMessage =
|
|
|
1209
1278
|
payload: RoomDmReactPayload;
|
|
1210
1279
|
timestamp?: number;
|
|
1211
1280
|
}
|
|
1281
|
+
| {
|
|
1282
|
+
type: typeof RoomClientMessageType.DM_PIN;
|
|
1283
|
+
payload: RoomDmPinPayload;
|
|
1284
|
+
timestamp?: number;
|
|
1285
|
+
}
|
|
1286
|
+
| {
|
|
1287
|
+
type: typeof RoomClientMessageType.DM_UNPIN;
|
|
1288
|
+
payload: RoomDmPinPayload;
|
|
1289
|
+
timestamp?: number;
|
|
1290
|
+
}
|
|
1291
|
+
| {
|
|
1292
|
+
type: typeof RoomClientMessageType.DM_PINS;
|
|
1293
|
+
payload: RoomDmPinsPayload;
|
|
1294
|
+
timestamp?: number;
|
|
1295
|
+
}
|
|
1212
1296
|
| {
|
|
1213
1297
|
type: typeof RoomClientMessageType.TOPIC_CREATE;
|
|
1214
1298
|
payload: RoomTopicCreatePayload;
|
|
@@ -1461,6 +1545,16 @@ export type RoomServerMessage =
|
|
|
1461
1545
|
payload: RoomDmReactionUpdatedPayload;
|
|
1462
1546
|
timestamp: number;
|
|
1463
1547
|
}
|
|
1548
|
+
| {
|
|
1549
|
+
type: typeof RoomServerMessageType.DM_PIN_UPDATED;
|
|
1550
|
+
payload: RoomDmPinUpdatedPayload;
|
|
1551
|
+
timestamp: number;
|
|
1552
|
+
}
|
|
1553
|
+
| {
|
|
1554
|
+
type: typeof RoomServerMessageType.DM_PINS;
|
|
1555
|
+
payload: RoomDmPinsResultPayload;
|
|
1556
|
+
timestamp: number;
|
|
1557
|
+
}
|
|
1464
1558
|
| {
|
|
1465
1559
|
type: typeof RoomServerMessageType.TOPIC;
|
|
1466
1560
|
payload: RoomTopicResultPayload;
|
package/src/social-client.ts
CHANGED
|
@@ -1044,6 +1044,18 @@ export async function removeSpaceMember(
|
|
|
1044
1044
|
);
|
|
1045
1045
|
}
|
|
1046
1046
|
|
|
1047
|
+
/** Leave a server as the authenticated caller. This is deliberately separate
|
|
1048
|
+
* from the manager-only member-removal route so clients never send their own
|
|
1049
|
+
* userId as authorization data. */
|
|
1050
|
+
export async function leaveSpace(
|
|
1051
|
+
config: RoomSocialConfig,
|
|
1052
|
+
spaceId: string,
|
|
1053
|
+
): Promise<{ left?: boolean }> {
|
|
1054
|
+
return socialRequest(config, `/spaces/${encodeURIComponent(spaceId)}/membership`, {
|
|
1055
|
+
method: "DELETE",
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1047
1059
|
export async function changeSpaceMemberRole(
|
|
1048
1060
|
config: RoomSocialConfig,
|
|
1049
1061
|
spaceId: string,
|
package/src/social-types.ts
CHANGED
|
@@ -144,6 +144,8 @@ export interface RoomDmMessage {
|
|
|
144
144
|
imageUrl?: string | null;
|
|
145
145
|
/** Aggregated reactions (per-emoji userId lists). Additive. */
|
|
146
146
|
reactions?: Array<{ emoji: string; users: string[] }>;
|
|
147
|
+
/** Shared pin state. Additive; absent means unpinned. */
|
|
148
|
+
pinned?: boolean;
|
|
147
149
|
/** Client-minted idempotency tag echoed back on DM sends, so a sender's
|
|
148
150
|
* durable queue can settle its optimistic row. Additive (0.4.0). */
|
|
149
151
|
clientMsgId?: string;
|
package/src/types.ts
CHANGED
|
@@ -15,14 +15,22 @@ export type {
|
|
|
15
15
|
RoomDmHistoryResultPayload,
|
|
16
16
|
RoomDmInboxUpdatedPayload,
|
|
17
17
|
RoomDmMessage,
|
|
18
|
+
RoomDmMessageDeletedPayload,
|
|
19
|
+
RoomDmMessageEditedPayload,
|
|
18
20
|
RoomDmMessagePayload,
|
|
19
21
|
RoomDmOpenedPayload,
|
|
20
22
|
RoomDmOpenPayload,
|
|
21
23
|
RoomDmParticipant,
|
|
24
|
+
RoomDmPinsResultPayload,
|
|
25
|
+
RoomDmPinUpdatedPayload,
|
|
26
|
+
RoomDmReactionUpdatedPayload,
|
|
22
27
|
RoomDmReadPayload,
|
|
28
|
+
RoomDmReplyPayload,
|
|
23
29
|
RoomDmSendPayload,
|
|
24
30
|
RoomEntryUpdatedPayload,
|
|
25
31
|
RoomErrorPayload,
|
|
32
|
+
RoomForwardedFrom,
|
|
33
|
+
RoomForwardMetadata,
|
|
26
34
|
RoomFriendPresence,
|
|
27
35
|
RoomInvitedPayload,
|
|
28
36
|
RoomInvitePayload,
|
package/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const VERSION = "0.
|
|
2
|
-
export const RUNNER_VERSION = "0.
|
|
1
|
+
export const VERSION = "0.7.0";
|
|
2
|
+
export const RUNNER_VERSION = "0.7.0";
|