@equinor/fusion-framework-module-bookmark 4.1.0 → 4.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/version.js +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +13 -10
- package/CHANGELOG.md +0 -821
- package/src/BookmarkClient.interface.ts +0 -157
- package/src/BookmarkClient.ts +0 -290
- package/src/BookmarkFlowError.ts +0 -38
- package/src/BookmarkModuleConfigurator.ts +0 -412
- package/src/BookmarkProvider.events.ts +0 -87
- package/src/BookmarkProvider.interface.ts +0 -180
- package/src/BookmarkProvider.selectors.ts +0 -69
- package/src/BookmarkProvider.ts +0 -1549
- package/src/BookmarkProviderError.ts +0 -19
- package/src/__tests__/mock/bookmark-mock.test.ts +0 -199
- package/src/bookmark-actions.ts +0 -132
- package/src/bookmark-config.schema.ts +0 -55
- package/src/bookmark-flows/bookmark-api-flows.ts +0 -45
- package/src/bookmark-flows/handle-add-bookmark-as-favorite.ts +0 -49
- package/src/bookmark-flows/handle-create-bookmark.ts +0 -47
- package/src/bookmark-flows/handle-delete-bookmark.ts +0 -47
- package/src/bookmark-flows/handle-fetch-all-bookmark.ts +0 -53
- package/src/bookmark-flows/handle-fetch-bookmark-data.ts +0 -58
- package/src/bookmark-flows/handle-fetch-bookmark.ts +0 -64
- package/src/bookmark-flows/handle-remove-bookmark-from-favorites.ts +0 -49
- package/src/bookmark-flows/handle-remove-bookmark.ts +0 -76
- package/src/bookmark-flows/handle-update-bookmark.ts +0 -48
- package/src/bookmark-flows/index.ts +0 -10
- package/src/bookmark-module.ts +0 -98
- package/src/bookmark.schemas.ts +0 -81
- package/src/create-bookmark-reducer.ts +0 -149
- package/src/create-bookmark-store.ts +0 -61
- package/src/enable-bookmark.ts +0 -44
- package/src/index.ts +0 -43
- package/src/mock/BookmarkMockClient.ts +0 -310
- package/src/mock/BookmarkMockConfigurator.ts +0 -159
- package/src/mock/index.ts +0 -25
- package/src/mock/module.ts +0 -64
- package/src/types.ts +0 -121
- package/src/version.ts +0 -2
- package/tsconfig.json +0 -30
- package/vitest.config.ts +0 -11
package/src/BookmarkProvider.ts
DELETED
|
@@ -1,1549 +0,0 @@
|
|
|
1
|
-
import { Observable, Subscription, forkJoin, from, lastValueFrom, of, defer } from 'rxjs';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
filter,
|
|
5
|
-
switchMap,
|
|
6
|
-
tap,
|
|
7
|
-
map,
|
|
8
|
-
mergeScan,
|
|
9
|
-
timeout,
|
|
10
|
-
raceWith,
|
|
11
|
-
first,
|
|
12
|
-
catchError,
|
|
13
|
-
} from 'rxjs/operators';
|
|
14
|
-
|
|
15
|
-
import { castDraft, createDraft, finishDraft } from 'immer';
|
|
16
|
-
|
|
17
|
-
import { v4 as generateGUID } from 'uuid';
|
|
18
|
-
|
|
19
|
-
import deepEqual from 'fast-deep-equal/es6';
|
|
20
|
-
|
|
21
|
-
import {
|
|
22
|
-
FrameworkEvent,
|
|
23
|
-
type FrameworkEventInitType,
|
|
24
|
-
} from '@equinor/fusion-framework-module-event';
|
|
25
|
-
|
|
26
|
-
import { isFailureAction } from '@equinor/fusion-observable';
|
|
27
|
-
|
|
28
|
-
import { SemanticVersion } from '@equinor/fusion-framework-module';
|
|
29
|
-
|
|
30
|
-
import type {
|
|
31
|
-
Bookmark,
|
|
32
|
-
BookmarkData,
|
|
33
|
-
BookmarkWithoutData,
|
|
34
|
-
Bookmarks,
|
|
35
|
-
BookmarkModuleConfig,
|
|
36
|
-
} from './types';
|
|
37
|
-
|
|
38
|
-
import type { BookmarkNew, BookmarkUpdate, IBookmarkClient } from './BookmarkClient.interface';
|
|
39
|
-
import { type BookmarkActions, bookmarkActions } from './bookmark-actions';
|
|
40
|
-
import {
|
|
41
|
-
type BookmarkState,
|
|
42
|
-
createBookmarkStore,
|
|
43
|
-
type BookmarkStore,
|
|
44
|
-
} from './create-bookmark-store';
|
|
45
|
-
import {
|
|
46
|
-
activeBookmarkSelector,
|
|
47
|
-
bookmarkSelector,
|
|
48
|
-
bookmarksSelector,
|
|
49
|
-
errorsSelector,
|
|
50
|
-
} from './BookmarkProvider.selectors';
|
|
51
|
-
|
|
52
|
-
import { type BookmarkFlowError, BookmarkProviderError } from './BookmarkFlowError';
|
|
53
|
-
|
|
54
|
-
import type { BookmarkProviderEventMap } from './BookmarkProvider.events';
|
|
55
|
-
import { version } from './version';
|
|
56
|
-
|
|
57
|
-
import type {
|
|
58
|
-
BookmarkCreateArgs,
|
|
59
|
-
BookmarkPayloadGenerator,
|
|
60
|
-
BookmarkUpdateOptions,
|
|
61
|
-
IBookmarkProvider,
|
|
62
|
-
} from './BookmarkProvider.interface';
|
|
63
|
-
|
|
64
|
-
// Default timeout for bookmark operations (2 minutes)
|
|
65
|
-
const defaultTimeout = 2 * 60 * 1000;
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* The `BookmarkProvider` class is responsible for managing bookmarks in the application.
|
|
69
|
-
* It provides methods for creating, updating, and removing bookmarks, as well as managing the current bookmark and the list of bookmarks.
|
|
70
|
-
*
|
|
71
|
-
* The `BookmarkProvider` uses a `BookmarkStore` to manage the state of the bookmarks, and an `IBookmarkClient` to interact with the backend API.
|
|
72
|
-
*
|
|
73
|
-
* The `BookmarkProvider` also supports event listeners for various bookmark-related events, such as when the current bookmark changes or when a bookmark is created, updated, or removed.
|
|
74
|
-
*/
|
|
75
|
-
export class BookmarkProvider implements IBookmarkProvider {
|
|
76
|
-
/** provided configuration for the bookmark provider */
|
|
77
|
-
#config: BookmarkModuleConfig;
|
|
78
|
-
|
|
79
|
-
/** state machine of the bookmark provider */
|
|
80
|
-
#store: BookmarkStore;
|
|
81
|
-
|
|
82
|
-
/** collection of callback for creating bookmark payload */
|
|
83
|
-
#payloadGenerators: Array<BookmarkPayloadGenerator> = [];
|
|
84
|
-
|
|
85
|
-
/** collection of subscriptions for bookmark events */
|
|
86
|
-
#subscriptions = new Subscription();
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* @deprecated
|
|
90
|
-
* this will be removed as soon as applications have been migrated to use the bookmark provider
|
|
91
|
-
*/
|
|
92
|
-
get config() {
|
|
93
|
-
return {
|
|
94
|
-
getCurrentAppIdentification() {
|
|
95
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
96
|
-
// @ts-expect-error
|
|
97
|
-
return window.Fusion.modules.app.current.appKey;
|
|
98
|
-
},
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* @deprecated
|
|
104
|
-
* this will be removed as soon as applications have been migrated to use the bookmark provider
|
|
105
|
-
* @param fn - The payload generator callback to register.
|
|
106
|
-
* @template T - The bookmark data type produced by the generator.
|
|
107
|
-
* @returns A function that unregisters the payload generator when called.
|
|
108
|
-
*/
|
|
109
|
-
addStateCreator<T extends BookmarkData>(fn: BookmarkPayloadGenerator<T>) {
|
|
110
|
-
console.warn('addStateCreator is deprecated, use addPayloadGenerator instead');
|
|
111
|
-
return this.addPayloadGenerator(fn);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* @deprecated
|
|
116
|
-
* this will be removed as soon as applications have been migrated to use the bookmark provider
|
|
117
|
-
* @param id - The ID of the bookmark to delete.
|
|
118
|
-
*/
|
|
119
|
-
deleteBookmarkByIdAsync(id: string): Promise<void> {
|
|
120
|
-
console.warn('deleteBookmarkByIdAsync is deprecated, use deleteBookmarkAsync instead');
|
|
121
|
-
return this.deleteBookmarkAsync(id);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* @deprecated
|
|
126
|
-
* this will be removed as soon as applications have been migrated to use the bookmark provider
|
|
127
|
-
* @param id - The ID of the bookmark to add as a favorite.
|
|
128
|
-
* @returns The updated bookmark, or undefined if it could not be found.
|
|
129
|
-
*/
|
|
130
|
-
addBookmarkFavoriteAsync(id: string): Promise<Bookmark | undefined> {
|
|
131
|
-
console.warn('addBookmarkFavoriteAsync is deprecated, use addBookmarkToFavoritesAsync instead');
|
|
132
|
-
return this.addBookmarkToFavoritesAsync(id);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* @deprecated
|
|
137
|
-
* this will be removed as soon as applications have been migrated to use the bookmark provider
|
|
138
|
-
* @param id - The ID of the bookmark to remove as a favorite.
|
|
139
|
-
*/
|
|
140
|
-
removeBookmarkFavoriteAsync(id: string): Promise<void> {
|
|
141
|
-
console.warn(
|
|
142
|
-
'removeBookmarkFavoriteAsync is deprecated, use removeBookmarkAsFavoriteAsync instead',
|
|
143
|
-
);
|
|
144
|
-
return this.removeBookmarkAsFavoriteAsync(id);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* @deprecated
|
|
149
|
-
* this will be removed as soon as applications have been migrated to use the bookmark provider
|
|
150
|
-
* @param id - The ID of the bookmark to get.
|
|
151
|
-
* @returns The bookmark, or null if it could not be found.
|
|
152
|
-
*/
|
|
153
|
-
getBookmarkById(id: string): Promise<Bookmark | null> {
|
|
154
|
-
console.warn('getBookmarkById is deprecated, use getBookmarkAsync instead');
|
|
155
|
-
return this.getBookmarkAsync(id);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Gets the semantic version of the bookmark provider.
|
|
160
|
-
* @returns The semantic version of the bookmark provider.
|
|
161
|
-
*/
|
|
162
|
-
public get version(): SemanticVersion {
|
|
163
|
-
return new SemanticVersion(version);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/** @returns The configured bookmark filters. */
|
|
167
|
-
public get filters(): BookmarkModuleConfig['filters'] {
|
|
168
|
-
// TODO(#5136) - freeze the config object?
|
|
169
|
-
return this.#config.filters;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Gets the `IBookmarkClient` instance used by this `BookmarkProvider`.
|
|
174
|
-
* @returns The `IBookmarkClient` instance.
|
|
175
|
-
*/
|
|
176
|
-
public get client(): IBookmarkClient {
|
|
177
|
-
return this.#config.client;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Gets the currently active bookmark (if any).
|
|
182
|
-
* @returns An observable of the currently active bookmark.
|
|
183
|
-
*/
|
|
184
|
-
public get currentBookmark$(): Observable<Bookmark | null | undefined> {
|
|
185
|
-
return this.#store.select(activeBookmarkSelector);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Gets an observable that emits the current list of bookmarks.
|
|
190
|
-
* @returns An observable of the current list of bookmarks.
|
|
191
|
-
*/
|
|
192
|
-
public get bookmarks$(): Observable<Array<BookmarkWithoutData>> {
|
|
193
|
-
this.getAllBookmarksAsync();
|
|
194
|
-
return this.#store.select(bookmarksSelector, deepEqual);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Gets the current list of bookmarks.
|
|
199
|
-
* @returns The current list of bookmarks.
|
|
200
|
-
*/
|
|
201
|
-
public get bookmarks(): Array<BookmarkWithoutData> {
|
|
202
|
-
return bookmarksSelector(this.#store.value);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Gets the currently active bookmark.
|
|
207
|
-
* @returns The currently active bookmark, or null/undefined if there is none.
|
|
208
|
-
*/
|
|
209
|
-
public get currentBookmark(): Bookmark | null | undefined {
|
|
210
|
-
const bookmark = activeBookmarkSelector(this.#store.value);
|
|
211
|
-
return bookmark;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Represents an observable stream of the bookmark status.
|
|
216
|
-
* @returns An observable of the bookmark status.
|
|
217
|
-
*/
|
|
218
|
-
public get status$(): Observable<BookmarkState['status']> {
|
|
219
|
-
return this.#store.select((x) => x.status);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Gets an observable that emits the current list of bookmark errors.
|
|
224
|
-
* @returns An observable of the current list of bookmark errors.
|
|
225
|
-
*/
|
|
226
|
-
public get errors$(): Observable<Array<BookmarkFlowError>> {
|
|
227
|
-
// TODO(#5137) - add deep diff
|
|
228
|
-
return this.#store.select(errorsSelector, deepEqual);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Determines whether there are any bookmark creators configured.
|
|
233
|
-
* @returns `true` if there are any bookmark creators configured, `false` otherwise.
|
|
234
|
-
*/
|
|
235
|
-
public get canCreateBookmarks(): boolean {
|
|
236
|
-
return this.#payloadGenerators.length > 0;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Gets the source system value from the configuration.
|
|
241
|
-
* @returns The source system value.
|
|
242
|
-
*/
|
|
243
|
-
public get sourceSystem(): BookmarkModuleConfig['sourceSystem'] {
|
|
244
|
-
return this.#config.sourceSystem;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Gets the resolved application from the bookmark module configuration.
|
|
249
|
-
* @returns The resolved application resolver.
|
|
250
|
-
*/
|
|
251
|
-
public get resolvedApplication(): BookmarkModuleConfig['resolve']['application'] {
|
|
252
|
-
return this.#config.resolve.application;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Gets the resolved context from the bookmark module configuration.
|
|
257
|
-
* @returns The resolved context resolver.
|
|
258
|
-
*/
|
|
259
|
-
public get resolvedContext(): BookmarkModuleConfig['resolve']['context'] {
|
|
260
|
-
return this.#config.resolve.context;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* @internal The API client used to communicate with the bookmark backend.
|
|
265
|
-
* @returns The configured `IBookmarkClient` instance.
|
|
266
|
-
*/
|
|
267
|
-
protected get _apiClient(): IBookmarkClient {
|
|
268
|
-
return this.#config.client;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* configured logger instance.
|
|
273
|
-
* @returns The configured logger.
|
|
274
|
-
*/
|
|
275
|
-
protected get _log(): BookmarkModuleConfig['log'] {
|
|
276
|
-
return this.#config.log;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* configured resolvers
|
|
281
|
-
* @returns The configured resolvers.
|
|
282
|
-
*/
|
|
283
|
-
protected get _resolve(): BookmarkModuleConfig['resolve'] {
|
|
284
|
-
return this.#config.resolve;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* configured event provider.
|
|
289
|
-
* @returns The configured event provider.
|
|
290
|
-
*/
|
|
291
|
-
protected get _event(): BookmarkModuleConfig['eventProvider'] {
|
|
292
|
-
return this.#config.eventProvider;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* configured parent bookmark provider
|
|
297
|
-
* @returns The configured parent bookmark provider.
|
|
298
|
-
*/
|
|
299
|
-
protected get _parent(): BookmarkModuleConfig['parent'] {
|
|
300
|
-
return this.#config.parent;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* Constructs a new `BookmarkProvider` instance.
|
|
305
|
-
*
|
|
306
|
-
* @param config - The configuration for the bookmark module, including the client, initial state, and optional parent provider.
|
|
307
|
-
*/
|
|
308
|
-
constructor(config: BookmarkModuleConfig) {
|
|
309
|
-
this.#config = config;
|
|
310
|
-
|
|
311
|
-
this.#store = createBookmarkStore({
|
|
312
|
-
client: config.client,
|
|
313
|
-
initial: { currentBookmark: this._parent?.currentBookmark ?? null },
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
// add teardown logic for disposing the store
|
|
317
|
-
this.#subscriptions.add(() => this.#store.complete());
|
|
318
|
-
|
|
319
|
-
// subscribe to the store actions and log them
|
|
320
|
-
this.#subscriptions.add(
|
|
321
|
-
this.#store.action$.subscribe((action) => {
|
|
322
|
-
// log failure actions as errors, everything else as debug
|
|
323
|
-
if (isFailureAction(action)) {
|
|
324
|
-
this._log?.error(`Action: ${action.type}`, action);
|
|
325
|
-
} else {
|
|
326
|
-
this._log?.debug(`Action: ${action.type}`, action);
|
|
327
|
-
}
|
|
328
|
-
}),
|
|
329
|
-
);
|
|
330
|
-
|
|
331
|
-
// subscribe to the current bookmark changes and dispatch events
|
|
332
|
-
this.#subscriptions.add(
|
|
333
|
-
this.#store.select(activeBookmarkSelector).subscribe((bookmark) => {
|
|
334
|
-
// do not emit before the first value
|
|
335
|
-
if (bookmark !== undefined) {
|
|
336
|
-
this._dispatchEvent('onCurrentBookmarkChanged', { detail: bookmark });
|
|
337
|
-
}
|
|
338
|
-
}),
|
|
339
|
-
);
|
|
340
|
-
|
|
341
|
-
// subscribe to child bookmark provider changes
|
|
342
|
-
// TODO(#5138) - add support for disabling this feature
|
|
343
|
-
if (this._event) {
|
|
344
|
-
this._event.addEventListener('onCurrentBookmarkChanged', (event) => {
|
|
345
|
-
const { source, detail } = event;
|
|
346
|
-
|
|
347
|
-
// ignore events without detail
|
|
348
|
-
if (detail === undefined) return;
|
|
349
|
-
|
|
350
|
-
// only update the current bookmark if the event source is not this provider
|
|
351
|
-
if (source !== this && detail !== this.currentBookmark) {
|
|
352
|
-
this.#store.next(bookmarkActions.setCurrentBookmark(detail));
|
|
353
|
-
}
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// if a parent bookmark provider is configured, subscribe to its current bookmark changes
|
|
358
|
-
if (this._parent) {
|
|
359
|
-
try {
|
|
360
|
-
this.#subscriptions.add(
|
|
361
|
-
this._parent.currentBookmark$
|
|
362
|
-
// only forward defined bookmarks that differ from the current one
|
|
363
|
-
.pipe(
|
|
364
|
-
filter((x): x is Bookmark => x !== undefined),
|
|
365
|
-
filter((x) => x !== this.currentBookmark),
|
|
366
|
-
)
|
|
367
|
-
.subscribe((bookmark) => {
|
|
368
|
-
this.#store.next(bookmarkActions.setCurrentBookmark(bookmark));
|
|
369
|
-
}),
|
|
370
|
-
);
|
|
371
|
-
} catch (e) {
|
|
372
|
-
this._log?.error('Failed to subscribe to parent bookmark provider', e);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
* Registers an event listener for the specified event on the BookmarkProvider.
|
|
379
|
-
*
|
|
380
|
-
* @template TType - The key of the `BookmarkProviderEventMap` identifying the event.
|
|
381
|
-
* @param eventName - The name of the event to listen for. Must be a key of the `BookmarkProviderEventMap` type.
|
|
382
|
-
* @param callback - The callback function to be invoked when the event is triggered.
|
|
383
|
-
* The callback will receive the event object as its parameter, which will be of the type corresponding to the `eventName`.
|
|
384
|
-
* @returns A function that can be called to remove the event listener.
|
|
385
|
-
*/
|
|
386
|
-
public on<TType extends keyof BookmarkProviderEventMap>(
|
|
387
|
-
eventName: TType,
|
|
388
|
-
callback: (event: BookmarkProviderEventMap[TType]) => void,
|
|
389
|
-
): VoidFunction {
|
|
390
|
-
// without an event provider, registering a listener would be a no-op
|
|
391
|
-
if (!this._event) {
|
|
392
|
-
this._log?.warn('Failed to register event listener, event provider not configured');
|
|
393
|
-
return () => {
|
|
394
|
-
// No-op function when event provider is not configured
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
return this._event.addEventListener(eventName, (event) => {
|
|
398
|
-
// only forward events originating from this provider instance
|
|
399
|
-
if (event.source === this) {
|
|
400
|
-
callback(event);
|
|
401
|
-
}
|
|
402
|
-
});
|
|
403
|
-
}
|
|
404
|
-
/**
|
|
405
|
-
* Adds a new payload generator function to the BookmarkProvider.
|
|
406
|
-
* The payload generator function will be used to generate the payload for a bookmark-related action.
|
|
407
|
-
*
|
|
408
|
-
* @template T - The type of the bookmark data the payload generator produces.
|
|
409
|
-
* @param fn - The payload generator function to add. It should be of type `PayloadGenerator<T>`, where `T` is the type of the bookmark data.
|
|
410
|
-
* @returns A function that can be called to remove the added payload generator.
|
|
411
|
-
*/
|
|
412
|
-
public addPayloadGenerator<T extends BookmarkData>(
|
|
413
|
-
fn: BookmarkPayloadGenerator<T>,
|
|
414
|
-
): VoidFunction {
|
|
415
|
-
this._log?.debug(`adding bookmark payload generator: ${fn.name}`);
|
|
416
|
-
|
|
417
|
-
// register the payload generator function
|
|
418
|
-
this.#payloadGenerators.push(fn as BookmarkPayloadGenerator);
|
|
419
|
-
|
|
420
|
-
// dispatch event to notify listeners of the added payload generator
|
|
421
|
-
this._dispatchEvent('onBookmarkPayloadCreatorAdded', {
|
|
422
|
-
detail: fn as BookmarkPayloadGenerator,
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
// return a function to remove the added payload generator
|
|
426
|
-
return () => {
|
|
427
|
-
this._log?.debug(`removing bookmark payload generator: ${fn.name}`);
|
|
428
|
-
// drop the matching generator reference from the registered list
|
|
429
|
-
this.#payloadGenerators = this.#payloadGenerators.filter((g) => g !== fn);
|
|
430
|
-
};
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
/**
|
|
434
|
-
* Generates a payload by applying registered generator functions to an accumulator object.
|
|
435
|
-
*
|
|
436
|
-
* @param initial - The initial value of the accumulator object.
|
|
437
|
-
* @returns An observable that emits the generated payload.
|
|
438
|
-
* @template T - The type of the generated payload.
|
|
439
|
-
*/
|
|
440
|
-
public generatePayload<T extends BookmarkData = BookmarkData>(
|
|
441
|
-
initial?: Partial<T> | null,
|
|
442
|
-
): Observable<T | null | undefined> {
|
|
443
|
-
this._log?.debug('generating payload', initial);
|
|
444
|
-
|
|
445
|
-
/**
|
|
446
|
-
* Observable that emits the generated payload.
|
|
447
|
-
*/
|
|
448
|
-
const result$ = from(this.#payloadGenerators).pipe(
|
|
449
|
-
mergeScan(
|
|
450
|
-
(acc, generator) => from(Promise.resolve(this._producePayload(acc, generator))),
|
|
451
|
-
initial ?? ({} as Partial<T>),
|
|
452
|
-
1,
|
|
453
|
-
),
|
|
454
|
-
tap((payload) => this._log?.debug('generated payload', { initial, payload })),
|
|
455
|
-
) as Observable<T | null | undefined>;
|
|
456
|
-
|
|
457
|
-
return result$;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
/**
|
|
461
|
-
* Produces a payload using the provided generator function.
|
|
462
|
-
*
|
|
463
|
-
* @template T - The type of the bookmark data.
|
|
464
|
-
* @param value - The partial value to be used as the base for the payload.
|
|
465
|
-
* @param generator - The function that generates the payload.
|
|
466
|
-
* @param initial - An optional initial value to be passed to the generator.
|
|
467
|
-
* @returns The produced payload or null if the generator wishes to clear the bookmark data.
|
|
468
|
-
*
|
|
469
|
-
* @remarks
|
|
470
|
-
* The generator function should not return a value, as the data is an immer draft object.
|
|
471
|
-
* If the generator returns a value, a warning will be logged.
|
|
472
|
-
* If the generator returns null, an info message will be logged indicating that the bookmark data should be cleared.
|
|
473
|
-
*/
|
|
474
|
-
protected async _producePayload<T extends BookmarkData = BookmarkData>(
|
|
475
|
-
value: Partial<T>,
|
|
476
|
-
generator: BookmarkPayloadGenerator<T>,
|
|
477
|
-
initial?: Partial<T> | null,
|
|
478
|
-
): Promise<T | null | undefined> {
|
|
479
|
-
// Create a draft for async operations using createDraft/finishDraft pattern
|
|
480
|
-
const draft = createDraft(value);
|
|
481
|
-
|
|
482
|
-
try {
|
|
483
|
-
const generatorResult = await Promise.resolve(generator(draft as Partial<T>, initial));
|
|
484
|
-
|
|
485
|
-
// Handle the generator result
|
|
486
|
-
if (generatorResult) {
|
|
487
|
-
this._log?.warn(
|
|
488
|
-
`bookmark data generator ${generator.name} returned a value, but it should not do this, since the data is an immer draft object`,
|
|
489
|
-
);
|
|
490
|
-
// Dirty fix since some developers are returning the reference object, which will freeze the object
|
|
491
|
-
return castDraft(generatorResult) as T | null | undefined;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
// clear the bookmark data if the generator returns null
|
|
495
|
-
if (generatorResult === null) {
|
|
496
|
-
this._log?.info(
|
|
497
|
-
`bookmark data generator ${generator.name} wish to clear the bookmark data`,
|
|
498
|
-
);
|
|
499
|
-
return undefined;
|
|
500
|
-
}
|
|
501
|
-
} catch (error) {
|
|
502
|
-
this._log?.error(`Failed to produce payload using generator ${generator.name}`, error);
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
// Finish the draft to get the immutable result
|
|
506
|
-
const result = finishDraft(draft);
|
|
507
|
-
|
|
508
|
-
return result as T | null | undefined;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
/**
|
|
512
|
-
* Retrieves a bookmark with the specified bookmarkId and returns an observable that emits the combined result of fetching the bookmark and bookmark data.
|
|
513
|
-
* @template T The type of the bookmark payload.
|
|
514
|
-
* @param bookmarkId The unique identifier of the bookmark.
|
|
515
|
-
* @param options An optional object that allows excluding the bookmark payload from the result.
|
|
516
|
-
* @returns An observable that emits the combined result of fetching the bookmark and bookmark data.
|
|
517
|
-
* @throws The error encountered while fetching the bookmark or its data, after logging it.
|
|
518
|
-
*/
|
|
519
|
-
public getBookmark<T extends BookmarkData = BookmarkData>(
|
|
520
|
-
bookmarkId: string,
|
|
521
|
-
options?: { excludePayload?: boolean },
|
|
522
|
-
): Observable<Bookmark<T>> {
|
|
523
|
-
this._log?.debug(`fetching bookmark: ${bookmarkId}`, options);
|
|
524
|
-
|
|
525
|
-
// fetch the bookmark and bookmark data
|
|
526
|
-
return forkJoin({
|
|
527
|
-
bookmark: this._getBookmarkInfo(bookmarkId),
|
|
528
|
-
payload: options?.excludePayload ? of({}) : this._getBookmarkData(bookmarkId),
|
|
529
|
-
}).pipe(
|
|
530
|
-
catchError((error) => {
|
|
531
|
-
this._log?.error(`Failed to fetch bookmark: ${bookmarkId}`, error);
|
|
532
|
-
throw error;
|
|
533
|
-
}),
|
|
534
|
-
// combine the bookmark and payload into a single object
|
|
535
|
-
map(({ bookmark, payload }) => Object.assign({}, bookmark, { payload }) as Bookmark<T>),
|
|
536
|
-
tap((bookmark) => {
|
|
537
|
-
this._log?.debug(`Bookmark fetched: ${bookmark.id}`, bookmark);
|
|
538
|
-
}),
|
|
539
|
-
);
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
/**
|
|
543
|
-
* Retrieves a bookmark asynchronously.
|
|
544
|
-
*
|
|
545
|
-
* @template T - The type of the bookmark payload.
|
|
546
|
-
* @param id - The ID of the bookmark to retrieve.
|
|
547
|
-
* @param options - Optional parameters for the retrieval.
|
|
548
|
-
* @param options.excludePayload - Specifies whether to exclude the payload from the bookmark.
|
|
549
|
-
* @returns A promise that resolves to the retrieved bookmark, or null if the bookmark is not found.
|
|
550
|
-
*/
|
|
551
|
-
public getBookmarkAsync<T extends BookmarkData = BookmarkData>(
|
|
552
|
-
id: string,
|
|
553
|
-
options?: { excludePayload?: boolean },
|
|
554
|
-
): Promise<Bookmark<T> | null> {
|
|
555
|
-
return lastValueFrom(this.getBookmark<T>(id, options));
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
/**
|
|
559
|
-
* Retrieves all bookmarks from the store as an Observable stream.
|
|
560
|
-
*
|
|
561
|
-
* This method implements a complex flow that:
|
|
562
|
-
* 1. Resolves filter parameters (sourceSystem, appKey, contextId) based on configuration
|
|
563
|
-
* 2. Dispatches a fetch action to the store with resolved filters
|
|
564
|
-
* 3. Monitors store actions for success/failure responses
|
|
565
|
-
* 4. Returns the bookmarks data or throws appropriate errors
|
|
566
|
-
*
|
|
567
|
-
* @remarks
|
|
568
|
-
* The method uses a reactive pattern where filter resolution and store actions are
|
|
569
|
-
* handled asynchronously. If filtering is enabled, it will resolve the current
|
|
570
|
-
* application and context to filter bookmarks accordingly.
|
|
571
|
-
*
|
|
572
|
-
* @example
|
|
573
|
-
* ```ts
|
|
574
|
-
* // Basic usage
|
|
575
|
-
* bookmarkProvider.getAllBookmarks().subscribe({
|
|
576
|
-
* next: (bookmarks) => console.log('Retrieved bookmarks:', bookmarks),
|
|
577
|
-
* error: (err) => console.error('Failed to fetch bookmarks:', err)
|
|
578
|
-
* });
|
|
579
|
-
*
|
|
580
|
-
* // With filtering enabled in config
|
|
581
|
-
* const config = {
|
|
582
|
-
* filters: { context: true, application: true }
|
|
583
|
-
* };
|
|
584
|
-
* // Will automatically filter by current context and application
|
|
585
|
-
* ```
|
|
586
|
-
*
|
|
587
|
-
* @returns An Observable that emits the array of bookmarks when successfully retrieved
|
|
588
|
-
* @throws {BookmarkProviderError} When filter resolution fails or store operations fail
|
|
589
|
-
* @throws {BookmarkProviderError} When a timeout occurs during the fetch operation
|
|
590
|
-
*/
|
|
591
|
-
public getAllBookmarks(): Observable<Bookmarks> {
|
|
592
|
-
return new Observable<Bookmarks>((observer) => {
|
|
593
|
-
this._log?.debug('fetching all bookmarks');
|
|
594
|
-
|
|
595
|
-
// Step 1: Generate filter parameters based on configuration
|
|
596
|
-
// This creates a stream that resolves all necessary filter values
|
|
597
|
-
const filter$ = forkJoin({
|
|
598
|
-
// Always include the source system for the current provider
|
|
599
|
-
sourceSystem: of(this.sourceSystem),
|
|
600
|
-
|
|
601
|
-
// Resolve application key if filtering by application is enabled
|
|
602
|
-
appKey: this.#config.filters?.application
|
|
603
|
-
? defer(() => this._resolve.application()).pipe(map((x) => x?.appKey))
|
|
604
|
-
: of(undefined),
|
|
605
|
-
|
|
606
|
-
// Resolve context ID if filtering by context is enabled
|
|
607
|
-
contextId: this.#config.filters?.context
|
|
608
|
-
? defer(() => this._resolve.context()).pipe(map((x) => x?.id))
|
|
609
|
-
: of(undefined),
|
|
610
|
-
}).pipe(
|
|
611
|
-
// Handle any errors during filter parameter resolution
|
|
612
|
-
catchError((err) => {
|
|
613
|
-
const error = new BookmarkProviderError(
|
|
614
|
-
'Could not fetch bookmarks, failed to resolve filter parameters',
|
|
615
|
-
err,
|
|
616
|
-
);
|
|
617
|
-
this._log?.error(error.message, error);
|
|
618
|
-
throw error;
|
|
619
|
-
}),
|
|
620
|
-
);
|
|
621
|
-
|
|
622
|
-
// Step 2: Dispatch fetch action to store when filter parameters are ready
|
|
623
|
-
// This triggers the actual API call through the store's action system
|
|
624
|
-
// The store will handle the async operation and emit success/failure actions
|
|
625
|
-
// Using observer.add() ensures proper cleanup when the Observable is unsubscribed
|
|
626
|
-
observer.add(
|
|
627
|
-
filter$.subscribe((filter) => {
|
|
628
|
-
this.#store.next(bookmarkActions.fetchBookmarks(filter));
|
|
629
|
-
}),
|
|
630
|
-
);
|
|
631
|
-
|
|
632
|
-
// Step 3: Monitor for failure responses from the store
|
|
633
|
-
// This stream will emit and throw an error if the fetch operation fails
|
|
634
|
-
const failure$ = this.#store.action$.pipe(
|
|
635
|
-
filter(bookmarkActions.fetchBookmarks.failure.match),
|
|
636
|
-
map((action) => {
|
|
637
|
-
this._log?.error('Failed to fetch bookmarks', action.payload);
|
|
638
|
-
throw new BookmarkProviderError('Failed to fetch bookmarks', action.payload);
|
|
639
|
-
}),
|
|
640
|
-
// Add timeout protection to prevent hanging requests
|
|
641
|
-
timeout({
|
|
642
|
-
each: defaultTimeout,
|
|
643
|
-
with: () => {
|
|
644
|
-
throw new BookmarkProviderError('Timeout while fetching bookmarks');
|
|
645
|
-
},
|
|
646
|
-
}),
|
|
647
|
-
);
|
|
648
|
-
|
|
649
|
-
// Step 4: Monitor for success responses from the store
|
|
650
|
-
// This stream will emit the bookmarks data when successfully retrieved
|
|
651
|
-
const request$ = this.#store.action$.pipe(
|
|
652
|
-
filter(bookmarkActions.fetchBookmarks.success.match),
|
|
653
|
-
map((a) => a.payload),
|
|
654
|
-
tap((bookmarks) => {
|
|
655
|
-
this._log?.debug('fetched all bookmarks', bookmarks);
|
|
656
|
-
}),
|
|
657
|
-
// Race against failure stream - whichever completes first wins
|
|
658
|
-
// This ensures we either get the bookmarks data or an error, not both
|
|
659
|
-
raceWith(failure$),
|
|
660
|
-
// Only take the first emission (success or failure) to complete the stream
|
|
661
|
-
first(),
|
|
662
|
-
);
|
|
663
|
-
|
|
664
|
-
// Step 5: Subscribe to the request stream and forward results to observer
|
|
665
|
-
request$.subscribe(observer);
|
|
666
|
-
});
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
/**
|
|
670
|
-
* Asynchronously retrieves all bookmarks from the store.
|
|
671
|
-
*
|
|
672
|
-
* @returns A Promise that resolves to the Bookmarks object containing all bookmarks.
|
|
673
|
-
*/
|
|
674
|
-
public async getAllBookmarksAsync(): Promise<Bookmarks> {
|
|
675
|
-
return lastValueFrom(this.getAllBookmarks());
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
/**
|
|
679
|
-
* Sets the current bookmark to the specified bookmark or ID.
|
|
680
|
-
* If the bookmark is already the current bookmark, it returns the bookmark directly.
|
|
681
|
-
* Otherwise, it emits the next bookmark or null through an observable.
|
|
682
|
-
*
|
|
683
|
-
* By proving `null` as the bookmark_or_id, the current bookmark will be cleared.
|
|
684
|
-
*
|
|
685
|
-
* @param bookmark_or_id - The bookmark or ID to set as the current bookmark.
|
|
686
|
-
* @returns An observable that emits the next bookmark or null.
|
|
687
|
-
* @template T - The type of the bookmark data.
|
|
688
|
-
* @throws {BookmarkProviderError} If the onCurrentBookmarkChange event is canceled by a listener, or resolving the bookmark fails.
|
|
689
|
-
*/
|
|
690
|
-
public setCurrentBookmark<T extends BookmarkData = BookmarkData>(
|
|
691
|
-
bookmark_or_id: Bookmark<T> | string | null,
|
|
692
|
-
): Observable<Bookmark<T> | null> {
|
|
693
|
-
this._log?.debug('setting current bookmark', bookmark_or_id);
|
|
694
|
-
|
|
695
|
-
// resolve the next bookmark
|
|
696
|
-
const next$: Observable<Bookmark<T> | null> =
|
|
697
|
-
typeof bookmark_or_id === 'string'
|
|
698
|
-
? this.getBookmark<T>(bookmark_or_id)
|
|
699
|
-
: of(bookmark_or_id ?? null);
|
|
700
|
-
|
|
701
|
-
// notify listeners before committing the next bookmark to the store
|
|
702
|
-
return next$.pipe(
|
|
703
|
-
switchMap(async (next) => {
|
|
704
|
-
const current = this.currentBookmark;
|
|
705
|
-
const { type, canceled } = await this._dispatchEvent('onCurrentBookmarkChange', {
|
|
706
|
-
detail: { current, next },
|
|
707
|
-
});
|
|
708
|
-
// throw an error if the event is canceled
|
|
709
|
-
if (canceled) {
|
|
710
|
-
const error = new BookmarkProviderError(
|
|
711
|
-
`event: ${type} was canceled by listener for change to ${next?.id ?? 'none'}, from ${current?.id ?? 'none'}`,
|
|
712
|
-
);
|
|
713
|
-
this._log?.info(error.message);
|
|
714
|
-
throw error;
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
return next;
|
|
718
|
-
}),
|
|
719
|
-
tap((next) => {
|
|
720
|
-
// set the current bookmark in the store
|
|
721
|
-
this.#store.next(bookmarkActions.setCurrentBookmark(next));
|
|
722
|
-
}),
|
|
723
|
-
catchError((err) => {
|
|
724
|
-
const error = new BookmarkProviderError(
|
|
725
|
-
'Could not set current bookmark, resolve of bookmark failed',
|
|
726
|
-
err,
|
|
727
|
-
);
|
|
728
|
-
this._log?.error(error.message, error);
|
|
729
|
-
throw error;
|
|
730
|
-
}),
|
|
731
|
-
);
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
/**
|
|
735
|
-
* Sets the current bookmark asynchronously.
|
|
736
|
-
*
|
|
737
|
-
* @template T - The type of the bookmark data.
|
|
738
|
-
* @param bookmark_or_id - The bookmark or ID to set as the current bookmark.
|
|
739
|
-
* @returns A promise that resolves to the next bookmark or null.
|
|
740
|
-
*/
|
|
741
|
-
public setCurrentBookmarkAsync<T extends BookmarkData = BookmarkData>(
|
|
742
|
-
bookmark_or_id: Bookmark<T> | string | null,
|
|
743
|
-
): Promise<Bookmark<T> | null> {
|
|
744
|
-
return lastValueFrom(this.setCurrentBookmark<T>(bookmark_or_id));
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
/**
|
|
748
|
-
* Creates a new bookmark with the provided bookmark data.
|
|
749
|
-
* The creation executes immediately when this method is called.
|
|
750
|
-
* @template T - The type of bookmark data.
|
|
751
|
-
* @param {BookmarkCreateArgs<T>} newBookmarkData - The data for creating the bookmark.
|
|
752
|
-
* @returns {Observable<Bookmark<T>>} - An observable that emits the created bookmark.
|
|
753
|
-
* @throws {BookmarkProviderError} If resolving the bookmark data fails, the onBookmarkCreate event is canceled, the create request fails, or the request times out.
|
|
754
|
-
*/
|
|
755
|
-
public createBookmark<T extends BookmarkData = BookmarkData>(
|
|
756
|
-
newBookmarkData: BookmarkCreateArgs<T>,
|
|
757
|
-
): Observable<Bookmark<T>> {
|
|
758
|
-
const { ref, action$ } = this._useScopedActions();
|
|
759
|
-
|
|
760
|
-
this._log?.debug(`creating new bookmark, ref: ${ref}`, newBookmarkData);
|
|
761
|
-
|
|
762
|
-
// resolve the bookmark
|
|
763
|
-
const bookmark$ = forkJoin({
|
|
764
|
-
appKey: newBookmarkData.appKey
|
|
765
|
-
? of(newBookmarkData.appKey)
|
|
766
|
-
: defer(() => this._resolve.application()).pipe(
|
|
767
|
-
map((app) => {
|
|
768
|
-
// the application must resolve to a valid appKey to attribute the bookmark
|
|
769
|
-
if (!app?.appKey) {
|
|
770
|
-
throw new BookmarkProviderError('Failed to resolve application key');
|
|
771
|
-
}
|
|
772
|
-
return app.appKey;
|
|
773
|
-
}),
|
|
774
|
-
),
|
|
775
|
-
contextId: defer(() => this._resolve.context()).pipe(map((context) => context?.id)),
|
|
776
|
-
payload: this.generatePayload<T>(newBookmarkData.payload),
|
|
777
|
-
sourceSystem: of(this.sourceSystem),
|
|
778
|
-
}).pipe(
|
|
779
|
-
catchError((err) => {
|
|
780
|
-
const error = new BookmarkProviderError(
|
|
781
|
-
'Could not create new bookmark, failed to resolve bookmark data',
|
|
782
|
-
err,
|
|
783
|
-
);
|
|
784
|
-
this._log?.error(error.message, error);
|
|
785
|
-
throw error;
|
|
786
|
-
}),
|
|
787
|
-
// merge the resolved data with the new bookmark data
|
|
788
|
-
map(
|
|
789
|
-
(resolvedData) =>
|
|
790
|
-
({
|
|
791
|
-
...newBookmarkData,
|
|
792
|
-
...resolvedData,
|
|
793
|
-
}) as BookmarkNew<T>,
|
|
794
|
-
),
|
|
795
|
-
);
|
|
796
|
-
|
|
797
|
-
// notify listeners that a bookmark is about to be created
|
|
798
|
-
const dispatch$ = bookmark$.pipe(
|
|
799
|
-
switchMap(async (bookmark) => {
|
|
800
|
-
// notify listeners that a bookmark is about to be created
|
|
801
|
-
const { canceled, type } = await this._dispatchEvent('onBookmarkCreate', {
|
|
802
|
-
detail: bookmark,
|
|
803
|
-
cancelable: true,
|
|
804
|
-
});
|
|
805
|
-
|
|
806
|
-
// throw an error if the event is canceled
|
|
807
|
-
if (canceled) {
|
|
808
|
-
const error = new BookmarkProviderError(
|
|
809
|
-
`event: ${type} was canceled by listener for creating bookmark: ${ref}`,
|
|
810
|
-
);
|
|
811
|
-
this._log?.info(error.message);
|
|
812
|
-
throw error;
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
return bookmark;
|
|
816
|
-
}),
|
|
817
|
-
);
|
|
818
|
-
|
|
819
|
-
// Start the creation process immediately
|
|
820
|
-
const createSubscription = dispatch$.subscribe({
|
|
821
|
-
error: (error) => {
|
|
822
|
-
this._log?.error(`Failed to prepare bookmark creation: ${ref}`, error);
|
|
823
|
-
},
|
|
824
|
-
next: (newBookmark) => {
|
|
825
|
-
// request the store to create the bookmark
|
|
826
|
-
this.#store.next(bookmarkActions.createBookmark(newBookmark, { ref }));
|
|
827
|
-
},
|
|
828
|
-
});
|
|
829
|
-
|
|
830
|
-
// monitor the failure case when creating a bookmark
|
|
831
|
-
const failure$ = action$.pipe(
|
|
832
|
-
filter(bookmarkActions.createBookmark.failure.match),
|
|
833
|
-
map(({ payload: cause }) => {
|
|
834
|
-
const error = new BookmarkProviderError(`Failed to create bookmark: ${ref}`, {
|
|
835
|
-
cause,
|
|
836
|
-
});
|
|
837
|
-
this._log?.warn(error.message);
|
|
838
|
-
throw error;
|
|
839
|
-
}),
|
|
840
|
-
timeout({
|
|
841
|
-
each: defaultTimeout,
|
|
842
|
-
with: () => {
|
|
843
|
-
throw new BookmarkProviderError(`Timeout while creating bookmark: ${ref}`);
|
|
844
|
-
},
|
|
845
|
-
}),
|
|
846
|
-
);
|
|
847
|
-
|
|
848
|
-
// monitor the success case when creating a bookmark
|
|
849
|
-
const request$ = action$.pipe(
|
|
850
|
-
filter(bookmarkActions.createBookmark.success.match),
|
|
851
|
-
map(({ payload }) => payload as Bookmark<T>),
|
|
852
|
-
tap((bookmark) => {
|
|
853
|
-
this._log?.info(`Bookmark created: ${bookmark.id}, ref: ${ref}`);
|
|
854
|
-
this._dispatchEvent('onBookmarkCreated', { detail: bookmark });
|
|
855
|
-
}),
|
|
856
|
-
raceWith(failure$),
|
|
857
|
-
first(),
|
|
858
|
-
);
|
|
859
|
-
|
|
860
|
-
// Return the observable that will emit the result
|
|
861
|
-
return new Observable<Bookmark<T>>((subscriber) => {
|
|
862
|
-
// Clean up the create subscription when the result observable is unsubscribed
|
|
863
|
-
subscriber.add(() => createSubscription.unsubscribe());
|
|
864
|
-
|
|
865
|
-
// Emit the created bookmark
|
|
866
|
-
request$.subscribe(subscriber);
|
|
867
|
-
});
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
/**
|
|
871
|
-
* Asynchronously creates a new bookmark.
|
|
872
|
-
*
|
|
873
|
-
* @template T - The type of bookmark data.
|
|
874
|
-
* @param args - The data for creating the bookmark.
|
|
875
|
-
* @returns A promise that resolves to the created bookmark with its associated data.
|
|
876
|
-
*/
|
|
877
|
-
public createBookmarkAsync<T extends BookmarkData = BookmarkData>(
|
|
878
|
-
args: BookmarkCreateArgs<T>,
|
|
879
|
-
): Promise<Bookmark<T>> {
|
|
880
|
-
return lastValueFrom(this.createBookmark<T>(args));
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
/**
|
|
884
|
-
* Updates a bookmark with the specified bookmarkId and bookmarkUpdates.
|
|
885
|
-
* The update executes immediately when this method is called.
|
|
886
|
-
*
|
|
887
|
-
* @template T - The type of the bookmark data.
|
|
888
|
-
* @param {string} bookmarkId - The identifier of the bookmark to update.
|
|
889
|
-
* @param {BookmarkUpdate<T>} [bookmarkUpdates] - The updates to apply to the bookmark.
|
|
890
|
-
* @param {BookmarkUpdateOptions} [options] - The options for updating the bookmark.
|
|
891
|
-
* @returns {Observable<Bookmark<T>>} - An observable that emits the updated bookmark.
|
|
892
|
-
* @throws {BookmarkProviderError} If bookmarkUpdates is omitted while excludePayloadGeneration is set, generating the payload fails, the onBookmarkUpdate event is canceled, the update request fails, or the request times out.
|
|
893
|
-
*/
|
|
894
|
-
public updateBookmark<T extends BookmarkData = BookmarkData>(
|
|
895
|
-
bookmarkId: string,
|
|
896
|
-
bookmarkUpdates?: BookmarkUpdate<T>,
|
|
897
|
-
options?: BookmarkUpdateOptions,
|
|
898
|
-
): Observable<Bookmark<T>> {
|
|
899
|
-
// bookmarkUpdates is required unless the caller opts out of payload generation entirely
|
|
900
|
-
if (!bookmarkUpdates && options?.excludePayloadGeneration) {
|
|
901
|
-
throw new BookmarkProviderError(
|
|
902
|
-
'Cannot update bookmark without updates and excludePayloadGeneration option',
|
|
903
|
-
);
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
const { ref, action$ } = this._useScopedActions();
|
|
907
|
-
|
|
908
|
-
this._log?.debug(`Updating bookmark: ${bookmarkId}, ref: ${ref}`);
|
|
909
|
-
|
|
910
|
-
/**
|
|
911
|
-
* Generate updates with payload
|
|
912
|
-
* @remarks
|
|
913
|
-
* If `excludePayloadGeneration` is `true`, it emits the `bookmarkUpdates` directly.
|
|
914
|
-
* If `excludePayloadGeneration` is `false`, it generates the payload using the `generatePayload` method and emits the updated `bookmarkUpdates` with the generated payload.
|
|
915
|
-
*/
|
|
916
|
-
const updates$ = options?.excludePayloadGeneration
|
|
917
|
-
? of(bookmarkUpdates as BookmarkUpdate<T>)
|
|
918
|
-
: this.generatePayload<T>(bookmarkUpdates?.payload).pipe(
|
|
919
|
-
map(
|
|
920
|
-
(payload) =>
|
|
921
|
-
({
|
|
922
|
-
...bookmarkUpdates,
|
|
923
|
-
payload,
|
|
924
|
-
}) as BookmarkUpdate<T>,
|
|
925
|
-
),
|
|
926
|
-
);
|
|
927
|
-
|
|
928
|
-
// notify listeners that a bookmark is about to be updated
|
|
929
|
-
const dispatch$ = updates$.pipe(
|
|
930
|
-
switchMap(async (updates) => {
|
|
931
|
-
const { canceled, type } = await this._dispatchEvent('onBookmarkUpdate', {
|
|
932
|
-
detail: {
|
|
933
|
-
current: bookmarkSelector(this.#store.value, bookmarkId),
|
|
934
|
-
updates,
|
|
935
|
-
},
|
|
936
|
-
cancelable: true,
|
|
937
|
-
});
|
|
938
|
-
|
|
939
|
-
// throw an error if the event is canceled
|
|
940
|
-
if (canceled) {
|
|
941
|
-
const error = new BookmarkProviderError(
|
|
942
|
-
`event: ${type} was canceled by listener for updating bookmark: ${bookmarkId}, ref: ${ref}`,
|
|
943
|
-
);
|
|
944
|
-
this._log?.warn(error.message, updates);
|
|
945
|
-
throw error;
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
return updates;
|
|
949
|
-
}),
|
|
950
|
-
);
|
|
951
|
-
|
|
952
|
-
// Start the update process immediately
|
|
953
|
-
const updateSubscription = dispatch$.subscribe({
|
|
954
|
-
error: (error) => {
|
|
955
|
-
this._log?.error(`Failed to prepare bookmark update: ${bookmarkId}`, error);
|
|
956
|
-
},
|
|
957
|
-
next: (updates) => {
|
|
958
|
-
// trigger the store to update the bookmark
|
|
959
|
-
this.#store.next(bookmarkActions.updateBookmark({ bookmarkId, updates }, { ref }));
|
|
960
|
-
},
|
|
961
|
-
});
|
|
962
|
-
|
|
963
|
-
// monitor the failure case when updating a bookmark
|
|
964
|
-
const failure$ = action$.pipe(
|
|
965
|
-
filter(bookmarkActions.updateBookmark.failure.match),
|
|
966
|
-
map(({ payload: cause }) => {
|
|
967
|
-
const error = new BookmarkProviderError(`Failed to update bookmark: ${bookmarkId}`, {
|
|
968
|
-
cause,
|
|
969
|
-
});
|
|
970
|
-
this._log?.info(error.message);
|
|
971
|
-
throw error;
|
|
972
|
-
}),
|
|
973
|
-
timeout({
|
|
974
|
-
each: defaultTimeout,
|
|
975
|
-
with: () => {
|
|
976
|
-
throw new BookmarkProviderError(`Timeout while updating bookmark: ${bookmarkId}`);
|
|
977
|
-
},
|
|
978
|
-
}),
|
|
979
|
-
);
|
|
980
|
-
|
|
981
|
-
// monitor the success case when updating a bookmark
|
|
982
|
-
const request$ = action$.pipe(
|
|
983
|
-
filter(bookmarkActions.updateBookmark.success.match),
|
|
984
|
-
map(
|
|
985
|
-
// TODO(#5139): add payload if current bookmark is the same as the updated bookmark
|
|
986
|
-
({ payload }): Bookmark<T> =>
|
|
987
|
-
({
|
|
988
|
-
...bookmarkSelector(this.#store.value, payload.id),
|
|
989
|
-
payload: payload.payload,
|
|
990
|
-
}) as Bookmark<T>,
|
|
991
|
-
),
|
|
992
|
-
tap((bookmark) => {
|
|
993
|
-
this._log?.info(`Bookmark updated: ${bookmark.id}, ref: ${ref}`);
|
|
994
|
-
this._dispatchEvent('onBookmarkUpdated', { detail: bookmark });
|
|
995
|
-
}),
|
|
996
|
-
raceWith(failure$),
|
|
997
|
-
first(),
|
|
998
|
-
);
|
|
999
|
-
|
|
1000
|
-
// Return the observable that will emit the result
|
|
1001
|
-
return new Observable<Bookmark<T>>((subscriber) => {
|
|
1002
|
-
// Clean up the update subscription when the result observable is unsubscribed
|
|
1003
|
-
subscriber.add(() => updateSubscription.unsubscribe());
|
|
1004
|
-
|
|
1005
|
-
// Emit the updated bookmark
|
|
1006
|
-
request$.subscribe(subscriber);
|
|
1007
|
-
});
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
/**
|
|
1011
|
-
* Updates a bookmark asynchronously.
|
|
1012
|
-
*
|
|
1013
|
-
* @todo TODO(#5140) - remove the deprecated overload accepting a full bookmark in the next major version
|
|
1014
|
-
*
|
|
1015
|
-
* @template T - The type of the bookmark data.
|
|
1016
|
-
* @param id_or_bookmark - The ID of the bookmark to update, or (deprecated) the full bookmark object.
|
|
1017
|
-
* @param updates_or_options - The updates to apply, or (deprecated) the update options when using the bookmark overload.
|
|
1018
|
-
* @param options - The options for updating the bookmark.
|
|
1019
|
-
* @returns A promise that resolves to the updated bookmark with its associated data.
|
|
1020
|
-
*/
|
|
1021
|
-
public updateBookmarkAsync<T extends BookmarkData = BookmarkData>(
|
|
1022
|
-
id_or_bookmark: string | Bookmark<T>,
|
|
1023
|
-
updates_or_options?: BookmarkUpdate<T> | BookmarkUpdateOptions,
|
|
1024
|
-
options?: BookmarkUpdateOptions,
|
|
1025
|
-
): Promise<Bookmark<T>> {
|
|
1026
|
-
// support the deprecated overload that accepted a full bookmark instead of an id
|
|
1027
|
-
if (typeof id_or_bookmark === 'object') {
|
|
1028
|
-
// @deprecated
|
|
1029
|
-
console.warn(
|
|
1030
|
-
'updateBookmarkAsync(bookmark, updates, options) is deprecated, use updateBookmarkAsync(id, updates, options) instead',
|
|
1031
|
-
);
|
|
1032
|
-
const { id, ...updates } = id_or_bookmark as Bookmark<T>;
|
|
1033
|
-
// `updates_or_options` is a union of `BookmarkUpdate<T> | BookmarkUpdateOptions` here,
|
|
1034
|
-
// neither of which declares `updatePayload` — cast through `unknown` to probe for the
|
|
1035
|
-
// legacy options shape at runtime.
|
|
1036
|
-
return lastValueFrom(
|
|
1037
|
-
this.updateBookmark<T>(id, updates as BookmarkUpdate<T>, {
|
|
1038
|
-
excludePayloadGeneration: !(
|
|
1039
|
-
updates_or_options as unknown as {
|
|
1040
|
-
updatePayload: boolean;
|
|
1041
|
-
}
|
|
1042
|
-
).updatePayload,
|
|
1043
|
-
}),
|
|
1044
|
-
);
|
|
1045
|
-
}
|
|
1046
|
-
return lastValueFrom(
|
|
1047
|
-
this.updateBookmark<T>(id_or_bookmark, updates_or_options as BookmarkUpdate<T>, options),
|
|
1048
|
-
);
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
/**
|
|
1052
|
-
* Deletes a bookmark with the specified bookmarkId.
|
|
1053
|
-
* The deletion executes immediately when this method is called.
|
|
1054
|
-
*
|
|
1055
|
-
* @param bookmarkId - The unique identifier of the bookmark to be deleted.
|
|
1056
|
-
* @returns An Observable that emits when the bookmark is successfully deleted.
|
|
1057
|
-
* @throws {BookmarkProviderError} If there is an error deleting the bookmark.
|
|
1058
|
-
*/
|
|
1059
|
-
public deleteBookmark(bookmarkId: string): Observable<void> {
|
|
1060
|
-
const { ref, action$ } = this._useScopedActions();
|
|
1061
|
-
|
|
1062
|
-
this._log?.debug(`Removing bookmark: ${bookmarkId}, ref: ${ref}`);
|
|
1063
|
-
|
|
1064
|
-
// bookmark to delete
|
|
1065
|
-
const bookmark = bookmarkSelector(this.#store.value, bookmarkId) ?? {
|
|
1066
|
-
id: bookmarkId,
|
|
1067
|
-
};
|
|
1068
|
-
|
|
1069
|
-
// observable that dispatches the 'onBookmarkDelete' event and maps the result
|
|
1070
|
-
const dispatch$ = from(
|
|
1071
|
-
this._dispatchEvent('onBookmarkDelete', {
|
|
1072
|
-
detail: bookmark,
|
|
1073
|
-
cancelable: true,
|
|
1074
|
-
}),
|
|
1075
|
-
).pipe(
|
|
1076
|
-
map(({ canceled, type, detail }) => {
|
|
1077
|
-
// throw an error if the event is canceled
|
|
1078
|
-
if (canceled) {
|
|
1079
|
-
const error = new BookmarkProviderError(
|
|
1080
|
-
`event: ${type} was canceled by listener for removing bookmark: ${bookmarkId}, ref: ${ref}`,
|
|
1081
|
-
);
|
|
1082
|
-
this._log?.warn(error.message);
|
|
1083
|
-
throw error;
|
|
1084
|
-
}
|
|
1085
|
-
return detail;
|
|
1086
|
-
}),
|
|
1087
|
-
);
|
|
1088
|
-
|
|
1089
|
-
// Start the deletion process immediately
|
|
1090
|
-
const deleteSubscription = dispatch$.subscribe({
|
|
1091
|
-
error: (error) => {
|
|
1092
|
-
this._log?.error(`Failed to prepare bookmark deletion: ${bookmarkId}`, error);
|
|
1093
|
-
},
|
|
1094
|
-
next: (bookmark) => {
|
|
1095
|
-
// request the store to delete the bookmark
|
|
1096
|
-
this.#store.next(bookmarkActions.deleteBookmark(bookmark.id, { ref }));
|
|
1097
|
-
},
|
|
1098
|
-
});
|
|
1099
|
-
|
|
1100
|
-
// monitor the failure case when deleting a bookmark
|
|
1101
|
-
const failure$ = action$.pipe(
|
|
1102
|
-
filter(bookmarkActions.deleteBookmark.failure.match),
|
|
1103
|
-
map(({ payload: cause }) => {
|
|
1104
|
-
const error = new BookmarkProviderError(`Failed to delete bookmark: ${bookmarkId}`, {
|
|
1105
|
-
cause,
|
|
1106
|
-
});
|
|
1107
|
-
this._log?.warn(error.message);
|
|
1108
|
-
throw error;
|
|
1109
|
-
}),
|
|
1110
|
-
timeout({
|
|
1111
|
-
each: defaultTimeout,
|
|
1112
|
-
with: () => {
|
|
1113
|
-
throw new BookmarkProviderError(`Timeout while deleting bookmark: ${bookmarkId}`);
|
|
1114
|
-
},
|
|
1115
|
-
}),
|
|
1116
|
-
);
|
|
1117
|
-
|
|
1118
|
-
// monitor the success case when deleting a bookmark
|
|
1119
|
-
const request$ = action$.pipe(
|
|
1120
|
-
filter(bookmarkActions.deleteBookmark.success.match),
|
|
1121
|
-
map(() => undefined),
|
|
1122
|
-
tap(() => {
|
|
1123
|
-
this._log?.info(`Removed bookmark: ${bookmark.id}, ref: ${ref}`);
|
|
1124
|
-
this._dispatchEvent('onBookmarkDeleted', { detail: bookmark });
|
|
1125
|
-
}),
|
|
1126
|
-
raceWith(failure$),
|
|
1127
|
-
first(),
|
|
1128
|
-
);
|
|
1129
|
-
|
|
1130
|
-
// Return the observable that will emit the result
|
|
1131
|
-
return new Observable<void>((subscriber) => {
|
|
1132
|
-
// Clean up the delete subscription when the result observable is unsubscribed
|
|
1133
|
-
subscriber.add(() => deleteSubscription.unsubscribe());
|
|
1134
|
-
|
|
1135
|
-
// Emit the deletion result
|
|
1136
|
-
request$.subscribe(subscriber);
|
|
1137
|
-
});
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
/**
|
|
1141
|
-
* Deletes a bookmark asynchronously.
|
|
1142
|
-
*
|
|
1143
|
-
* @param bookmarkId - The ID of the bookmark to delete.
|
|
1144
|
-
* @returns A Promise that resolves when the bookmark is deleted.
|
|
1145
|
-
*/
|
|
1146
|
-
public async deleteBookmarkAsync(bookmarkId: string): Promise<void> {
|
|
1147
|
-
return lastValueFrom(this.deleteBookmark(bookmarkId));
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
/**
|
|
1151
|
-
* Adds a bookmark to favorites.
|
|
1152
|
-
*
|
|
1153
|
-
* @param bookmarkId - The ID of the bookmark to add.
|
|
1154
|
-
* @returns An observable that emits the added bookmark, or undefined if it could not be resolved.
|
|
1155
|
-
* @throws {BookmarkProviderError} If the onBookmarkFavouriteAdd event is canceled, the add request fails, or the request times out.
|
|
1156
|
-
*/
|
|
1157
|
-
public addBookmarkToFavorites(bookmarkId: string): Observable<BookmarkWithoutData | undefined> {
|
|
1158
|
-
/**
|
|
1159
|
-
* Observable that represents the result of adding a bookmark as a favorite.
|
|
1160
|
-
* Emits a `Bookmark` object or `undefined` if the operation fails to resolve added bookmark.
|
|
1161
|
-
*/
|
|
1162
|
-
const result$ = new Observable<Bookmark | undefined>((subscriber) => {
|
|
1163
|
-
const { ref, action$ } = this._useScopedActions();
|
|
1164
|
-
|
|
1165
|
-
this._log?.debug(`Adding bookmark: ${bookmarkId} to favourites, ref: ${ref}`);
|
|
1166
|
-
|
|
1167
|
-
// observable that dispatches the 'onBookmarkFavouriteAdd' event and maps the result
|
|
1168
|
-
const dispatch$ = from(
|
|
1169
|
-
this._dispatchEvent('onBookmarkFavouriteAdd', {
|
|
1170
|
-
detail: { id: bookmarkId },
|
|
1171
|
-
cancelable: true,
|
|
1172
|
-
}),
|
|
1173
|
-
).pipe(
|
|
1174
|
-
map(({ canceled, type }) => {
|
|
1175
|
-
// throw an error if the event is canceled
|
|
1176
|
-
if (canceled) {
|
|
1177
|
-
const error = new BookmarkProviderError(
|
|
1178
|
-
`event: ${type} was canceled by listener for adding favourite bookmark: ${bookmarkId}, ref: ${ref}`,
|
|
1179
|
-
);
|
|
1180
|
-
this._log?.info(error.message);
|
|
1181
|
-
throw error;
|
|
1182
|
-
}
|
|
1183
|
-
}),
|
|
1184
|
-
);
|
|
1185
|
-
|
|
1186
|
-
// execute the add bookmark as a favorite action when the event is not canceled
|
|
1187
|
-
subscriber.add(
|
|
1188
|
-
dispatch$.subscribe({
|
|
1189
|
-
error: (error) => subscriber.error(error),
|
|
1190
|
-
next: () => {
|
|
1191
|
-
this.#store.next(bookmarkActions.addBookmarkAsFavourite(bookmarkId, { ref }));
|
|
1192
|
-
},
|
|
1193
|
-
}),
|
|
1194
|
-
);
|
|
1195
|
-
|
|
1196
|
-
// monitor the failure case when adding a bookmark as a favorite
|
|
1197
|
-
const failure$ = action$.pipe(
|
|
1198
|
-
filter(bookmarkActions.addBookmarkAsFavourite.failure.match),
|
|
1199
|
-
map(({ payload: cause }) => {
|
|
1200
|
-
const error = new BookmarkProviderError(
|
|
1201
|
-
`Failed to add bookmark to favorites: ${bookmarkId}`,
|
|
1202
|
-
{ cause },
|
|
1203
|
-
);
|
|
1204
|
-
this._log?.warn(error.message);
|
|
1205
|
-
throw error;
|
|
1206
|
-
}),
|
|
1207
|
-
timeout({
|
|
1208
|
-
each: defaultTimeout,
|
|
1209
|
-
with: () => {
|
|
1210
|
-
throw new BookmarkProviderError(
|
|
1211
|
-
`Timeout while adding bookmark to favorites: ${bookmarkId}`,
|
|
1212
|
-
);
|
|
1213
|
-
},
|
|
1214
|
-
}),
|
|
1215
|
-
);
|
|
1216
|
-
|
|
1217
|
-
// monitor the success case when adding a bookmark as a favorite
|
|
1218
|
-
const request$ = action$.pipe(
|
|
1219
|
-
filter(bookmarkActions.addBookmarkAsFavourite.success.match),
|
|
1220
|
-
switchMap(({ payload }) => this.getBookmark(payload, { excludePayload: true })),
|
|
1221
|
-
tap((bookmark) => {
|
|
1222
|
-
this._log?.info(`Added bookmark: ${bookmarkId} to favourites, ref: ${ref}`);
|
|
1223
|
-
this._dispatchEvent('onBookmarkFavouriteAdded', { detail: bookmark });
|
|
1224
|
-
}),
|
|
1225
|
-
raceWith(failure$),
|
|
1226
|
-
first(),
|
|
1227
|
-
);
|
|
1228
|
-
|
|
1229
|
-
// emit the added bookmark
|
|
1230
|
-
return request$.subscribe(subscriber);
|
|
1231
|
-
});
|
|
1232
|
-
|
|
1233
|
-
return result$;
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
/**
|
|
1237
|
-
* Asynchronously adds a bookmark to the user's favorites.
|
|
1238
|
-
*
|
|
1239
|
-
* @param bookmarkId - The ID of the bookmark to add to the user's favorites.
|
|
1240
|
-
* @returns A Promise that resolves to the added Bookmark, or null if the operation failed.
|
|
1241
|
-
*/
|
|
1242
|
-
public addBookmarkToFavoritesAsync(bookmarkId: string): Promise<Bookmark | undefined> {
|
|
1243
|
-
return lastValueFrom(this.addBookmarkToFavorites(bookmarkId));
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
/**
|
|
1247
|
-
* Removes a bookmark as a favorite.
|
|
1248
|
-
*
|
|
1249
|
-
* @param bookmarkId - The ID of the bookmark to remove as a favorite.
|
|
1250
|
-
* @returns A Promise that resolves when the bookmark is successfully removed as a favorite.
|
|
1251
|
-
* @throws {BookmarkProviderError} If the event is canceled or if there is a failure in removing the bookmark as a favorite.
|
|
1252
|
-
*/
|
|
1253
|
-
public removeBookmarkAsFavorite(bookmarkId: string): Observable<void> {
|
|
1254
|
-
/**
|
|
1255
|
-
* Represents an observable that emits void values when a bookmark's favorite is removed.
|
|
1256
|
-
*/
|
|
1257
|
-
const result$ = new Observable<void>((subscriber) => {
|
|
1258
|
-
const { ref, action$ } = this._useScopedActions();
|
|
1259
|
-
|
|
1260
|
-
this._log?.debug(`Removing bookmark: ${bookmarkId} from favourites, ref: ${ref}`);
|
|
1261
|
-
|
|
1262
|
-
// get the bookmark to remove as a favorite
|
|
1263
|
-
const bookmark = bookmarkSelector(this.#store.value, bookmarkId) ?? { id: bookmarkId };
|
|
1264
|
-
|
|
1265
|
-
// observable that dispatches the 'onBookmarkFavouriteRemove' event and maps the result
|
|
1266
|
-
const dispatch$ = from(
|
|
1267
|
-
this._dispatchEvent('onBookmarkFavouriteRemove', {
|
|
1268
|
-
detail: bookmark,
|
|
1269
|
-
cancelable: true,
|
|
1270
|
-
}),
|
|
1271
|
-
).pipe(
|
|
1272
|
-
map(({ canceled, type, detail }) => {
|
|
1273
|
-
// throw an error if the event is canceled
|
|
1274
|
-
if (canceled) {
|
|
1275
|
-
const error = new BookmarkProviderError(
|
|
1276
|
-
`event: ${type} was canceled by listener for removing favourite bookmark: ${bookmarkId}, ref: ${ref}`,
|
|
1277
|
-
);
|
|
1278
|
-
this._log?.warn(error.message);
|
|
1279
|
-
throw error;
|
|
1280
|
-
}
|
|
1281
|
-
return detail;
|
|
1282
|
-
}),
|
|
1283
|
-
);
|
|
1284
|
-
|
|
1285
|
-
// execute the remove bookmark as a favorite action when the event is not canceled
|
|
1286
|
-
subscriber.add(
|
|
1287
|
-
dispatch$.subscribe({
|
|
1288
|
-
error: (error) => subscriber.error(error),
|
|
1289
|
-
next: (detail) => {
|
|
1290
|
-
// request the store to remove the bookmark as a favorite
|
|
1291
|
-
this.#store.next(bookmarkActions.removeBookmarkAsFavourite(detail.id, { ref }));
|
|
1292
|
-
},
|
|
1293
|
-
}),
|
|
1294
|
-
);
|
|
1295
|
-
|
|
1296
|
-
// monitor the failure case when removing a bookmark as a favorite
|
|
1297
|
-
const failure$ = action$.pipe(
|
|
1298
|
-
filter(bookmarkActions.removeBookmarkAsFavourite.failure.match),
|
|
1299
|
-
map(({ payload: cause }) => {
|
|
1300
|
-
const error = new BookmarkProviderError(
|
|
1301
|
-
`Failed to remove bookmark: ${bookmarkId} from favourites`,
|
|
1302
|
-
{
|
|
1303
|
-
cause,
|
|
1304
|
-
},
|
|
1305
|
-
);
|
|
1306
|
-
this._log?.warn(error.message);
|
|
1307
|
-
throw error;
|
|
1308
|
-
}),
|
|
1309
|
-
timeout({
|
|
1310
|
-
each: defaultTimeout,
|
|
1311
|
-
with: () => {
|
|
1312
|
-
throw new BookmarkProviderError(
|
|
1313
|
-
`Timeout while removing bookmark: ${bookmarkId} from favourites`,
|
|
1314
|
-
);
|
|
1315
|
-
},
|
|
1316
|
-
}),
|
|
1317
|
-
);
|
|
1318
|
-
|
|
1319
|
-
// monitor the success case when removing a bookmark as a favorite
|
|
1320
|
-
const request$ = action$.pipe(
|
|
1321
|
-
filter(bookmarkActions.removeBookmarkAsFavourite.success.match),
|
|
1322
|
-
tap(() => {
|
|
1323
|
-
this._log?.info(`Removed bookmark: ${bookmarkId} from favourites, ref: ${ref}`);
|
|
1324
|
-
this._dispatchEvent('onBookmarkFavouriteRemoved', { detail: bookmark });
|
|
1325
|
-
}),
|
|
1326
|
-
map(() => undefined),
|
|
1327
|
-
raceWith(failure$),
|
|
1328
|
-
first(),
|
|
1329
|
-
);
|
|
1330
|
-
|
|
1331
|
-
// emit the removed bookmark
|
|
1332
|
-
request$.subscribe(subscriber);
|
|
1333
|
-
});
|
|
1334
|
-
|
|
1335
|
-
return result$;
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
|
-
/**
|
|
1339
|
-
* Removes a bookmark as a favorite asynchronously.
|
|
1340
|
-
*
|
|
1341
|
-
* @param bookmarkId - The ID of the bookmark to remove as a favorite.
|
|
1342
|
-
* @returns A Promise that resolves when the bookmark is successfully removed as a favorite.
|
|
1343
|
-
*/
|
|
1344
|
-
public removeBookmarkAsFavoriteAsync(bookmarkId: string): Promise<void> {
|
|
1345
|
-
return lastValueFrom(this.removeBookmarkAsFavorite(bookmarkId));
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
|
-
/**
|
|
1349
|
-
* Checks if a bookmark is in the favorites.
|
|
1350
|
-
* @param bookmarkId - The ID of the bookmark to check.
|
|
1351
|
-
* @returns An observable that emits a boolean indicating whether the bookmark is in the favorites.
|
|
1352
|
-
* @throws {BookmarkProviderError} If checking the favorite status fails.
|
|
1353
|
-
*/
|
|
1354
|
-
public isBookmarkInFavorites(bookmarkId: string): Observable<boolean> {
|
|
1355
|
-
this._log?.debug(`Checking if bookmark: ${bookmarkId} is in favourites`);
|
|
1356
|
-
|
|
1357
|
-
// Check if the bookmark is a favorite
|
|
1358
|
-
return from(this._apiClient.isBookmarkFavorite(bookmarkId)).pipe(
|
|
1359
|
-
catchError((err) => {
|
|
1360
|
-
const error = new BookmarkProviderError(
|
|
1361
|
-
`Failed to check if bookmark: ${bookmarkId} is in favourites`,
|
|
1362
|
-
err,
|
|
1363
|
-
);
|
|
1364
|
-
this._log?.error(error.message, error);
|
|
1365
|
-
throw error;
|
|
1366
|
-
}),
|
|
1367
|
-
tap((isFavorite) => {
|
|
1368
|
-
this._log?.debug(`Bookmark: ${bookmarkId} is ${isFavorite ? 'in' : 'not in'} favourites`);
|
|
1369
|
-
}),
|
|
1370
|
-
);
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
|
-
/**
|
|
1374
|
-
* Checks if the specified bookmark is in the user's favorites.
|
|
1375
|
-
*
|
|
1376
|
-
* @param bookmarkId - The ID of the bookmark to check.
|
|
1377
|
-
* @returns A Promise that resolves to a boolean indicating whether the bookmark is in the user's favorites.
|
|
1378
|
-
*/
|
|
1379
|
-
public isBookmarkInFavoritesAsync(bookmarkId: string): Promise<boolean> {
|
|
1380
|
-
return lastValueFrom(this.isBookmarkInFavorites(bookmarkId));
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
/**
|
|
1384
|
-
* Fetches a single bookmark's metadata (without payload) via the store.
|
|
1385
|
-
*
|
|
1386
|
-
* @param bookmarkId - The unique identifier of the bookmark to fetch.
|
|
1387
|
-
* @param options - Optional settings, including a custom `timeout` in milliseconds.
|
|
1388
|
-
* @returns An observable that emits the bookmark's metadata once fetched.
|
|
1389
|
-
* @throws {BookmarkProviderError} If the fetch fails or times out.
|
|
1390
|
-
*/
|
|
1391
|
-
protected _getBookmarkInfo(
|
|
1392
|
-
bookmarkId: string,
|
|
1393
|
-
options?: { timeout?: number },
|
|
1394
|
-
): Observable<BookmarkWithoutData> {
|
|
1395
|
-
// only actions with the specified reference are considered
|
|
1396
|
-
const { action$ } = this._useScopedActions(bookmarkId);
|
|
1397
|
-
|
|
1398
|
-
// observable stream that emits the bookmark data when the fetch action is successful
|
|
1399
|
-
const success$ = action$.pipe(
|
|
1400
|
-
filter(bookmarkActions.fetchBookmark.success.match),
|
|
1401
|
-
map((action) => action.payload),
|
|
1402
|
-
first(),
|
|
1403
|
-
);
|
|
1404
|
-
|
|
1405
|
-
// observable stream that emits an error when fetching bookmark data fails
|
|
1406
|
-
const failure$ = this.#store.action$.pipe(
|
|
1407
|
-
filter(bookmarkActions.fetchBookmark.failure.match),
|
|
1408
|
-
map((action) => {
|
|
1409
|
-
throw new BookmarkProviderError(
|
|
1410
|
-
`Failed to fetch bookmark data: ${bookmarkId}`,
|
|
1411
|
-
action.payload,
|
|
1412
|
-
);
|
|
1413
|
-
}),
|
|
1414
|
-
timeout({
|
|
1415
|
-
each: options?.timeout ?? 2 * 60 * 1000,
|
|
1416
|
-
with: () => {
|
|
1417
|
-
throw new BookmarkProviderError(`Timeout while fetching bookmark data: ${bookmarkId}`);
|
|
1418
|
-
},
|
|
1419
|
-
}),
|
|
1420
|
-
);
|
|
1421
|
-
|
|
1422
|
-
this.#store.next(bookmarkActions.fetchBookmark(bookmarkId, { ref: bookmarkId }));
|
|
1423
|
-
|
|
1424
|
-
// race the success and failure streams, whichever resolves first wins
|
|
1425
|
-
return success$.pipe(raceWith(failure$));
|
|
1426
|
-
}
|
|
1427
|
-
|
|
1428
|
-
/**
|
|
1429
|
-
* Fetches a single bookmark's payload data via the store.
|
|
1430
|
-
*
|
|
1431
|
-
* @template T - The type of the bookmark payload.
|
|
1432
|
-
* @param bookmarkId - The unique identifier of the bookmark whose data to fetch.
|
|
1433
|
-
* @param options - Optional settings, including a custom `timeout` in milliseconds.
|
|
1434
|
-
* @returns An observable that emits the bookmark's payload data once fetched.
|
|
1435
|
-
* @throws {BookmarkProviderError} If the fetch fails or times out.
|
|
1436
|
-
*/
|
|
1437
|
-
protected _getBookmarkData<T extends BookmarkData = BookmarkData>(
|
|
1438
|
-
bookmarkId: string,
|
|
1439
|
-
options?: { timeout?: number },
|
|
1440
|
-
): Observable<T | null> {
|
|
1441
|
-
// only actions with the specified reference are considered
|
|
1442
|
-
const { action$ } = this._useScopedActions(bookmarkId);
|
|
1443
|
-
|
|
1444
|
-
// observable stream that emits the bookmark data when the fetch action is successful
|
|
1445
|
-
const success$ = action$.pipe(
|
|
1446
|
-
filter(bookmarkActions.fetchBookmarkData.success.match),
|
|
1447
|
-
map((action) => action.payload.data as T),
|
|
1448
|
-
first(),
|
|
1449
|
-
);
|
|
1450
|
-
|
|
1451
|
-
// observable stream that emits an error when fetching bookmark data fails
|
|
1452
|
-
const failure$ = this.#store.action$.pipe(
|
|
1453
|
-
filter(bookmarkActions.fetchBookmarkData.failure.match),
|
|
1454
|
-
map((action) => {
|
|
1455
|
-
throw new BookmarkProviderError(
|
|
1456
|
-
`Failed to fetch bookmark data: ${bookmarkId}`,
|
|
1457
|
-
action.payload,
|
|
1458
|
-
);
|
|
1459
|
-
}),
|
|
1460
|
-
timeout({
|
|
1461
|
-
each: options?.timeout ?? defaultTimeout,
|
|
1462
|
-
with: () => {
|
|
1463
|
-
throw new BookmarkProviderError(`Timeout while fetching bookmark data: ${bookmarkId}`);
|
|
1464
|
-
},
|
|
1465
|
-
}),
|
|
1466
|
-
);
|
|
1467
|
-
|
|
1468
|
-
// request the store to fetch the bookmark data
|
|
1469
|
-
this.#store.next(bookmarkActions.fetchBookmarkData(bookmarkId, { ref: bookmarkId }));
|
|
1470
|
-
|
|
1471
|
-
// race the success and failure streams, whichever resolves first wins
|
|
1472
|
-
return success$.pipe(raceWith(failure$));
|
|
1473
|
-
}
|
|
1474
|
-
|
|
1475
|
-
/**
|
|
1476
|
-
* Dispatches an event of the specified type with the provided arguments.
|
|
1477
|
-
*
|
|
1478
|
-
* @template TType - The specific event type key being dispatched.
|
|
1479
|
-
* @param type - The type of the event to dispatch.
|
|
1480
|
-
* @param args - The arguments to pass to the event.
|
|
1481
|
-
* @returns A promise that resolves with the result of the event dispatch.
|
|
1482
|
-
*/
|
|
1483
|
-
protected async _dispatchEvent<TType extends keyof BookmarkProviderEventMap>(
|
|
1484
|
-
type: TType,
|
|
1485
|
-
args: FrameworkEventInitType<BookmarkProviderEventMap[NoInfer<TType>]>,
|
|
1486
|
-
): Promise<BookmarkProviderEventMap[NoInfer<TType>]> {
|
|
1487
|
-
// create a new event instance
|
|
1488
|
-
const event = new FrameworkEvent(type, {
|
|
1489
|
-
source: this,
|
|
1490
|
-
canBubble: true,
|
|
1491
|
-
...args,
|
|
1492
|
-
}) as BookmarkProviderEventMap[NoInfer<TType>];
|
|
1493
|
-
|
|
1494
|
-
// only dispatch through the event bus when one is available
|
|
1495
|
-
if (this._event) {
|
|
1496
|
-
this._log?.debug(`dispatching event ${type}`, args);
|
|
1497
|
-
const { canceled } = await this._event.dispatchEvent(event);
|
|
1498
|
-
// log when a listener canceled the event
|
|
1499
|
-
if (canceled) {
|
|
1500
|
-
this._log?.debug(`event ${type} was canceled`, args);
|
|
1501
|
-
}
|
|
1502
|
-
}
|
|
1503
|
-
return event;
|
|
1504
|
-
}
|
|
1505
|
-
|
|
1506
|
-
/**
|
|
1507
|
-
* Creates a scoped action reference and a filtered action stream containing only
|
|
1508
|
-
* actions tagged with that reference (or the provided `ref`, if given).
|
|
1509
|
-
*
|
|
1510
|
-
* @template TAction - The specific action type emitted on the scoped stream.
|
|
1511
|
-
* @param ref - An existing reference to scope actions to; a new GUID is generated when omitted.
|
|
1512
|
-
* @returns An object containing the resolved `ref` and the `action$` stream filtered to it.
|
|
1513
|
-
*/
|
|
1514
|
-
protected _useScopedActions<TAction extends BookmarkActions>(
|
|
1515
|
-
ref?: string,
|
|
1516
|
-
): {
|
|
1517
|
-
ref: string;
|
|
1518
|
-
action$: Observable<TAction>;
|
|
1519
|
-
} {
|
|
1520
|
-
/**
|
|
1521
|
-
* Generates a unique identifier for the operation
|
|
1522
|
-
*/
|
|
1523
|
-
const operationRef = ref ?? generateGUID();
|
|
1524
|
-
|
|
1525
|
-
/**
|
|
1526
|
-
* Observable stream of actions filtered by a specific reference.
|
|
1527
|
-
*/
|
|
1528
|
-
const action$ = this.#store.action$.pipe(
|
|
1529
|
-
filter((action): action is TAction => 'meta' in action && action.meta?.ref === operationRef),
|
|
1530
|
-
);
|
|
1531
|
-
|
|
1532
|
-
return { ref: operationRef, action$ };
|
|
1533
|
-
}
|
|
1534
|
-
|
|
1535
|
-
/**
|
|
1536
|
-
* Disposes the BookmarkProvider.
|
|
1537
|
-
*/
|
|
1538
|
-
dispose(): void {
|
|
1539
|
-
this._log?.debug('disposing BookmarkProvider');
|
|
1540
|
-
this.#subscriptions.unsubscribe();
|
|
1541
|
-
}
|
|
1542
|
-
|
|
1543
|
-
/**
|
|
1544
|
-
* Disposes the BookmarkProvider via the `Symbol.dispose` protocol.
|
|
1545
|
-
*/
|
|
1546
|
-
[Symbol.dispose]() {
|
|
1547
|
-
this.dispose();
|
|
1548
|
-
}
|
|
1549
|
-
}
|