@fabriktor/fx 0.0.147 → 0.0.149
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/src/chat/forms.js +2 -2
- package/dist/src/chat/send-queue.d.ts +2 -0
- package/dist/src/chat/send-queue.js +25 -5
- package/dist/src/core/events.d.ts +2 -0
- package/dist/src/core/events.js +20 -1
- package/dist/src/core/search-controller.d.ts +1 -0
- package/dist/src/core/search-controller.js +3 -0
- package/dist/src/users/users.d.ts +1 -0
- package/dist/src/users/users.js +7 -0
- package/package.json +1 -1
package/dist/src/chat/forms.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { normalizeIDs, resolvedID } from "../core/id.js";
|
|
2
|
-
import { isFabriktorUser, userSupportsMode } from "../users/users.js";
|
|
2
|
+
import { isFabriktorUser, isOtherUserID, userSupportsMode, } from "../users/users.js";
|
|
3
3
|
// Recipients are all other members of the conversation, resolved before use.
|
|
4
4
|
// Sender modes apply now; recipient modes describe what their identity supports.
|
|
5
5
|
// Do not depend on the recipient's current mode, which Public() omits.
|
|
@@ -10,7 +10,7 @@ export function canSendChatForm(policy, actor, recipients) {
|
|
|
10
10
|
(policy.allow_group || recipients.length === 1) &&
|
|
11
11
|
normalizeIDs(recipients.map((recipient) => recipient.id)).length ===
|
|
12
12
|
recipients.length &&
|
|
13
|
-
recipients.every((recipient) => recipient.id
|
|
13
|
+
recipients.every((recipient) => isOtherUserID(recipient.id, actor.owner_id) &&
|
|
14
14
|
isFabriktorUser(recipient) &&
|
|
15
15
|
policy.recipient_modes.some((mode) => userSupportsMode(recipient, mode))));
|
|
16
16
|
}
|
|
@@ -48,6 +48,7 @@ export type ChatSendQueueOptions = {
|
|
|
48
48
|
upload: (entry: QueuedChat, signal: AbortSignal) => Promise<boolean>;
|
|
49
49
|
changed?: (chat?: Chat) => void;
|
|
50
50
|
online?: () => boolean;
|
|
51
|
+
on_error?: (error: unknown) => void;
|
|
51
52
|
};
|
|
52
53
|
export declare class ChatSendQueue {
|
|
53
54
|
private readonly opts;
|
|
@@ -87,6 +88,7 @@ export declare class ChatSendQueue {
|
|
|
87
88
|
private startConfirmation;
|
|
88
89
|
private startUpload;
|
|
89
90
|
private recoverUpload;
|
|
91
|
+
private report;
|
|
90
92
|
private waitForRetry;
|
|
91
93
|
private recoverStorage;
|
|
92
94
|
private scheduleRecovery;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// not use this file except in compliance with the License. You may obtain
|
|
5
5
|
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
6
|
import { requiredOperationID } from "../core/id.js";
|
|
7
|
-
import { createEvent, EventBus } from "../core/events.js";
|
|
7
|
+
import { createEvent, EventBus, notifyObserver, } from "../core/events.js";
|
|
8
8
|
import { PersistentQueue, QueueWakeup, nextQueueRetry, } from "../core/queue.js";
|
|
9
9
|
import { newOptimisticChat } from "./optimistic.js";
|
|
10
10
|
export const ChatSendStatus = {
|
|
@@ -141,6 +141,7 @@ export class ChatSendQueue {
|
|
|
141
141
|
this.pendingSaves.set(entry.id, failed);
|
|
142
142
|
this.replace(failed);
|
|
143
143
|
if (entry.owner_id === this.owner) {
|
|
144
|
+
this.report(err);
|
|
144
145
|
this.error = err;
|
|
145
146
|
this.publish();
|
|
146
147
|
}
|
|
@@ -198,7 +199,9 @@ export class ChatSendQueue {
|
|
|
198
199
|
}
|
|
199
200
|
for (const entry of stored) {
|
|
200
201
|
if (chatSendCanConfirm(entry) ||
|
|
201
|
-
(!entry.chat &&
|
|
202
|
+
(!entry.chat &&
|
|
203
|
+
entry.server_id &&
|
|
204
|
+
this.confirmed.has(entry.server_id))) {
|
|
202
205
|
this.startConfirmation(entry, owner, signal);
|
|
203
206
|
}
|
|
204
207
|
if (entry.chat && entry.status === ChatSendStatus.Uploading) {
|
|
@@ -208,6 +211,7 @@ export class ChatSendQueue {
|
|
|
208
211
|
}
|
|
209
212
|
catch (err) {
|
|
210
213
|
if (this.current(owner, signal)) {
|
|
214
|
+
this.report(err);
|
|
211
215
|
this.error = err;
|
|
212
216
|
this.publish();
|
|
213
217
|
this.recoverStorage();
|
|
@@ -321,6 +325,7 @@ export class ChatSendQueue {
|
|
|
321
325
|
select: (entry) => entry.input.chat_room_id === room && chatSendCanCreate(entry),
|
|
322
326
|
on_error: (err) => {
|
|
323
327
|
if (this.current(owner, signal)) {
|
|
328
|
+
this.report(err);
|
|
324
329
|
this.error = err;
|
|
325
330
|
this.publish();
|
|
326
331
|
this.recoverStorage();
|
|
@@ -390,12 +395,14 @@ export class ChatSendQueue {
|
|
|
390
395
|
}
|
|
391
396
|
this.startConfirmation(current, owner, signal);
|
|
392
397
|
}
|
|
393
|
-
catch {
|
|
398
|
+
catch (error) {
|
|
394
399
|
// remove() owns deliberate cancellation and removes the durable entry
|
|
395
400
|
// under this same room lock. Do not publish a transient send failure.
|
|
396
401
|
if (this.cancellations.has(entry.id) && !current.server_id) {
|
|
397
402
|
return false;
|
|
398
403
|
}
|
|
404
|
+
if (this.current(owner, signal))
|
|
405
|
+
this.report(error);
|
|
399
406
|
current = {
|
|
400
407
|
...current,
|
|
401
408
|
attempted: current.attempted && (requestStarted || !!current.server_id),
|
|
@@ -409,6 +416,7 @@ export class ChatSendQueue {
|
|
|
409
416
|
this.pendingCreates.delete(current.id);
|
|
410
417
|
}
|
|
411
418
|
catch (storageError) {
|
|
419
|
+
this.report(storageError);
|
|
412
420
|
this.error = storageError;
|
|
413
421
|
this.publish();
|
|
414
422
|
this.recoverStorage();
|
|
@@ -487,7 +495,9 @@ export class ChatSendQueue {
|
|
|
487
495
|
this.opts.changed?.(chat);
|
|
488
496
|
}
|
|
489
497
|
}
|
|
490
|
-
catch {
|
|
498
|
+
catch (error) {
|
|
499
|
+
if (this.current(owner, signal))
|
|
500
|
+
this.report(error);
|
|
491
501
|
await this.save({
|
|
492
502
|
...current,
|
|
493
503
|
retry: undefined,
|
|
@@ -501,6 +511,7 @@ export class ChatSendQueue {
|
|
|
501
511
|
})
|
|
502
512
|
.catch((err) => {
|
|
503
513
|
if (this.current(owner, signal)) {
|
|
514
|
+
this.report(err);
|
|
504
515
|
this.error = err;
|
|
505
516
|
this.publish();
|
|
506
517
|
this.recoverStorage();
|
|
@@ -563,7 +574,9 @@ export class ChatSendQueue {
|
|
|
563
574
|
this.publish();
|
|
564
575
|
this.opts.changed?.(this.confirmed.get(entry.server_id));
|
|
565
576
|
}
|
|
566
|
-
catch {
|
|
577
|
+
catch (error) {
|
|
578
|
+
if (this.current(owner, signal))
|
|
579
|
+
this.report(error);
|
|
567
580
|
await this.save({
|
|
568
581
|
...entry,
|
|
569
582
|
status: ChatSendStatus.UploadInterrupted,
|
|
@@ -572,6 +585,7 @@ export class ChatSendQueue {
|
|
|
572
585
|
})
|
|
573
586
|
.catch((err) => {
|
|
574
587
|
if (this.current(owner, signal)) {
|
|
588
|
+
this.report(err);
|
|
575
589
|
this.error = err;
|
|
576
590
|
this.publish();
|
|
577
591
|
}
|
|
@@ -616,6 +630,7 @@ export class ChatSendQueue {
|
|
|
616
630
|
if (!this.current(owner, signal)) {
|
|
617
631
|
return;
|
|
618
632
|
}
|
|
633
|
+
this.report(err);
|
|
619
634
|
this.error = err;
|
|
620
635
|
}
|
|
621
636
|
await this.save({
|
|
@@ -625,12 +640,17 @@ export class ChatSendQueue {
|
|
|
625
640
|
})
|
|
626
641
|
.catch((err) => {
|
|
627
642
|
if (this.current(owner, signal)) {
|
|
643
|
+
this.report(err);
|
|
628
644
|
this.error = err;
|
|
629
645
|
this.publish();
|
|
630
646
|
}
|
|
631
647
|
})
|
|
632
648
|
.finally(() => this.uploads.delete(entry.id));
|
|
633
649
|
}
|
|
650
|
+
report(error) {
|
|
651
|
+
if (this.opts.on_error)
|
|
652
|
+
notifyObserver(() => this.opts.on_error?.(error));
|
|
653
|
+
}
|
|
634
654
|
waitForRetry(entry) {
|
|
635
655
|
if (!entry.retry || entry.retry.after <= Date.now())
|
|
636
656
|
return false;
|
|
@@ -5,6 +5,8 @@ export type EventKey<T> = symbol & {
|
|
|
5
5
|
export type EventListener<T> = (value: T) => void | Promise<void>;
|
|
6
6
|
export declare function createEvent<T>(): EventKey<T>;
|
|
7
7
|
export declare function notifyObserver(run: () => unknown): void;
|
|
8
|
+
type ObserverErrorHandler = (error: unknown) => void;
|
|
9
|
+
export declare function setObserverErrorHandler(handler: ObserverErrorHandler): () => void;
|
|
8
10
|
export declare class EventBus {
|
|
9
11
|
private readonly listeners;
|
|
10
12
|
subscribe<T>(event: EventKey<T>, listener: EventListener<NoInfer<T>>, initial?: () => T): () => void;
|
package/dist/src/core/events.js
CHANGED
|
@@ -14,9 +14,28 @@ export function notifyObserver(run) {
|
|
|
14
14
|
observerError(err);
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
-
function
|
|
17
|
+
function defaultObserverError(err) {
|
|
18
18
|
console.error("Event observer failed.", err);
|
|
19
19
|
}
|
|
20
|
+
let observerErrorHandler = defaultObserverError;
|
|
21
|
+
// Apps own diagnostic presentation; independent FX consumers keep the existing
|
|
22
|
+
// console fallback. Observer failures must never break a completed mutation.
|
|
23
|
+
export function setObserverErrorHandler(handler) {
|
|
24
|
+
const previous = observerErrorHandler;
|
|
25
|
+
observerErrorHandler = handler;
|
|
26
|
+
return () => {
|
|
27
|
+
if (observerErrorHandler === handler)
|
|
28
|
+
observerErrorHandler = previous;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function observerError(err) {
|
|
32
|
+
try {
|
|
33
|
+
observerErrorHandler(err);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
defaultObserverError(err);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
20
39
|
export class EventBus {
|
|
21
40
|
listeners = new Map();
|
|
22
41
|
subscribe(event, listener, initial) {
|
|
@@ -11,6 +11,7 @@ export type SearchState<T> = {
|
|
|
11
11
|
export type SearchControllerOptions<T, Options extends SearchOptions> = {
|
|
12
12
|
search: (opts: Options) => Promise<SearchPage<T>>;
|
|
13
13
|
on_change: (state: SearchState<T>) => void;
|
|
14
|
+
on_error?: (error: unknown) => void;
|
|
14
15
|
};
|
|
15
16
|
export declare class SearchController<T extends SearchResultWithID, Options extends SearchOptions> {
|
|
16
17
|
private readonly options;
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
6
|
import { appendUniqueSearchResults, normalizeSearchPage, } from "./search.js";
|
|
7
7
|
import { errUnknown } from "./err.js";
|
|
8
|
+
import { notifyObserver } from "./events.js";
|
|
8
9
|
// Holds only the current selection and its displayed pages. Previous queries
|
|
9
10
|
// are discarded, and revisiting a selection always makes a backend request.
|
|
10
11
|
export class SearchController {
|
|
@@ -96,6 +97,8 @@ export class SearchController {
|
|
|
96
97
|
loading_more: false,
|
|
97
98
|
error: errUnknown(error),
|
|
98
99
|
});
|
|
100
|
+
if (this.options.on_error)
|
|
101
|
+
notifyObserver(() => this.options.on_error?.(error));
|
|
99
102
|
return false;
|
|
100
103
|
}
|
|
101
104
|
finally {
|
|
@@ -17,6 +17,7 @@ export type UpdateExternalUserOptions = {
|
|
|
17
17
|
};
|
|
18
18
|
export declare function userSupportsMode(user: Pick<User, "account_identity"> | null | undefined, mode: User["active_mode"]): boolean;
|
|
19
19
|
export declare function isFabriktorUser(user: Pick<User, "account_identity"> | null | undefined): boolean;
|
|
20
|
+
export declare function isOtherUserID(user_id: string | null | undefined, owner_id: string | null | undefined): boolean;
|
|
20
21
|
export declare function canManageExternalUser(user: User | null | undefined, owner_id: string): boolean;
|
|
21
22
|
export type SearchExternalUsersOptions = SearchUsersOptions;
|
|
22
23
|
export type SearchExternalUsersPage = SearchUsersPage;
|
package/dist/src/users/users.js
CHANGED
|
@@ -44,6 +44,13 @@ export function userSupportsMode(user, mode) {
|
|
|
44
44
|
export function isFabriktorUser(user) {
|
|
45
45
|
return userSupportsMode(user, UserTypeClient);
|
|
46
46
|
}
|
|
47
|
+
// Client capability and being a different party are separate requirements.
|
|
48
|
+
// Apply this to counterparties, not to ownership or employee scope filters.
|
|
49
|
+
export function isOtherUserID(user_id, owner_id) {
|
|
50
|
+
const userID = resolvedID(user_id ?? undefined);
|
|
51
|
+
const ownerID = resolvedID(owner_id ?? undefined);
|
|
52
|
+
return !!userID && !!ownerID && userID !== ownerID;
|
|
53
|
+
}
|
|
47
54
|
export function canManageExternalUser(user, owner_id) {
|
|
48
55
|
return (!!resolvedID(owner_id) &&
|
|
49
56
|
user?.account_identity === UserTypeExternal &&
|