@stream-io/feeds-client 0.1.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/@react-bindings/index.ts +2 -0
- package/CHANGELOG.md +44 -0
- package/LICENSE +219 -0
- package/README.md +9 -0
- package/dist/@react-bindings/hooks/useComments.d.ts +12 -0
- package/dist/@react-bindings/hooks/useStateStore.d.ts +3 -0
- package/dist/@react-bindings/index.d.ts +2 -0
- package/dist/index-react-bindings.browser.cjs +56 -0
- package/dist/index-react-bindings.browser.cjs.map +1 -0
- package/dist/index-react-bindings.browser.js +53 -0
- package/dist/index-react-bindings.browser.js.map +1 -0
- package/dist/index-react-bindings.node.cjs +56 -0
- package/dist/index-react-bindings.node.cjs.map +1 -0
- package/dist/index-react-bindings.node.js +53 -0
- package/dist/index-react-bindings.node.js.map +1 -0
- package/dist/index.browser.cjs +5799 -0
- package/dist/index.browser.cjs.map +1 -0
- package/dist/index.browser.js +5782 -0
- package/dist/index.browser.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.node.cjs +5799 -0
- package/dist/index.node.cjs.map +1 -0
- package/dist/index.node.js +5782 -0
- package/dist/index.node.js.map +1 -0
- package/dist/src/Feed.d.ts +109 -0
- package/dist/src/FeedsClient.d.ts +63 -0
- package/dist/src/ModerationClient.d.ts +3 -0
- package/dist/src/common/ActivitySearchSource.d.ts +17 -0
- package/dist/src/common/ApiClient.d.ts +20 -0
- package/dist/src/common/BaseSearchSource.d.ts +87 -0
- package/dist/src/common/ConnectionIdManager.d.ts +11 -0
- package/dist/src/common/EventDispatcher.d.ts +11 -0
- package/dist/src/common/FeedSearchSource.d.ts +17 -0
- package/dist/src/common/Poll.d.ts +34 -0
- package/dist/src/common/SearchController.d.ts +41 -0
- package/dist/src/common/StateStore.d.ts +124 -0
- package/dist/src/common/TokenManager.d.ts +29 -0
- package/dist/src/common/UserSearchSource.d.ts +17 -0
- package/dist/src/common/gen-imports.d.ts +2 -0
- package/dist/src/common/rate-limit.d.ts +2 -0
- package/dist/src/common/real-time/StableWSConnection.d.ts +144 -0
- package/dist/src/common/real-time/event-models.d.ts +36 -0
- package/dist/src/common/types.d.ts +29 -0
- package/dist/src/common/utils.d.ts +54 -0
- package/dist/src/gen/feeds/FeedApi.d.ts +26 -0
- package/dist/src/gen/feeds/FeedsApi.d.ts +237 -0
- package/dist/src/gen/model-decoders/decoders.d.ts +3 -0
- package/dist/src/gen/model-decoders/event-decoder-mapping.d.ts +6 -0
- package/dist/src/gen/models/index.d.ts +3437 -0
- package/dist/src/gen/moderation/ModerationApi.d.ts +21 -0
- package/dist/src/gen-imports.d.ts +3 -0
- package/dist/src/state-updates/activity-reaction-utils.d.ts +10 -0
- package/dist/src/state-updates/activity-utils.d.ts +13 -0
- package/dist/src/state-updates/bookmark-utils.d.ts +14 -0
- package/dist/src/types-internal.d.ts +4 -0
- package/dist/src/types.d.ts +13 -0
- package/dist/src/utils.d.ts +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/index.ts +13 -0
- package/package.json +85 -0
- package/src/Feed.ts +1070 -0
- package/src/FeedsClient.ts +352 -0
- package/src/ModerationClient.ts +3 -0
- package/src/common/ActivitySearchSource.ts +46 -0
- package/src/common/ApiClient.ts +197 -0
- package/src/common/BaseSearchSource.ts +238 -0
- package/src/common/ConnectionIdManager.ts +51 -0
- package/src/common/EventDispatcher.ts +52 -0
- package/src/common/FeedSearchSource.ts +94 -0
- package/src/common/Poll.ts +313 -0
- package/src/common/SearchController.ts +152 -0
- package/src/common/StateStore.ts +314 -0
- package/src/common/TokenManager.ts +112 -0
- package/src/common/UserSearchSource.ts +93 -0
- package/src/common/gen-imports.ts +2 -0
- package/src/common/rate-limit.ts +23 -0
- package/src/common/real-time/StableWSConnection.ts +761 -0
- package/src/common/real-time/event-models.ts +38 -0
- package/src/common/types.ts +40 -0
- package/src/common/utils.ts +194 -0
- package/src/gen/feeds/FeedApi.ts +129 -0
- package/src/gen/feeds/FeedsApi.ts +2192 -0
- package/src/gen/model-decoders/decoders.ts +1877 -0
- package/src/gen/model-decoders/event-decoder-mapping.ts +150 -0
- package/src/gen/models/index.ts +5882 -0
- package/src/gen/moderation/ModerationApi.ts +270 -0
- package/src/gen-imports.ts +3 -0
- package/src/state-updates/activity-reaction-utils.test.ts +348 -0
- package/src/state-updates/activity-reaction-utils.ts +107 -0
- package/src/state-updates/activity-utils.test.ts +257 -0
- package/src/state-updates/activity-utils.ts +80 -0
- package/src/state-updates/bookmark-utils.test.ts +383 -0
- package/src/state-updates/bookmark-utils.ts +157 -0
- package/src/types-internal.ts +5 -0
- package/src/types.ts +20 -0
- package/src/utils.ts +4 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
export type Patch<T> = (value: T) => T;
|
|
2
|
+
export type ValueOrPatch<T> = T | Patch<T>;
|
|
3
|
+
export type Handler<T> = (nextValue: T, previousValue: T | undefined) => void;
|
|
4
|
+
export type Unsubscribe = () => void;
|
|
5
|
+
// aliases
|
|
6
|
+
export type RemovePreprocessor = Unsubscribe;
|
|
7
|
+
export type Preprocessor<T> = Handler<T>;
|
|
8
|
+
|
|
9
|
+
export const isPatch = <T>(value: ValueOrPatch<T>): value is Patch<T> =>
|
|
10
|
+
typeof value === 'function';
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
const noop = () => {};
|
|
14
|
+
|
|
15
|
+
export class StateStore<T extends Record<string, unknown>> {
|
|
16
|
+
protected handlers = new Set<Handler<T>>();
|
|
17
|
+
protected preprocessors = new Set<Preprocessor<T>>();
|
|
18
|
+
|
|
19
|
+
constructor(protected value: T) {}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Allows merging two stores only if their keys differ otherwise there's no way to ensure the data type stability.
|
|
23
|
+
* @experimental
|
|
24
|
+
* This method is experimental and may change in future versions.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
public merge<Q extends StateStore<any>>(
|
|
28
|
+
stateStore: Q extends StateStore<infer L>
|
|
29
|
+
? Extract<keyof T, keyof L> extends never
|
|
30
|
+
? Q
|
|
31
|
+
: never
|
|
32
|
+
: never,
|
|
33
|
+
) {
|
|
34
|
+
return new MergedStateStore<T, Q extends StateStore<infer L> ? L : never>({
|
|
35
|
+
original: this,
|
|
36
|
+
merged: stateStore,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public next(newValueOrPatch: ValueOrPatch<T>): void {
|
|
41
|
+
// newValue (or patch output) should never be a mutated previous value
|
|
42
|
+
const newValue = isPatch(newValueOrPatch)
|
|
43
|
+
? newValueOrPatch(this.value)
|
|
44
|
+
: newValueOrPatch;
|
|
45
|
+
|
|
46
|
+
// do not notify subscribers if the value hasn't changed
|
|
47
|
+
if (newValue === this.value) return;
|
|
48
|
+
|
|
49
|
+
this.preprocessors.forEach((preprocessor) =>
|
|
50
|
+
preprocessor(newValue, this.value),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const oldValue = this.value;
|
|
54
|
+
this.value = newValue;
|
|
55
|
+
|
|
56
|
+
this.handlers.forEach((handler) => handler(this.value, oldValue));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public partialNext = (partial: Partial<T>): void =>
|
|
60
|
+
this.next((current) => ({ ...current, ...partial }));
|
|
61
|
+
|
|
62
|
+
public getLatestValue(): T {
|
|
63
|
+
return this.value;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public subscribe(handler: Handler<T>): Unsubscribe {
|
|
67
|
+
handler(this.value, undefined);
|
|
68
|
+
this.handlers.add(handler);
|
|
69
|
+
return () => {
|
|
70
|
+
this.handlers.delete(handler);
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public subscribeWithSelector = <
|
|
75
|
+
O extends Readonly<Record<string, unknown>> | readonly unknown[],
|
|
76
|
+
>(
|
|
77
|
+
selector: (nextValue: T) => O,
|
|
78
|
+
handler: Handler<O>,
|
|
79
|
+
) => {
|
|
80
|
+
// begin with undefined to reduce amount of selector calls
|
|
81
|
+
let previouslySelectedValues: O | undefined;
|
|
82
|
+
|
|
83
|
+
const wrappedHandler: Handler<T> = (nextValue) => {
|
|
84
|
+
const newlySelectedValues = selector(nextValue);
|
|
85
|
+
|
|
86
|
+
let hasUpdatedValues = typeof previouslySelectedValues === 'undefined';
|
|
87
|
+
|
|
88
|
+
for (const key in previouslySelectedValues) {
|
|
89
|
+
if (previouslySelectedValues[key] === newlySelectedValues[key])
|
|
90
|
+
continue;
|
|
91
|
+
hasUpdatedValues = true;
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!hasUpdatedValues) return;
|
|
96
|
+
|
|
97
|
+
// save a copy of previouslySelectedValues before running
|
|
98
|
+
// handler - if previouslySelectedValues are set to
|
|
99
|
+
// newlySelectedValues after the handler call, there's a chance
|
|
100
|
+
// that it'll never get set as handler can throw and flow might
|
|
101
|
+
// go out of sync
|
|
102
|
+
const previouslySelectedValuesCopy = previouslySelectedValues;
|
|
103
|
+
previouslySelectedValues = newlySelectedValues;
|
|
104
|
+
|
|
105
|
+
handler(newlySelectedValues, previouslySelectedValuesCopy);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
return this.subscribe(wrappedHandler);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Registers a preprocessor function that will be called before the state is updated.
|
|
113
|
+
*
|
|
114
|
+
* Preprocessors are invoked with the new and previous values whenever `next` or `partialNext` methods
|
|
115
|
+
* are called, allowing you to mutate or react to the new value before it is set. Preprocessors run in the
|
|
116
|
+
* order they were registered.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```ts
|
|
120
|
+
* const store = new StateStore<{ count: number; isMaxValue: bool; }>({ count: 0, isMaxValue: false });
|
|
121
|
+
*
|
|
122
|
+
* store.addPreprocessor((nextValue, prevValue) => {
|
|
123
|
+
* if (nextValue.count > 10) {
|
|
124
|
+
* nextValue.count = 10; // Clamp the value to a maximum of 10
|
|
125
|
+
* }
|
|
126
|
+
*
|
|
127
|
+
* if (nextValue.count === 10) {
|
|
128
|
+
* nextValue.isMaxValue = true; // Set isMaxValue to true if count is 10
|
|
129
|
+
* } else {
|
|
130
|
+
* nextValue.isMaxValue = false; // Reset isMaxValue otherwise
|
|
131
|
+
* }
|
|
132
|
+
* });
|
|
133
|
+
*
|
|
134
|
+
* store.partialNext({ count: 15 });
|
|
135
|
+
*
|
|
136
|
+
* store.getLatestValue(); // { count: 10, isMaxValue: true }
|
|
137
|
+
*
|
|
138
|
+
* store.partialNext({ count: 5 });
|
|
139
|
+
*
|
|
140
|
+
* store.getLatestValue(); // { count: 5, isMaxValue: false }
|
|
141
|
+
* ```
|
|
142
|
+
*
|
|
143
|
+
* @param preprocessor - The function to be called with the next and previous values before the state is updated.
|
|
144
|
+
* @returns A `RemovePreprocessor` function that removes the preprocessor when called.
|
|
145
|
+
*/
|
|
146
|
+
public addPreprocessor(preprocessor: Preprocessor<T>): RemovePreprocessor {
|
|
147
|
+
this.preprocessors.add(preprocessor);
|
|
148
|
+
|
|
149
|
+
return () => {
|
|
150
|
+
this.preprocessors.delete(preprocessor);
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Represents a merged state store that combines two separate state stores into one.
|
|
157
|
+
*
|
|
158
|
+
* The MergedStateStore allows combining two stores with non-overlapping keys.
|
|
159
|
+
* It extends StateStore with the combined type of both source stores.
|
|
160
|
+
* Changes to either the original or merged store will propagate to the combined store.
|
|
161
|
+
*
|
|
162
|
+
* Note: Direct mutations (next, partialNext, addPreprocessor) are disabled on the merged store.
|
|
163
|
+
* You should instead call these methods on the original or merged stores.
|
|
164
|
+
*
|
|
165
|
+
* @template O The type of the original state store
|
|
166
|
+
* @template M The type of the merged state store
|
|
167
|
+
*
|
|
168
|
+
* @experimental
|
|
169
|
+
* This class is experimental and may change in future versions.
|
|
170
|
+
*/
|
|
171
|
+
export class MergedStateStore<
|
|
172
|
+
O extends Record<string, unknown>,
|
|
173
|
+
M extends Record<string, unknown>,
|
|
174
|
+
> extends StateStore<O & M> {
|
|
175
|
+
public readonly original: StateStore<O>;
|
|
176
|
+
public readonly merged: StateStore<M>;
|
|
177
|
+
private cachedOriginalValue: O;
|
|
178
|
+
private cachedMergedValue: M;
|
|
179
|
+
|
|
180
|
+
constructor({
|
|
181
|
+
original,
|
|
182
|
+
merged,
|
|
183
|
+
}: {
|
|
184
|
+
original: StateStore<O>;
|
|
185
|
+
merged: StateStore<M>;
|
|
186
|
+
}) {
|
|
187
|
+
const originalValue = original.getLatestValue();
|
|
188
|
+
const mergedValue = merged.getLatestValue();
|
|
189
|
+
|
|
190
|
+
super({
|
|
191
|
+
...originalValue,
|
|
192
|
+
...mergedValue,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
this.cachedOriginalValue = originalValue;
|
|
196
|
+
this.cachedMergedValue = mergedValue;
|
|
197
|
+
|
|
198
|
+
this.original = original;
|
|
199
|
+
this.merged = merged;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Subscribes to changes in the merged state store.
|
|
204
|
+
*
|
|
205
|
+
* This method extends the base subscribe functionality to handle the merged nature of this store:
|
|
206
|
+
* 1. The first subscriber triggers registration of helper subscribers that listen to both source stores
|
|
207
|
+
* 2. Changes from either source store are propagated to this merged store
|
|
208
|
+
* 3. Source store values are cached to prevent unnecessary updates
|
|
209
|
+
*
|
|
210
|
+
* When the first subscriber is added, the method sets up listeners on both original and merged stores.
|
|
211
|
+
* These listeners update the combined store value whenever either source store changes.
|
|
212
|
+
* All subscriptions (helpers and the actual handler) are tracked so they can be properly cleaned up.
|
|
213
|
+
*
|
|
214
|
+
* @param handler - The callback function that will be executed when the state changes
|
|
215
|
+
* @returns An unsubscribe function that, when called, removes the subscription and any helper subscriptions
|
|
216
|
+
*/
|
|
217
|
+
public subscribe(handler: Handler<O & M>) {
|
|
218
|
+
const unsubscribeFunctions: Unsubscribe[] = [];
|
|
219
|
+
|
|
220
|
+
// first subscriber will also register helpers which listen to changes of the
|
|
221
|
+
// "original" and "merged" stores, combined outputs will be emitted through super.next
|
|
222
|
+
// whenever cached values do not equal (always apart from the initial subscription)
|
|
223
|
+
// since the actual handler subscription is registered after helpers, the actual
|
|
224
|
+
// handler will run only once
|
|
225
|
+
if (!this.handlers.size) {
|
|
226
|
+
const base = (nextValue: O | M) => {
|
|
227
|
+
super.next((currentValue) => ({
|
|
228
|
+
...currentValue,
|
|
229
|
+
...nextValue,
|
|
230
|
+
}));
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
unsubscribeFunctions.push(
|
|
234
|
+
this.original.subscribe((nextValue) => {
|
|
235
|
+
if (nextValue === this.cachedOriginalValue) return;
|
|
236
|
+
this.cachedOriginalValue = nextValue;
|
|
237
|
+
base(nextValue);
|
|
238
|
+
}),
|
|
239
|
+
this.merged.subscribe((nextValue) => {
|
|
240
|
+
if (nextValue === this.cachedMergedValue) return;
|
|
241
|
+
this.cachedMergedValue = nextValue;
|
|
242
|
+
base(nextValue);
|
|
243
|
+
}),
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
unsubscribeFunctions.push(super.subscribe(handler));
|
|
248
|
+
|
|
249
|
+
return () => {
|
|
250
|
+
unsubscribeFunctions.forEach((unsubscribe) => unsubscribe());
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Retrieves the latest combined state from both original and merged stores.
|
|
256
|
+
*
|
|
257
|
+
* This method extends the base getLatestValue functionality to ensure the merged store
|
|
258
|
+
* remains in sync with its source stores even when there are no active subscribers.
|
|
259
|
+
*
|
|
260
|
+
* When there are no handlers registered, the method:
|
|
261
|
+
* 1. Fetches the latest values from both source stores
|
|
262
|
+
* 2. Compares them with the cached values to detect changes
|
|
263
|
+
* 3. If changes are detected, updates the internal value and caches
|
|
264
|
+
* the new source values to maintain consistency
|
|
265
|
+
*
|
|
266
|
+
* This approach ensures that calling getLatestValue() always returns the most
|
|
267
|
+
* up-to-date combined state, even if the merged store hasn't been actively
|
|
268
|
+
* receiving updates through subscriptions.
|
|
269
|
+
*
|
|
270
|
+
* @returns The latest combined state from both original and merged stores
|
|
271
|
+
*/
|
|
272
|
+
public getLatestValue() {
|
|
273
|
+
// if there are no handlers registered to MergedStore then the local value might be out-of-sync
|
|
274
|
+
// pull latest and compare against cached - if they differ, cache latest and produce new combined
|
|
275
|
+
if (!this.handlers.size) {
|
|
276
|
+
const originalValue = this.original.getLatestValue();
|
|
277
|
+
const mergedValue = this.merged.getLatestValue();
|
|
278
|
+
|
|
279
|
+
if (
|
|
280
|
+
originalValue !== this.cachedOriginalValue ||
|
|
281
|
+
mergedValue !== this.cachedMergedValue
|
|
282
|
+
) {
|
|
283
|
+
this.value = {
|
|
284
|
+
...originalValue,
|
|
285
|
+
...mergedValue,
|
|
286
|
+
};
|
|
287
|
+
this.cachedMergedValue = mergedValue;
|
|
288
|
+
this.cachedOriginalValue = originalValue;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return super.getLatestValue();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// override original methods and "disable" them
|
|
296
|
+
public next = () => {
|
|
297
|
+
console.warn(
|
|
298
|
+
`${MergedStateStore.name}.next is disabled, call original.next or merged.next instead`,
|
|
299
|
+
);
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
public partialNext = () => {
|
|
303
|
+
console.warn(
|
|
304
|
+
`${MergedStateStore.name}.partialNext is disabled, call original.partialNext or merged.partialNext instead`,
|
|
305
|
+
);
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
public addPreprocessor() {
|
|
309
|
+
console.warn(
|
|
310
|
+
`${MergedStateStore.name}.addPreprocessor is disabled, call original.addPreprocessor or merged.addPreprocessor instead`,
|
|
311
|
+
);
|
|
312
|
+
return noop;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { isFunction, retryInterval, sleep } from './utils';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* TokenManager
|
|
5
|
+
*
|
|
6
|
+
* Handles all the operations around user token.
|
|
7
|
+
*/
|
|
8
|
+
export class TokenManager {
|
|
9
|
+
loadTokenPromise: Promise<string> | null;
|
|
10
|
+
type: 'static' | 'provider';
|
|
11
|
+
token?: string;
|
|
12
|
+
tokenProvider?: string | (() => Promise<string>);
|
|
13
|
+
|
|
14
|
+
constructor() {
|
|
15
|
+
this.loadTokenPromise = null;
|
|
16
|
+
|
|
17
|
+
this.type = 'static';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Set the static string token or token provider.
|
|
22
|
+
* Token provider should return a token string or a promise which resolves to string token.
|
|
23
|
+
*
|
|
24
|
+
* @param {TokenOrProvider} tokenOrProvider - the token or token provider.
|
|
25
|
+
* @param {UserResponse} user - the user object.
|
|
26
|
+
* @param {boolean} isAnonymous - whether the user is anonymous or not.
|
|
27
|
+
*/
|
|
28
|
+
setTokenOrProvider = (tokenOrProvider?: string | (() => Promise<string>)) => {
|
|
29
|
+
if (isFunction(tokenOrProvider)) {
|
|
30
|
+
this.tokenProvider = tokenOrProvider;
|
|
31
|
+
this.type = 'provider';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (typeof tokenOrProvider === 'string') {
|
|
35
|
+
this.token = tokenOrProvider;
|
|
36
|
+
this.type = 'static';
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Resets the token manager.
|
|
42
|
+
* Useful for client disconnection or switching user.
|
|
43
|
+
*/
|
|
44
|
+
reset = () => {
|
|
45
|
+
this.token = undefined;
|
|
46
|
+
this.loadTokenPromise = null;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Fetches a token from tokenProvider function and sets in tokenManager.
|
|
50
|
+
// In case of static token, it will simply resolve to static token.
|
|
51
|
+
loadToken = () => {
|
|
52
|
+
if (this.loadTokenPromise) {
|
|
53
|
+
return this.loadTokenPromise;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this.loadTokenPromise = new Promise(async (resolve, reject) => {
|
|
57
|
+
if (this.type === 'static') {
|
|
58
|
+
this.loadTokenPromise = null;
|
|
59
|
+
return resolve(this.token!);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (this.tokenProvider && typeof this.tokenProvider !== 'string') {
|
|
63
|
+
this.token = undefined;
|
|
64
|
+
const tokenProvider = this.tokenProvider;
|
|
65
|
+
const loadTokenWithRetries = async (previousFailuresCount = 0) => {
|
|
66
|
+
try {
|
|
67
|
+
this.token = await tokenProvider();
|
|
68
|
+
} catch (e) {
|
|
69
|
+
const numberOfFailures = ++previousFailuresCount;
|
|
70
|
+
await sleep(retryInterval(numberOfFailures));
|
|
71
|
+
if (numberOfFailures === 3) {
|
|
72
|
+
this.loadTokenPromise = null;
|
|
73
|
+
return reject(
|
|
74
|
+
new Error(
|
|
75
|
+
|
|
76
|
+
`Stream error: tried to get token ${numberOfFailures} times, but it failed with ${e}. Check your token provider`,
|
|
77
|
+
{ cause: e },
|
|
78
|
+
),
|
|
79
|
+
);
|
|
80
|
+
} else {
|
|
81
|
+
return await loadTokenWithRetries(numberOfFailures);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
this.loadTokenPromise = null;
|
|
85
|
+
resolve(this.token);
|
|
86
|
+
};
|
|
87
|
+
return await loadTokenWithRetries();
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return this.loadTokenPromise;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Returns the current token, or fetches in a new one if there is no current token
|
|
95
|
+
getToken = () => {
|
|
96
|
+
if (this.token) {
|
|
97
|
+
return this.token;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (this.tokenProvider) {
|
|
101
|
+
if (this.loadTokenPromise) {
|
|
102
|
+
return this.loadTokenPromise;
|
|
103
|
+
} else {
|
|
104
|
+
return this.loadToken();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
throw new Error(`Can't get token because token provider isn't set`);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
isStatic = () => this.type === 'static';
|
|
112
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { BaseSearchSource } from './BaseSearchSource';
|
|
2
|
+
import type { SearchSourceOptions } from './BaseSearchSource';
|
|
3
|
+
|
|
4
|
+
import { FeedsClient } from '../FeedsClient';
|
|
5
|
+
import { UserResponse } from '../gen/models';
|
|
6
|
+
|
|
7
|
+
export class UserSearchSource extends BaseSearchSource<UserResponse> {
|
|
8
|
+
readonly type = 'user' as const;
|
|
9
|
+
private readonly client: FeedsClient;
|
|
10
|
+
// messageSearchChannelFilters: ChannelFilters | undefined;
|
|
11
|
+
// messageSearchFilters: MessageFilters | undefined;
|
|
12
|
+
// messageSearchSort: SearchMessageSort | undefined;
|
|
13
|
+
// channelQueryFilters: ChannelFilters | undefined;
|
|
14
|
+
// channelQuerySort: ChannelSort | undefined;
|
|
15
|
+
// channelQueryOptions: Omit<ChannelOptions, 'limit' | 'offset'> | undefined;
|
|
16
|
+
|
|
17
|
+
constructor(client: FeedsClient, options?: SearchSourceOptions) {
|
|
18
|
+
super(options);
|
|
19
|
+
this.client = client;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
protected async query(searchQuery: string) {
|
|
23
|
+
const { connectedUser } = this.client.state.getLatestValue();
|
|
24
|
+
if (!connectedUser) return { items: [] };
|
|
25
|
+
|
|
26
|
+
// const channelFilters: ChannelFilters = {
|
|
27
|
+
// members: { $in: [this.client.userID] },
|
|
28
|
+
// ...this.messageSearchChannelFilters,
|
|
29
|
+
// } as ChannelFilters;
|
|
30
|
+
|
|
31
|
+
// const messageFilters: MessageFilters = {
|
|
32
|
+
// text: searchQuery,
|
|
33
|
+
// type: 'regular', // FIXME: type: 'reply' resp. do not filter by type and allow to jump to a message in a thread - missing support
|
|
34
|
+
// ...this.messageSearchFilters,
|
|
35
|
+
// } as MessageFilters;
|
|
36
|
+
|
|
37
|
+
// const sort: SearchMessageSort = {
|
|
38
|
+
// created_at: -1,
|
|
39
|
+
// ...this.messageSearchSort,
|
|
40
|
+
// };
|
|
41
|
+
|
|
42
|
+
// const options = {
|
|
43
|
+
// limit: this.pageSize,
|
|
44
|
+
// next: this.next,
|
|
45
|
+
// sort,
|
|
46
|
+
// } as SearchOptions;
|
|
47
|
+
|
|
48
|
+
// const { next, results } = await this.client.search(
|
|
49
|
+
// channelFilters,
|
|
50
|
+
// messageFilters,
|
|
51
|
+
// options,
|
|
52
|
+
// );
|
|
53
|
+
// const items = results.map(({ message }) => message);
|
|
54
|
+
|
|
55
|
+
// const cids = Array.from(
|
|
56
|
+
// items.reduce((acc, message) => {
|
|
57
|
+
// if (message.cid && !this.client.activeChannels[message.cid])
|
|
58
|
+
// acc.add(message.cid);
|
|
59
|
+
// return acc;
|
|
60
|
+
// }, new Set<string>()), // keep the cids unique
|
|
61
|
+
// );
|
|
62
|
+
// const allChannelsLoadedLocally = cids.length === 0;
|
|
63
|
+
// if (!allChannelsLoadedLocally) {
|
|
64
|
+
// await this.client.queryChannels(
|
|
65
|
+
// {
|
|
66
|
+
// cid: { $in: cids },
|
|
67
|
+
// ...this.channelQueryFilters,
|
|
68
|
+
// } as ChannelFilters,
|
|
69
|
+
// {
|
|
70
|
+
// last_message_at: -1,
|
|
71
|
+
// ...this.channelQuerySort,
|
|
72
|
+
// },
|
|
73
|
+
// this.channelQueryOptions,
|
|
74
|
+
// );
|
|
75
|
+
// }
|
|
76
|
+
|
|
77
|
+
const { users: items } = await this.client.queryUsers({
|
|
78
|
+
payload: {
|
|
79
|
+
filter_conditions: {
|
|
80
|
+
name: {
|
|
81
|
+
$autocomplete: searchQuery,
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return { items, next: undefined };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
protected filterQueryResults(items: UserResponse[]) {
|
|
91
|
+
return items;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { RateLimit } from './types';
|
|
2
|
+
|
|
3
|
+
export const getRateLimitFromResponseHeader = (
|
|
4
|
+
response_headers: Record<string, string>,
|
|
5
|
+
) => {
|
|
6
|
+
const rate_limit = response_headers['x-ratelimit-limit']
|
|
7
|
+
? +response_headers['x-ratelimit-limit']!
|
|
8
|
+
: undefined;
|
|
9
|
+
const rate_limit_remaining = response_headers['x-ratelimit-remaining']
|
|
10
|
+
? +response_headers['x-ratelimit-remaining']!
|
|
11
|
+
: undefined;
|
|
12
|
+
const rate_limit_reset = response_headers['x-ratelimit-reset']
|
|
13
|
+
? new Date(+response_headers['x-ratelimit-reset']! * 1000)
|
|
14
|
+
: undefined;
|
|
15
|
+
|
|
16
|
+
const result: RateLimit = {
|
|
17
|
+
rate_limit,
|
|
18
|
+
rate_limit_remaining,
|
|
19
|
+
rate_limit_reset,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return result;
|
|
23
|
+
};
|