@xmtp/browser-sdk 0.0.17 → 0.0.18

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.
@@ -1,9 +1,14 @@
1
+ import { ConversationType } from "@xmtp/wasm-bindings";
2
+ import { v4 } from "uuid";
3
+ import { AsyncStream, type StreamCallback } from "@/AsyncStream";
1
4
  import type { Client } from "@/Client";
2
5
  import { Conversation } from "@/Conversation";
3
6
  import { DecodedMessage } from "@/DecodedMessage";
4
7
  import type {
8
+ SafeConversation,
5
9
  SafeCreateGroupOptions,
6
10
  SafeListConversationsOptions,
11
+ SafeMessage,
7
12
  } from "@/utils/conversions";
8
13
 
9
14
  export class Conversations {
@@ -99,4 +104,78 @@ export class Conversations {
99
104
  async getHmacKeys() {
100
105
  return this.#client.sendMessage("getHmacKeys", undefined);
101
106
  }
107
+
108
+ async stream(
109
+ callback?: StreamCallback<Conversation>,
110
+ conversationType?: ConversationType,
111
+ ) {
112
+ const streamId = v4();
113
+ const asyncStream = new AsyncStream<Conversation>();
114
+ const endStream = this.#client.handleStreamMessage<SafeConversation>(
115
+ streamId,
116
+ (error, value) => {
117
+ const conversation = value
118
+ ? new Conversation(this.#client, value.id, value)
119
+ : undefined;
120
+ void asyncStream.callback(error, conversation);
121
+ void callback?.(error, conversation);
122
+ },
123
+ );
124
+ await this.#client.sendMessage("streamAllGroups", {
125
+ streamId,
126
+ conversationType,
127
+ });
128
+ asyncStream.onReturn = () => {
129
+ void this.#client.sendMessage("endStream", {
130
+ streamId,
131
+ });
132
+ endStream();
133
+ };
134
+ return asyncStream;
135
+ }
136
+
137
+ async streamGroups(callback?: StreamCallback<Conversation>) {
138
+ return this.stream(callback, ConversationType.Group);
139
+ }
140
+
141
+ async streamDms(callback?: StreamCallback<Conversation>) {
142
+ return this.stream(callback, ConversationType.Dm);
143
+ }
144
+
145
+ async streamAllMessages(
146
+ callback?: StreamCallback<DecodedMessage>,
147
+ conversationType?: ConversationType,
148
+ ) {
149
+ const streamId = v4();
150
+ const asyncStream = new AsyncStream<DecodedMessage>();
151
+ const endStream = this.#client.handleStreamMessage<SafeMessage>(
152
+ streamId,
153
+ (error, value) => {
154
+ const decodedMessage = value
155
+ ? new DecodedMessage(this.#client, value)
156
+ : undefined;
157
+ void asyncStream.callback(error, decodedMessage);
158
+ void callback?.(error, decodedMessage);
159
+ },
160
+ );
161
+ await this.#client.sendMessage("streamAllMessages", {
162
+ streamId,
163
+ conversationType,
164
+ });
165
+ asyncStream.onReturn = () => {
166
+ void this.#client.sendMessage("endStream", {
167
+ streamId,
168
+ });
169
+ endStream();
170
+ };
171
+ return asyncStream;
172
+ }
173
+
174
+ async streamAllGroupMessages(callback?: StreamCallback<DecodedMessage>) {
175
+ return this.streamAllMessages(callback, ConversationType.Group);
176
+ }
177
+
178
+ async streamAllDmMessages(callback?: StreamCallback<DecodedMessage>) {
179
+ return this.streamAllMessages(callback, ConversationType.Dm);
180
+ }
102
181
  }
@@ -51,9 +51,9 @@ export class WorkerClient {
51
51
  return this.#client.isRegistered;
52
52
  }
53
53
 
