@xmtp/browser-sdk 3.1.1 → 4.0.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/index.d.ts +120 -44
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/workers/client.js +1 -1
- package/dist/workers/client.js.map +1 -1
- package/package.json +2 -2
- package/src/AsyncStream.ts +140 -87
- package/src/ClientWorkerClass.ts +15 -4
- package/src/Conversation.ts +31 -30
- package/src/Conversations.ts +101 -87
- package/src/Preferences.ts +49 -37
- package/src/WorkerConversation.ts +8 -5
- package/src/WorkerConversations.ts +21 -13
- package/src/WorkerPreferences.ts +22 -8
- package/src/index.ts +2 -1
- package/src/types/actions/streams.ts +5 -0
- package/src/utils/errors.ts +13 -0
- package/src/utils/streams.ts +210 -0
- package/src/workers/client.ts +70 -14
package/src/WorkerPreferences.ts
CHANGED
|
@@ -5,8 +5,8 @@ import {
|
|
|
5
5
|
type Conversations,
|
|
6
6
|
type UserPreference,
|
|
7
7
|
} from "@xmtp/wasm-bindings";
|
|
8
|
-
import type { StreamCallback } from "@/AsyncStream";
|
|
9
8
|
import { fromSafeConsent, type SafeConsent } from "@/utils/conversions";
|
|
9
|
+
import type { StreamCallback } from "@/utils/streams";
|
|
10
10
|
|
|
11
11
|
export class WorkerPreferences {
|
|
12
12
|
#client: Client;
|
|
@@ -47,26 +47,40 @@ export class WorkerPreferences {
|
|
|
47
47
|
return this.#client.getConsentState(entityType, entity);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
streamConsent(callback
|
|
50
|
+
streamConsent(callback: StreamCallback<Consent[]>, onFail: () => void) {
|
|
51
51
|
const on_consent_update = (consent: Consent[]) => {
|
|
52
|
-
|
|
52
|
+
callback(null, consent);
|
|
53
53
|
};
|
|
54
54
|
const on_error = (error: Error | null) => {
|
|
55
|
-
|
|
55
|
+
callback(error, undefined);
|
|
56
56
|
};
|
|
57
|
-
|
|
57
|
+
const on_close = () => {
|
|
58
|
+
onFail();
|
|
59
|
+
};
|
|
60
|
+
return this.#conversations.streamConsent({
|
|
61
|
+
on_consent_update,
|
|
62
|
+
on_error,
|
|
63
|
+
on_close,
|
|
64
|
+
});
|
|
58
65
|
}
|
|
59
66
|
|
|
60
|
-
streamPreferences(
|
|
67
|
+
streamPreferences(
|
|
68
|
+
callback: StreamCallback<UserPreference[]>,
|
|
69
|
+
onFail: () => void,
|
|
70
|
+
) {
|
|
61
71
|
const on_user_preference_update = (preferences: UserPreference[]) => {
|
|
62
|
-
|
|
72
|
+
callback(null, preferences);
|
|
63
73
|
};
|
|
64
74
|
const on_error = (error: Error | null) => {
|
|
65
|
-
|
|
75
|
+
callback(error, undefined);
|
|
76
|
+
};
|
|
77
|
+
const on_close = () => {
|
|
78
|
+
onFail();
|
|
66
79
|
};
|
|
67
80
|
return this.#conversations.streamPreferences({
|
|
68
81
|
on_user_preference_update,
|
|
69
82
|
on_error,
|
|
83
|
+
on_close,
|
|
70
84
|
});
|
|
71
85
|
}
|
|
72
86
|
}
|
package/src/index.ts
CHANGED
|
@@ -10,7 +10,7 @@ export { Utils } from "./Utils";
|
|
|
10
10
|
export { ApiUrls, HistorySyncUrls } from "./constants";
|
|
11
11
|
export type * from "./types/options";
|
|
12
12
|
export * from "./utils/conversions";
|
|
13
|
-
export type {
|
|
13
|
+
export type { AsyncStreamProxy } from "./AsyncStream";
|
|
14
14
|
export type {
|
|
15
15
|
Identifier,
|
|
16
16
|
IdentifierKind,
|
|
@@ -53,3 +53,4 @@ export {
|
|
|
53
53
|
export type { Signer, SafeSigner, EOASigner, SCWSigner } from "./utils/signer";
|
|
54
54
|
export { toSafeSigner } from "./utils/signer";
|
|
55
55
|
export * from "./utils/errors";
|
|
56
|
+
export type * from "./utils/streams";
|
|
@@ -25,6 +25,11 @@ export type StreamAction =
|
|
|
25
25
|
action: "stream.preferences";
|
|
26
26
|
streamId: string;
|
|
27
27
|
result: UserPreference[] | undefined;
|
|
28
|
+
}
|
|
29
|
+
| {
|
|
30
|
+
action: "stream.fail";
|
|
31
|
+
streamId: string;
|
|
32
|
+
result: undefined;
|
|
28
33
|
};
|
|
29
34
|
|
|
30
35
|
export type StreamActionName = StreamAction["action"];
|
package/src/utils/errors.ts
CHANGED
|
@@ -59,3 +59,16 @@ export class MissingContentTypeError extends Error {
|
|
|
59
59
|
super("Content type is required when sending content other than text");
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
+
|
|
63
|
+
export class StreamFailedError extends Error {
|
|
64
|
+
constructor(retryAttempts: number) {
|
|
65
|
+
const times = `time${retryAttempts !== 1 ? "s" : ""}`;
|
|
66
|
+
super(`Stream failed, retried ${retryAttempts} ${times}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export class StreamInvalidRetryAttemptsError extends Error {
|
|
71
|
+
constructor() {
|
|
72
|
+
super("Stream retry attempts must be greater than 0");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { AsyncStream, createAsyncStreamProxy } from "@/AsyncStream";
|
|
2
|
+
import { StreamFailedError, StreamInvalidRetryAttemptsError } from "./errors";
|
|
3
|
+
|
|
4
|
+
const isPromise = <T = unknown>(value: unknown): value is Promise<T> => {
|
|
5
|
+
return (
|
|
6
|
+
!!value &&
|
|
7
|
+
(typeof value === "object" || typeof value === "function") &&
|
|
8
|
+
"then" in value &&
|
|
9
|
+
typeof value.then === "function"
|
|
10
|
+
);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
14
|
+
|
|
15
|
+
export const DEFAULT_RETRY_DELAY = 10000; // milliseconds
|
|
16
|
+
export const DEFAULT_RETRY_ATTEMPTS = 6;
|
|
17
|
+
|
|
18
|
+
export type StreamOptions<T = unknown, V = T> = {
|
|
19
|
+
/**
|
|
20
|
+
* Called when the stream ends
|
|
21
|
+
*/
|
|
22
|
+
onEnd?: () => void;
|
|
23
|
+
/**
|
|
24
|
+
* Called when a stream error occurs
|
|
25
|
+
*/
|
|
26
|
+
onError?: (error: Error) => void;
|
|
27
|
+
/**
|
|
28
|
+
* Called when the stream fails
|
|
29
|
+
*/
|
|
30
|
+
onFail?: () => void;
|
|
31
|
+
/**
|
|
32
|
+
* Called when the stream is restarted
|
|
33
|
+
*/
|
|
34
|
+
onRestart?: () => void;
|
|
35
|
+
/**
|
|
36
|
+
* Called when the stream is retried
|
|
37
|
+
*/
|
|
38
|
+
onRetry?: (attempts: number, maxAttempts: number) => void;
|
|
39
|
+
/**
|
|
40
|
+
* Called when a value is emitted from the stream
|
|
41
|
+
*/
|
|
42
|
+
onValue?: (value: V) => void;
|
|
43
|
+
/**
|
|
44
|
+
* The number of times to retry the stream
|
|
45
|
+
* (default: 6)
|
|
46
|
+
*/
|
|
47
|
+
retryAttempts?: number;
|
|
48
|
+
/**
|
|
49
|
+
* The delay between retries (in milliseconds)
|
|
50
|
+
* (default: 10000)
|
|
51
|
+
*/
|
|
52
|
+
retryDelay?: number;
|
|
53
|
+
/**
|
|
54
|
+
* Whether to retry the stream if it fails
|
|
55
|
+
* (default: true)
|
|
56
|
+
*/
|
|
57
|
+
retryOnFail?: boolean;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export type StreamCallback<T = unknown> = (
|
|
61
|
+
error: Error | null,
|
|
62
|
+
value: T | undefined,
|
|
63
|
+
) => void;
|
|
64
|
+
|
|
65
|
+
export type StreamFunction<T = unknown> = (
|
|
66
|
+
callback: StreamCallback<T>,
|
|
67
|
+
onFail: () => void,
|
|
68
|
+
) => Promise<() => void>;
|
|
69
|
+
|
|
70
|
+
export type StreamValueMutator<T = unknown, V = T> = (
|
|
71
|
+
value: T,
|
|
72
|
+
) => V | Promise<V>;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Creates a stream from a stream function
|
|
76
|
+
*
|
|
77
|
+
* If the stream fails, an attempt will be made to restart it.
|
|
78
|
+
*
|
|
79
|
+
* This function is not intended to be used directly.
|
|
80
|
+
*
|
|
81
|
+
* @param streamFunction - The stream function to create a stream from
|
|
82
|
+
* @param streamValueMutator - An optional function to mutate the value emitted from the stream
|
|
83
|
+
* @param options - The options for the stream
|
|
84
|
+
* @param args - Additional arguments to pass to the stream function
|
|
85
|
+
* @returns An async iterable stream proxy
|
|
86
|
+
* @throws {StreamInvalidRetryAttemptsError} if the retryAttempts option is less than 0 and retryOnFail is true
|
|
87
|
+
* @throws {StreamFailedError} if the stream fails and can't be restarted
|
|
88
|
+
*/
|
|
89
|
+
export const createStream = async <T = unknown, V = T>(
|
|
90
|
+
streamFunction: StreamFunction<T>,
|
|
91
|
+
streamValueMutator?: StreamValueMutator<T, V>,
|
|
92
|
+
options?: StreamOptions<T, V>,
|
|
93
|
+
) => {
|
|
94
|
+
const {
|
|
95
|
+
onEnd,
|
|
96
|
+
onError,
|
|
97
|
+
onFail,
|
|
98
|
+
onRestart,
|
|
99
|
+
onRetry,
|
|
100
|
+
onValue,
|
|
101
|
+
retryAttempts = DEFAULT_RETRY_ATTEMPTS,
|
|
102
|
+
retryDelay = DEFAULT_RETRY_DELAY,
|
|
103
|
+
retryOnFail = true,
|
|
104
|
+
} = options ?? {};
|
|
105
|
+
// retry attempts must be greater than 0
|
|
106
|
+
if (retryOnFail && retryAttempts < 0) {
|
|
107
|
+
throw new StreamInvalidRetryAttemptsError();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const asyncStream = new AsyncStream<V>();
|
|
111
|
+
const streamCallback: StreamCallback<T> = (error, value) => {
|
|
112
|
+
// if a stream error occurs, call the onError callback
|
|
113
|
+
if (error) {
|
|
114
|
+
onError?.(error);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
// ensure the value is not undefined
|
|
118
|
+
if (value !== undefined) {
|
|
119
|
+
try {
|
|
120
|
+
// if a streamValueMutator is provided, mutate the value
|
|
121
|
+
if (streamValueMutator) {
|
|
122
|
+
const mutatedValue = streamValueMutator(value);
|
|
123
|
+
if (isPromise(mutatedValue)) {
|
|
124
|
+
void mutatedValue
|
|
125
|
+
.then((mutatedValue) => {
|
|
126
|
+
asyncStream.push(mutatedValue);
|
|
127
|
+
onValue?.(mutatedValue);
|
|
128
|
+
})
|
|
129
|
+
.catch((error: unknown) => {
|
|
130
|
+
onError?.(error as Error);
|
|
131
|
+
});
|
|
132
|
+
} else {
|
|
133
|
+
asyncStream.push(mutatedValue);
|
|
134
|
+
onValue?.(mutatedValue);
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
asyncStream.push(value as unknown as V);
|
|
138
|
+
onValue?.(value as unknown as V);
|
|
139
|
+
}
|
|
140
|
+
} catch (error) {
|
|
141
|
+
onError?.(error as Error);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
const retry = async (retries: number = retryAttempts) => {
|
|
146
|
+
try {
|
|
147
|
+
// if the stream has been retried the maximum number of times without
|
|
148
|
+
// success, throw an error
|
|
149
|
+
if (retries === 0) {
|
|
150
|
+
void asyncStream.end();
|
|
151
|
+
throw new StreamFailedError(retryAttempts);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// wait for the retry delay before attempting to restart the stream
|
|
155
|
+
await wait(retryDelay);
|
|
156
|
+
// call the onRetry callback
|
|
157
|
+
onRetry?.(retryAttempts - retries + 1, retryAttempts);
|
|
158
|
+
|
|
159
|
+
// attempt to restart the stream
|
|
160
|
+
const streamCloser = await streamFunction(streamCallback, () => {
|
|
161
|
+
// call the onFail callback
|
|
162
|
+
onFail?.();
|
|
163
|
+
void retry();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// when the async stream is done, end the stream
|
|
167
|
+
asyncStream.onDone = () => {
|
|
168
|
+
streamCloser();
|
|
169
|
+
onEnd?.();
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// stream restarted, call the onRestart callback
|
|
173
|
+
onRestart?.();
|
|
174
|
+
} catch (error) {
|
|
175
|
+
onError?.(error as Error);
|
|
176
|
+
// retry
|
|
177
|
+
void retry(retries - 1);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
const startRetry = () => {
|
|
181
|
+
// if the stream should be retried, start the process
|
|
182
|
+
if (retryOnFail) {
|
|
183
|
+
void retry();
|
|
184
|
+
} else {
|
|
185
|
+
void asyncStream.end();
|
|
186
|
+
// stream failed and should not be retried, throw an error
|
|
187
|
+
throw new StreamFailedError(0);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
// create the stream
|
|
193
|
+
const streamCloser = await streamFunction(streamCallback, () => {
|
|
194
|
+
// call the onFail callback
|
|
195
|
+
onFail?.();
|
|
196
|
+
startRetry();
|
|
197
|
+
});
|
|
198
|
+
// when the async stream is done, end the stream
|
|
199
|
+
asyncStream.onDone = () => {
|
|
200
|
+
streamCloser();
|
|
201
|
+
onEnd?.();
|
|
202
|
+
};
|
|
203
|
+
} catch (error) {
|
|
204
|
+
onError?.(error as Error);
|
|
205
|
+
startRetry();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// return a proxy for the async stream
|
|
209
|
+
return createAsyncStreamProxy(asyncStream);
|
|
210
|
+
};
|
package/src/workers/client.ts
CHANGED
|
@@ -448,7 +448,17 @@ self.onmessage = async (
|
|
|
448
448
|
});
|
|
449
449
|
}
|
|
450
450
|
};
|
|
451
|
-
const streamCloser = client.preferences.streamConsent(
|
|
451
|
+
const streamCloser = client.preferences.streamConsent(
|
|
452
|
+
streamCallback,
|
|
453
|
+
() => {
|
|
454
|
+
streamClosers.delete(data.streamId);
|
|
455
|
+
postStreamMessage({
|
|
456
|
+
action: "stream.fail",
|
|
457
|
+
streamId: data.streamId,
|
|
458
|
+
result: undefined,
|
|
459
|
+
});
|
|
460
|
+
},
|
|
461
|
+
);
|
|
452
462
|
streamClosers.set(data.streamId, streamCloser);
|
|
453
463
|
postMessage({
|
|
454
464
|
id,
|
|
@@ -476,8 +486,17 @@ self.onmessage = async (
|
|
|
476
486
|
});
|
|
477
487
|
}
|
|
478
488
|
};
|
|
479
|
-
const streamCloser =
|
|
480
|
-
|
|
489
|
+
const streamCloser = client.preferences.streamPreferences(
|
|
490
|
+
streamCallback,
|
|
491
|
+
() => {
|
|
492
|
+
streamClosers.delete(data.streamId);
|
|
493
|
+
postStreamMessage({
|
|
494
|
+
action: "stream.fail",
|
|
495
|
+
streamId: data.streamId,
|
|
496
|
+
result: undefined,
|
|
497
|
+
});
|
|
498
|
+
},
|
|
499
|
+
);
|
|
481
500
|
streamClosers.set(data.streamId, streamCloser);
|
|
482
501
|
postMessage({
|
|
483
502
|
id,
|
|
@@ -490,7 +509,7 @@ self.onmessage = async (
|
|
|
490
509
|
* Conversations actions
|
|
491
510
|
*/
|
|
492
511
|
case "conversations.stream": {
|
|
493
|
-
const streamCallback =
|
|
512
|
+
const streamCallback = (
|
|
494
513
|
error: Error | null,
|
|
495
514
|
value: Conversation | undefined,
|
|
496
515
|
) => {
|
|
@@ -501,19 +520,41 @@ self.onmessage = async (
|
|
|
501
520
|
error,
|
|
502
521
|
});
|
|
503
522
|
} else {
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
523
|
+
if (value) {
|
|
524
|
+
toSafeConversation(new WorkerConversation(client, value))
|
|
525
|
+
.then((result) => {
|
|
526
|
+
postStreamMessage({
|
|
527
|
+
action: "stream.conversation",
|
|
528
|
+
streamId: data.streamId,
|
|
529
|
+
result,
|
|
530
|
+
});
|
|
531
|
+
})
|
|
532
|
+
.catch((error: unknown) => {
|
|
533
|
+
postStreamMessageError({
|
|
534
|
+
action: "stream.conversation",
|
|
535
|
+
streamId: data.streamId,
|
|
536
|
+
error: error as Error,
|
|
537
|
+
});
|
|
538
|
+
});
|
|
539
|
+
} else {
|
|
540
|
+
postStreamMessage({
|
|
541
|
+
action: "stream.conversation",
|
|
542
|
+
streamId: data.streamId,
|
|
543
|
+
result: undefined,
|
|
544
|
+
});
|
|
545
|
+
}
|
|
513
546
|
}
|
|
514
547
|
};
|
|
515
548
|
const streamCloser = client.conversations.stream(
|
|
516
549
|
streamCallback,
|
|
550
|
+
() => {
|
|
551
|
+
streamClosers.delete(data.streamId);
|
|
552
|
+
postStreamMessage({
|
|
553
|
+
action: "stream.fail",
|
|
554
|
+
streamId: data.streamId,
|
|
555
|
+
result: undefined,
|
|
556
|
+
});
|
|
557
|
+
},
|
|
517
558
|
data.conversationType,
|
|
518
559
|
);
|
|
519
560
|
streamClosers.set(data.streamId, streamCloser);
|
|
@@ -541,6 +582,14 @@ self.onmessage = async (
|
|
|
541
582
|
};
|
|
542
583
|
const streamCloser = client.conversations.streamAllMessages(
|
|
543
584
|
streamCallback,
|
|
585
|
+
() => {
|
|
586
|
+
streamClosers.delete(data.streamId);
|
|
587
|
+
postStreamMessage({
|
|
588
|
+
action: "stream.fail",
|
|
589
|
+
streamId: data.streamId,
|
|
590
|
+
result: undefined,
|
|
591
|
+
});
|
|
592
|
+
},
|
|
544
593
|
data.conversationType,
|
|
545
594
|
data.consentStates,
|
|
546
595
|
);
|
|
@@ -876,7 +925,14 @@ self.onmessage = async (
|
|
|
876
925
|
});
|
|
877
926
|
}
|
|
878
927
|
};
|
|
879
|
-
const streamCloser = group.stream(streamCallback)
|
|
928
|
+
const streamCloser = group.stream(streamCallback, () => {
|
|
929
|
+
streamClosers.delete(data.streamId);
|
|
930
|
+
postStreamMessage({
|
|
931
|
+
action: "stream.fail",
|
|
932
|
+
streamId: data.streamId,
|
|
933
|
+
result: undefined,
|
|
934
|
+
});
|
|
935
|
+
});
|
|
880
936
|
streamClosers.set(data.streamId, streamCloser);
|
|
881
937
|
postMessage({ id, action, result: undefined });
|
|
882
938
|
break;
|