54
- async createInboxSignatureText() {
54
+ createInboxSignatureText() {
55
55
  try {
56
- return await this.#client.createInboxSignatureText();
56
+ return this.#client.createInboxSignatureText();
57
57
  } catch {
58
58
  return undefined;
59
59
  }
@@ -3,10 +3,12 @@ import type {
3
3
  Conversation,
4
4
  EncodedContent,
5
5
  GroupMember,
6
+ Message,
6
7
  MetadataField,
7
8
  PermissionPolicy,
8
9
  PermissionUpdateType,
9
10
  } from "@xmtp/wasm-bindings";
11
+ import { type StreamCallback } from "@/AsyncStream";
10
12
  import {
11
13
  fromSafeListMessagesOptions,
12
14
  toSafeGroupMember,
@@ -53,14 +55,6 @@ export class WorkerConversation {
53
55
  return this.#group.updateGroupDescription(description);
54
56
  }
55
57
 
56
- get pinnedFrameUrl() {
57
- return this.#group.groupPinnedFrameUrl();
58
- }
59
-
60
- async updatePinnedFrameUrl(pinnedFrameUrl: string) {
61
- return this.#group.updateGroupPinnedFrameUrl(pinnedFrameUrl);
62
- }
63
-
64
58
  get isActive() {
65
59
  return this.#group.isActive();
66
60
  }
@@ -187,4 +181,14 @@ export class WorkerConversation {
187
181
  dmPeerInboxId() {
188
182
  return this.#group.dmPeerInboxId();
189
183
  }
184
+
185
+ stream(callback?: StreamCallback<Message>) {
186
+ const on_message = (message: Message) => {
187
+ void callback?.(null, message);
188
+ };
189
+ const on_error = (error: Error | null) => {
190
+ void callback?.(error, undefined);
191
+ };
192
+ return this.#group.stream({ on_message, on_error });
193
+ }
190
194
  }
@@ -1,4 +1,10 @@
1
- import type { Conversation, Conversations } from "@xmtp/wasm-bindings";
1
+ import {
2
+ ConversationType,
3
+ type Conversation,
4
+ type Conversations,
5
+ type Message,
6
+ } from "@xmtp/wasm-bindings";
7
+ import type { StreamCallback } from "@/AsyncStream";
2
8
  import {
3
9
  fromSafeCreateGroupOptions,
4
10
  fromSafeListConversationsOptions,
@@ -94,4 +100,44 @@ export class WorkerConversations {
94
100
  getHmacKeys() {
95
101
  return this.#conversations.getHmacKeys() as HmacKeys;
96
102
  }
103
+
104
+ stream(
105
+ callback?: StreamCallback<Conversation>,
106
+ conversationType?: ConversationType,
107
+ ) {
108
+ const on_conversation = (conversation: Conversation) => {
109
+ void callback?.(null, conversation);
110
+ };
111
+ const on_error = (error: Error | null) => {
112
+ void callback?.(error, undefined);
113
+ };
114
+ return this.#conversations.stream(
115
+ { on_conversation, on_error },
116
+ conversationType,
117
+ );
118
+ }
119
+
120
+ streamGroups(callback?: StreamCallback<Conversation>) {
121
+ return this.#conversations.stream(callback, ConversationType.Group);
122
+ }
123
+
124
+ streamDms(callback?: StreamCallback<Conversation>) {
125
+ return this.#conversations.stream(callback, ConversationType.Dm);
126
+ }
127
+
128
+ streamAllMessages(
129
+ callback?: StreamCallback<Message>,
130
+ conversationType?: ConversationType,
131
+ ) {
132
+ const on_message = (message: Message) => {
133
+ void callback?.(null, message);
134
+ };
135
+ const on_error = (error: Error | null) => {
136
+ void callback?.(error, undefined);
137
+ };
138
+ return this.#conversations.streamAllMessages(
139
+ { on_message, on_error },
140
+ conversationType,
141
+ );
142
+ }
97
143
  }
@@ -1,6 +1,7 @@
1
1
  import type {
2
2
  ConsentEntityType,
3
3
  ConsentState,
4
+ ConversationType,
4
5
  MetadataField,
5
6
  PermissionPolicy,
6
7
  PermissionUpdateType,
@@ -30,6 +31,17 @@ import type {
30
31
  } from "@/utils/conversions";
31
32
 
32
33
  export type ClientEvents =
34
+ /**
35
+ * Stream actions
36
+ */
37
+ | {
38
+ action: "endStream";
39
+ id: string;
40
+ result: undefined;
41
+ data: {
42
+ streamId: string;
43
+ };
44
+ }
33
45
  /**
34
46
  * Client actions
35
47
  */
@@ -283,6 +295,24 @@ export type ClientEvents =
283
295
  result: SafeHmacKeys;
284
296
  data: undefined;
285
297
  }
298
+ | {
299
+ action: "streamAllGroups";
300
+ id: string;
301
+ result: undefined;
302
+ data: {
303
+ streamId: string;
304
+ conversationType?: ConversationType;
305
+ };
306
+ }
307
+ | {
308
+ action: "streamAllMessages";
309
+ id: string;
310
+ result: undefined;
311
+ data: {
312
+ streamId: string;
313
+ conversationType?: ConversationType;
314
+ };
315
+ }
286
316
  /**
287
317
  * Group actions
288
318
  */
@@ -470,15 +500,6 @@ export type ClientEvents =
470
500
  imageUrl: string;
471
501
  };
472
502
  }
473
- | {
474
- action: "updateGroupPinnedFrameUrl";
475
- id: string;
476
- result: undefined;
477
- data: {
478
- id: string;
479
- pinnedFrameUrl: string;
480
- };
481
- }
482
503
  | {
483
504
  action: "getGroupConsentState";
484
505
  id: string;
@@ -522,6 +543,15 @@ export type ClientEvents =
522
543
  data: {
523
544
  id: string;
524
545
  };
546
+ }
547
+ | {
548
+ action: "streamGroupMessages";
549
+ id: string;
550
+ result: undefined;
551
+ data: {
552
+ groupId: string;
553
+ streamId: string;
554
+ };
525
555
  };
526
556
 
527
557
  export type ClientEventsActions = ClientEvents["action"];
@@ -0,0 +1,30 @@
1
+ import type {
2
+ StreamEventsClientPostMessageData,
3
+ StreamEventsErrorData,
4
+ StreamEventsResult,
5
+ } from "@/types";
6
+ import type { SafeConversation, SafeMessage } from "@/utils/conversions";
7
+
8
+ export type ClientStreamEvents =
9
+ | {
10
+ type: "message";
11
+ streamId: string;
12
+ result: SafeMessage | undefined;
13
+ }
14
+ | {
15
+ type: "group";
16
+ streamId: string;
17
+ result: SafeConversation | undefined;
18
+ };
19
+
20
+ export type ClientStreamEventsTypes = ClientStreamEvents["type"];
21
+
22
+ export type ClientStreamEventsResult<A extends ClientStreamEventsTypes> =
23
+ StreamEventsResult<ClientStreamEvents, A>;
24
+
25
+ export type ClientStreamEventsWorkerPostMessageData<
26
+ A extends ClientStreamEventsTypes,
27
+ > = StreamEventsClientPostMessageData<ClientStreamEvents, A>;
28
+
29
+ export type ClientStreamEventsErrorData =
30
+ StreamEventsErrorData<ClientStreamEvents>;
@@ -44,3 +44,29 @@ export type EventsErrorData<Events extends GenericEvent> = {
44
44
  action: Events["action"];
45
45
  error: string;
46
46
  };
47
+
48
+ export type GenericStreamEvent = {
49
+ type: string;
50
+ streamId: string;
51
+ result: unknown;
52
+ };
53
+
54
+ export type StreamEventsClientMessageData<Events extends GenericStreamEvent> = {
55
+ [Type in Events["type"]]: Omit<Extract<Events, { type: Type }>, "result">;
56
+ }[Events["type"]];
57
+
58
+ export type StreamEventsResult<
59
+ Events extends GenericStreamEvent,
60
+ Type extends Events["type"],
61
+ > = Extract<Events, { type: Type }>["result"];
62
+
63
+ export type StreamEventsClientPostMessageData<
64
+ Events extends GenericStreamEvent,
65
+ Type extends Events["type"],
66
+ > = Extract<Events, { type: Type }>;
67
+
68
+ export type StreamEventsErrorData<Events extends GenericStreamEvent> = {
69
+ streamId: string;
70
+ type: Events["type"];
71
+ error: string;
72
+ };
@@ -210,8 +210,7 @@ export type SafePermissionPolicySet = {
210
210
  updateGroupDescriptionPolicy: PermissionPolicy;
211
211
  updateGroupImageUrlSquarePolicy: PermissionPolicy;
212
212
  updateGroupNamePolicy: PermissionPolicy;
213
- updateGroupPinnedFrameUrlPolicy: PermissionPolicy;
214
- updateMessageExpirationPolicy: PermissionPolicy;
213
+ updateMessageDisappearingPolicy: PermissionPolicy;
215
214
  };
216
215
 
217
216
  export const toSafePermissionPolicySet = (
@@ -224,8 +223,7 @@ export const toSafePermissionPolicySet = (
224
223
  updateGroupDescriptionPolicy: policySet.updateGroupDescriptionPolicy,
225
224
  updateGroupImageUrlSquarePolicy: policySet.updateGroupImageUrlSquarePolicy,
226
225
  updateGroupNamePolicy: policySet.updateGroupNamePolicy,
227
- updateGroupPinnedFrameUrlPolicy: policySet.updateGroupPinnedFrameUrlPolicy,
228
- updateMessageExpirationPolicy: policySet.updateMessageExpirationPolicy,
226
+ updateMessageDisappearingPolicy: policySet.updateMessageDisappearingPolicy,
229
227
  });
230
228
 
231
229
  export const fromSafePermissionPolicySet = (
@@ -239,8 +237,7 @@ export const fromSafePermissionPolicySet = (
239
237
  policySet.updateGroupNamePolicy,
240
238
  policySet.updateGroupDescriptionPolicy,
241
239
  policySet.updateGroupImageUrlSquarePolicy,
242
- policySet.updateGroupPinnedFrameUrlPolicy,
243
- policySet.updateMessageExpirationPolicy,
240
+ policySet.updateMessageDisappearingPolicy,
244
241
  );
245
242
 
246
243
  export type SafeCreateGroupOptions = {
@@ -249,7 +246,6 @@ export type SafeCreateGroupOptions = {
249
246
  imageUrlSquare?: string;
250
247
  name?: string;
251
248
  permissions?: GroupPermissionsOptions;
252
- pinnedFrameUrl?: string;
253
249
  };
254
250
 
255
251
  export const toSafeCreateGroupOptions = (
@@ -258,7 +254,6 @@ export const toSafeCreateGroupOptions = (
258
254
  description: options.groupDescription,
259
255
  imageUrlSquare: options.groupImageUrlSquare,
260
256
  name: options.groupName,
261
- pinnedFrameUrl: options.groupPinnedFrameUrl,
262
257
  permissions: options.permissions,
263
258
  customPermissionPolicySet: options.customPermissionPolicySet,
264
259
  });
@@ -271,7 +266,6 @@ export const fromSafeCreateGroupOptions = (
271
266
  options.name,
272
267
  options.imageUrlSquare,
273
268
  options.description,
274
- options.pinnedFrameUrl,
275
269
  // only include custom policy set if permissions are set to CustomPolicy
276
270
  options.customPermissionPolicySet &&
277
271
  options.permissions === GroupPermissionsOptions.CustomPolicy
@@ -284,7 +278,6 @@ export type SafeConversation = {
284
278
  name: string;
285
279
  imageUrl: string;
286
280
  description: string;
287
- pinnedFrameUrl: string;
288
281
  permissions: {
289
282
  policyType: GroupPermissionsOptions;
290
283
  policySet: {
@@ -295,8 +288,7 @@ export type SafeConversation = {
295
288
  updateGroupDescriptionPolicy: PermissionPolicy;
296
289
  updateGroupImageUrlSquarePolicy: PermissionPolicy;
297
290
  updateGroupNamePolicy: PermissionPolicy;
298
- updateGroupPinnedFrameUrlPolicy: PermissionPolicy;
299
- updateMessageExpirationPolicy: PermissionPolicy;
291
+ updateMessageDisappearingPolicy: PermissionPolicy;
300
292
  };
301
293
  };
302
294
  isActive: boolean;
@@ -317,7 +309,6 @@ export const toSafeConversation = async (
317
309
  const name = conversation.name;
318
310
  const imageUrl = conversation.imageUrl;
319
311
  const description = conversation.description;
320
- const pinnedFrameUrl = conversation.pinnedFrameUrl;
321
312
  const permissions = conversation.permissions;
322
313
  const isActive = conversation.isActive;
323
314
  const addedByInboxId = conversation.addedByInboxId;
@@ -332,7 +323,6 @@ export const toSafeConversation = async (
332
323
  name,
333
324
  imageUrl,
334
325
  description,
335
- pinnedFrameUrl,
336
326
  permissions: {
337
327
  policyType,
338
328
  policySet: {
@@ -344,9 +334,8 @@ export const toSafeConversation = async (
344
334
  updateGroupImageUrlSquarePolicy:
345
335
  policySet.updateGroupImageUrlSquarePolicy,
346
336
  updateGroupNamePolicy: policySet.updateGroupNamePolicy,
347
- updateGroupPinnedFrameUrlPolicy:
348
- policySet.updateGroupPinnedFrameUrlPolicy,
349
- updateMessageExpirationPolicy: policySet.updateMessageExpirationPolicy,
337
+ updateMessageDisappearingPolicy:
338
+ policySet.updateMessageDisappearingPolicy,
350
339
  },
351
340
  },
352
341
  isActive,
@@ -1,9 +1,15 @@
1
+ import type { Conversation, Message, StreamCloser } from "@xmtp/wasm-bindings";
1
2
  import type {
2
3
  ClientEventsActions,
3
4
  ClientEventsClientMessageData,
4
5
  ClientEventsErrorData,
5
6
  ClientEventsWorkerPostMessageData,
6
7
  } from "@/types";
8
+ import type {
9
+ ClientStreamEventsErrorData,
10
+ ClientStreamEventsTypes,
11
+ ClientStreamEventsWorkerPostMessageData,
12
+ } from "@/types/clientStreamEvents";
7
13
  import {
8
14
  fromEncodedContent,
9
15
  fromSafeEncodedContent,
@@ -13,10 +19,13 @@ import {
13
19
  toSafeMessage,
14
20
  } from "@/utils/conversions";
15
21
  import { WorkerClient } from "@/WorkerClient";
22
+ import { WorkerConversation } from "@/WorkerConversation";
16
23
 
17
24
  let client: WorkerClient;
18
25
  let enableLogging = false;
19
26
 
27
+ const streamClosers = new Map<string, StreamCloser>();
28
+
20
29
  /**
21
30
  * Type-safe postMessage
22
31
  */
@@ -33,6 +42,22 @@ const postMessageError = (data: ClientEventsErrorData) => {
33
42
  self.postMessage(data);
34
43
  };
35
44
 
45
+ /**
46
+ * Type-safe postMessage for streams
47
+ */
48
+ const postStreamMessage = <A extends ClientStreamEventsTypes>(
49
+ data: ClientStreamEventsWorkerPostMessageData<A>,
50
+ ) => {
51
+ self.postMessage(data);
52
+ };
53
+
54
+ /**
55
+ * Type-safe postMessage for stream errors
56
+ */
57
+ const postStreamMessageError = (data: ClientStreamEventsErrorData) => {
58
+ self.postMessage(data);
59
+ };
60
+
36
61
  self.onmessage = async (event: MessageEvent<ClientEventsClientMessageData>) => {
37
62
  const { action, id, data } = event.data;
38
63
 
@@ -53,6 +78,28 @@ self.onmessage = async (event: MessageEvent<ClientEventsClientMessageData>) => {
53
78
 
54
79
  try {
55
80
  switch (action) {
81
+ /**
82
+ * Stream actions
83
+ */
84
+ case "endStream": {
85
+ const streamCloser = streamClosers.get(data.streamId);
86
+ if (streamCloser) {
87
+ streamCloser.end();
88
+ streamClosers.delete(data.streamId);
89
+ postMessage({
90
+ id,
91
+ action,
92
+ result: undefined,
93
+ });
94
+ } else {
95
+ postMessageError({
96
+ id,
97
+ action,
98
+ error: `Stream "${data.streamId}" not found`,
99
+ });
100
+ }
101
+ break;
102
+ }
56
103
  /**
57
104
  * Client actions
58
105
  */
@@ -76,7 +123,7 @@ self.onmessage = async (event: MessageEvent<ClientEventsClientMessageData>) => {
76
123
  });
77
124
  break;
78
125
  case "createInboxSignatureText": {
79
- const result = await client.createInboxSignatureText();
126
+ const result = client.createInboxSignatureText();
80
127
  postMessage({
81
128
  id,
82
129
  action,
@@ -266,6 +313,72 @@ self.onmessage = async (event: MessageEvent<ClientEventsClientMessageData>) => {
266
313
  /**
267
314
  * Conversations actions
268
315
  */
316
+ case "streamAllGroups": {
317
+ const streamCallback = async (
318
+ error: Error | null,
319
+ value: Conversation | undefined,
320
+ ) => {
321
+ if (error) {
322
+ postStreamMessageError({
323
+ type: "group",
324
+ streamId: data.streamId,
325
+ error: error.message,
326
+ });
327
+ } else {
328
+ postStreamMessage({
329
+ type: "group",
330
+ streamId: data.streamId,
331
+ result: value
332
+ ? await toSafeConversation(
333
+ new WorkerConversation(client, value),
334
+ )
335
+ : undefined,
336
+ });
337
+ }
338
+ };
339
+ const streamCloser = client.conversations.stream(
340
+ streamCallback,
341
+ data.conversationType,
342
+ );
343
+ streamClosers.set(data.streamId, streamCloser);
344
+ postMessage({
345
+ id,
346
+ action,
347
+ result: undefined,
348
+ });
349
+ break;
350
+ }
351
+ case "streamAllMessages": {
352
+ const streamCallback = (
353
+ error: Error | null,
354
+ value: Message | undefined,
355
+ ) => {
356
+ if (error) {
357
+ postStreamMessageError({
358
+ type: "message",
359
+ streamId: data.streamId,
360
+ error: error.message,
361
+ });
362
+ } else {
363
+ postStreamMessage({
364
+ type: "message",
365
+ streamId: data.streamId,
366
+ result: value ? toSafeMessage(value) : undefined,
367
+ });
368
+ }
369
+ };
370
+ const streamCloser = client.conversations.streamAllMessages(
371
+ streamCallback,
372
+ data.conversationType,
373
+ );
374
+ streamClosers.set(data.streamId, streamCloser);
375
+ postMessage({
376
+ id,
377
+ action,
378
+ result: undefined,
379
+ });
380
+ break;
381
+ }
269
382
  case "getConversations": {
270
383
  const conversations = client.conversations.list(data.options);
271
384
  postMessage({
@@ -466,24 +579,6 @@ self.onmessage = async (event: MessageEvent<ClientEventsClientMessageData>) => {
466
579
  }
467
580
  break;
468
581
  }
469
- case "updateGroupPinnedFrameUrl": {
470
- const group = client.conversations.getConversationById(data.id);
471
- if (group) {
472
- await group.updatePinnedFrameUrl(data.pinnedFrameUrl);
473
- postMessage({
474
- id,
475
- action,
476
- result: undefined,
477
- });
478
- } else {
479
- postMessageError({
480
- id,
481
- action,
482
- error: "Group not found",
483
- });
484
- }
485
- break;
486
- }
487
582
  case "sendGroupMessage": {
488
583
  const group = client.conversations.getConversationById(data.id);
489
584
  if (group) {
@@ -885,6 +980,43 @@ self.onmessage = async (event: MessageEvent<ClientEventsClientMessageData>) => {
885
980
  }
886
981
  break;
887
982
  }
983
+ case "streamGroupMessages": {
984
+ const group = client.conversations.getConversationById(data.groupId);
985
+ if (group) {
986
+ const streamCallback = (
987
+ error: Error | null,
988
+ value: Message | undefined,
989
+ ) => {
990
+ if (error) {
991
+ postStreamMessageError({
992
+ type: "message",
993
+ streamId: data.streamId,
994
+ error: error.message,
995
+ });
996
+ } else {
997
+ postStreamMessage({
998
+ type: "message",
999
+ streamId: data.streamId,
1000
+ result: value ? toSafeMessage(value) : undefined,
1001
+ });
1002
+ }
1003
+ };
1004
+ const streamCloser = group.stream(streamCallback);
1005
+ streamClosers.set(data.streamId, streamCloser);
1006
+ postMessage({
1007
+ id,
1008
+ action,
1009
+ result: undefined,
1010
+ });
1011
+ } else {
1012
+ postMessageError({
1013
+ id,
1014
+ action,
1015
+ error: "Group not found",
1016
+ });
1017
+ }
1018
+ break;
1019
+ }
888
1020
  }
889
1021
  } catch (e) {
890
1022
  postMessageError({