@mohasinac/appkit 4.30.18 → 4.30.19
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/_internal/client/features/digital-code/CodeRevealPanel.js +5 -2
- package/dist/_internal/client/features/layout/DashboardLayoutClient.js +3 -3
- package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +3 -2
- package/dist/_internal/client/features/maintenance/views/AnalysisRunnerView.js +5 -2
- package/dist/_internal/client/features/maintenance/views/CloudLogsListView.js +11 -3
- package/dist/_internal/client/hand-mode/HandModeProvider.js +2 -0
- package/dist/_internal/server/features/auctions/actions.js +14 -3
- package/dist/_internal/server/features/auctions/service.js +1 -1
- package/dist/_internal/server/features/blog/data.js +2 -2
- package/dist/_internal/server/features/blog/service.js +3 -3
- package/dist/_internal/server/features/brands/data.js +1 -1
- package/dist/_internal/server/features/brands/service.js +3 -3
- package/dist/_internal/server/features/bundles/actions.js +6 -1
- package/dist/_internal/server/features/bundles/data.js +21 -4
- package/dist/_internal/server/features/cart/data.js +3 -1
- package/dist/_internal/server/features/cart/service.js +13 -2
- package/dist/_internal/server/features/categories/data.js +12 -3
- package/dist/_internal/server/features/events/actions.js +17 -4
- package/dist/_internal/server/features/events/data.js +1 -1
- package/dist/_internal/server/features/events/service.js +1 -1
- package/dist/_internal/server/features/grouped/data.js +7 -2
- package/dist/_internal/server/features/item-requests/data.js +1 -1
- package/dist/_internal/server/features/lottery/actions.js +2 -2
- package/dist/_internal/server/features/lottery/data.js +1 -1
- package/dist/_internal/server/features/orders/actions.js +7 -7
- package/dist/_internal/server/features/orders/data.d.ts +1 -1
- package/dist/_internal/server/features/orders/data.js +14 -2
- package/dist/_internal/server/features/orders/service.js +1 -1
- package/dist/_internal/server/features/pre-orders/service.js +1 -1
- package/dist/_internal/server/features/prize-draws/actions.js +1 -1
- package/dist/_internal/server/features/products/data.js +7 -2
- package/dist/_internal/server/features/products/service.js +1 -1
- package/dist/_internal/server/features/promotions/actions.js +1 -1
- package/dist/_internal/server/features/promotions/data.js +1 -1
- package/dist/_internal/server/features/promotions/service.js +1 -1
- package/dist/_internal/server/features/reviews/data.d.ts +1 -1
- package/dist/_internal/server/features/reviews/data.js +6 -1
- package/dist/_internal/server/features/reviews/og-data.js +1 -1
- package/dist/_internal/server/features/shared/listing-data-factory.js +7 -2
- package/dist/_internal/server/features/stores/data.js +1 -1
- package/dist/_internal/server/features/sublisting-categories/data.js +1 -1
- package/dist/_internal/server/jobs/core/auctionSettlement.js +6 -1
- package/dist/_internal/server/jobs/core/onOrderStatusChange.js +7 -15
- package/dist/_internal/server/jobs/core/revenueRollup.js +7 -1
- package/dist/client/api/useApiQuery.js +17 -6
- package/dist/client.d.ts +1 -0
- package/dist/client.js +3 -0
- package/dist/contracts/client-realtime.d.ts +6 -0
- package/dist/contracts/realtime-session.d.ts +12 -12
- package/dist/contracts/realtime-session.js +72 -77
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.js +1 -1
- package/dist/features/about/components/DeveloperView.js +7 -2
- package/dist/features/about/components/PolicyPageView.js +9 -1
- package/dist/features/about/components/PublicProfileView.js +23 -5
- package/dist/features/account/components/AddressForm.js +1 -1
- package/dist/features/account/components/NotificationBell.js +4 -3
- package/dist/features/account/components/NotificationPreferencesPanel.js +1 -1
- package/dist/features/account/components/UserOffersPanel.js +1 -1
- package/dist/features/admin/components/AdminFulfillmentView.js +5 -2
- package/dist/features/admin/components/AdminMediaView.js +3 -3
- package/dist/features/analytics/types.d.ts +1 -1
- package/dist/features/analytics/types.js +13 -0
- package/dist/features/auctions/components/AuctionDetailPageView.js +6 -2
- package/dist/features/auctions/components/MarketplaceAuctionCard.js +3 -1
- package/dist/features/auth/actions/profile-actions.js +4 -3
- package/dist/features/auth/components/ForgotPasswordView.js +1 -1
- package/dist/features/auth/components/LoginForm.js +1 -1
- package/dist/features/auth/components/RegisterForm.js +1 -1
- package/dist/features/auth/components/ResetPasswordView.js +1 -1
- package/dist/features/auth/hooks/useAuth.js +3 -0
- package/dist/features/blog/repository/blog.repository.js +3 -1
- package/dist/features/cart/actions/cart-actions.js +13 -4
- package/dist/features/cart/utils/pending-ops.js +1 -1
- package/dist/features/categories/components/BrandDetailPageView.js +28 -12
- package/dist/features/categories/components/CategoryDetailPageView.js +37 -20
- package/dist/features/categories/repository/categories.repository.js +3 -1
- package/dist/features/checkout/actions/checkout-value-otp-actions.js +10 -1
- package/dist/features/classified/components/ClassifiedContactSellerPanel.js +1 -1
- package/dist/features/classified/components/ClassifiedDetailPageView.js +10 -2
- package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +4 -1
- package/dist/features/events/components/EventBanner.js +5 -2
- package/dist/features/events/components/EventOfferCard.js +3 -1
- package/dist/features/events/components/EventPollWidget.js +1 -1
- package/dist/features/events/components/SpinWheelView.js +3 -2
- package/dist/features/grouped/components/GroupedListingEditorView.js +2 -1
- package/dist/features/history/hooks/useHistory.js +29 -27
- package/dist/features/homepage/components/AdSlot.js +3 -1
- package/dist/features/homepage/components/GoogleReviewsSection.js +8 -2
- package/dist/features/homepage/components/MarketplaceHomepageView.js +34 -10
- package/dist/features/homepage/components/SocialFeedSection.js +4 -1
- package/dist/features/homepage/lib/live-stats.js +8 -1
- package/dist/features/homepage/repository/carousel.repository.js +2 -1
- package/dist/features/live/components/LiveItemDetailPageView.js +4 -1
- package/dist/features/media/finalize.js +3 -1
- package/dist/features/media/modals/ImageEditor.js +1 -1
- package/dist/features/media/modals/VideoThumbnailSelector.js +5 -2
- package/dist/features/media/modals/VideoTrimModal.js +5 -2
- package/dist/features/media/upload/MediaUploadField.js +2 -2
- package/dist/features/messages/hooks/useConversation.js +18 -4
- package/dist/features/messages/hooks/useConversations.js +25 -8
- package/dist/features/orders/components/RefundRequestView.js +5 -2
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -1
- package/dist/features/pre-orders/components/PreOrderActionsClient.js +1 -1
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +4 -1
- package/dist/features/products/components/PrizeDrawDetailPageView.js +4 -1
- package/dist/features/products/components/ProductDetailPageView.js +8 -2
- package/dist/features/products/components/ShareButton.js +3 -1
- package/dist/features/products/repository/loadProductFeatures.js +10 -2
- package/dist/features/products/repository/products.repository.js +2 -1
- package/dist/features/promotions/components/CouponCard.js +3 -1
- package/dist/features/reviews/actions/review-actions.js +8 -1
- package/dist/features/reviews/components/ReviewDetailPageView.js +13 -3
- package/dist/features/reviews/components/ReviewDetailShell.js +3 -1
- package/dist/features/scams/actions/scam-actions.d.ts +9 -1
- package/dist/features/scams/actions/scam-actions.js +52 -12
- package/dist/features/scams/components/SellerTrustBadge.d.ts +1 -1
- package/dist/features/scams/components/SellerTrustBadge.js +13 -0
- package/dist/features/scams/repository/scammer.repository.js +2 -1
- package/dist/features/seller/actions/offer-actions.js +6 -1
- package/dist/features/seller/components/FulfillmentView.js +5 -2
- package/dist/features/seller/components/PhysicalLocationModal.js +1 -1
- package/dist/features/seller/components/PrintCenterView.js +3 -1
- package/dist/features/seller/components/SellerBidsView.js +1 -1
- package/dist/features/seller/components/SellerCouponsView.js +1 -1
- package/dist/features/seller/components/SellerGoogleReviewsView.js +2 -2
- package/dist/features/seller/components/SellerOrdersView.js +3 -3
- package/dist/features/seller/components/SellerPayoutMethodsView.js +2 -2
- package/dist/features/seller/components/SellerProductShell.d.ts +5 -0
- package/dist/features/seller/components/SellerProductShell.js +15 -5
- package/dist/features/seller/components/SellerProductsView.js +24 -10
- package/dist/features/seller/components/SellerReviewsView.js +47 -16
- package/dist/features/seller/components/SellerShippingConfigsView.js +5 -4
- package/dist/features/seller/components/SellerStoreCategoriesView.js +3 -2
- package/dist/features/stores/components/StoreDetailLayoutView.d.ts +1 -1
- package/dist/features/stores/components/StoreDetailLayoutView.js +12 -3
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +16 -1
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +3 -2
- package/dist/features/wishlist/hooks/useWishlistToggle.js +8 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -2
- package/dist/next/api/routeHandler.js +3 -1
- package/dist/providers/auth-firebase/session.js +3 -1
- package/dist/providers/db-firebase/admin-app-lite.js +8 -8
- package/dist/providers/db-firebase/admin.js +12 -12
- package/dist/providers/db-firebase/browser-stub.d.ts +1 -11
- package/dist/providers/db-firebase/browser-stub.js +6 -11
- package/dist/providers/db-firebase/index.d.ts +0 -1
- package/dist/providers/db-firebase/index.js +0 -1
- package/dist/providers/db-firebase/rtdb-paths.d.ts +15 -6
- package/dist/providers/db-firebase/rtdb-paths.js +15 -6
- package/dist/providers/db-firebase/rtdb-url.d.ts +27 -0
- package/dist/providers/db-firebase/rtdb-url.js +50 -0
- package/dist/providers/firebase-client/realtime.d.ts +20 -0
- package/dist/providers/firebase-client/realtime.js +34 -1
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +1 -1
- package/dist/react/hooks/use-action-dispatch.js +8 -2
- package/dist/react/hooks/useFormStatePreservation.js +3 -1
- package/dist/react/hooks/useRealtimeEvent.js +20 -9
- package/dist/ui/components/toolbar-collapse.js +1 -2
- package/package.json +1 -1
|
@@ -27,7 +27,6 @@ export { firebaseFieldOps } from "./field-ops";
|
|
|
27
27
|
export { FirebaseRepository } from "./base";
|
|
28
28
|
export { BaseRepository } from "./base.repository";
|
|
29
29
|
export { FirebaseSieveRepository } from "./sieve";
|
|
30
|
-
export { FirebaseRealtimeRepository } from "./realtime";
|
|
31
30
|
// Re-export RTDB paths
|
|
32
31
|
export { RTDB_PATHS } from "./rtdb-paths";
|
|
33
32
|
import { FirebaseRepository as _FirebaseRepository } from "./base";
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared Realtime Database path constants for auth/payment/bulk event channels.
|
|
2
|
+
* Shared Realtime Database path constants for the auth/payment/bulk event channels.
|
|
3
|
+
*
|
|
4
|
+
* 🛑 Only paths that are actually referenced through this constant belong here.
|
|
5
|
+
* Five keys were removed on 2026-08-29 — `PRESENCE`, `CHAT`, `NOTIFICATIONS`,
|
|
6
|
+
* `LIVE_UPDATES`, `SEED_EVENTS` — every one with **zero consumers**, because
|
|
7
|
+
* each live call site hardcodes its path string instead. A constant nobody uses
|
|
8
|
+
* is not a source of truth; it is a second place for the value to be wrong, and
|
|
9
|
+
* three of those five named RTDB nodes that no longer exist at all.
|
|
10
|
+
*
|
|
11
|
+
* If you add a key, migrate the call sites to it in the same change.
|
|
12
|
+
*
|
|
13
|
+
* This module has NO imports on purpose — that is what lets `browser-stub.ts`
|
|
14
|
+
* re-export it rather than keep the byte-for-byte duplicate it used to carry.
|
|
15
|
+
* The stub is the `browser` resolution target, so that duplicate meant client
|
|
16
|
+
* code and server code were reading two independently-maintained copies.
|
|
3
17
|
*/
|
|
4
18
|
export declare const RTDB_PATHS: {
|
|
5
|
-
readonly PRESENCE: "presence";
|
|
6
|
-
readonly CHAT: "chat";
|
|
7
|
-
readonly NOTIFICATIONS: "notifications";
|
|
8
|
-
readonly LIVE_UPDATES: "live_updates";
|
|
9
19
|
readonly AUTH_EVENTS: "auth_events";
|
|
10
20
|
readonly PAYMENT_EVENTS: "payment_events";
|
|
11
21
|
readonly BULK_EVENTS: "bulk_events";
|
|
12
|
-
readonly SEED_EVENTS: "seed_events";
|
|
13
22
|
};
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared Realtime Database path constants for auth/payment/bulk event channels.
|
|
2
|
+
* Shared Realtime Database path constants for the auth/payment/bulk event channels.
|
|
3
|
+
*
|
|
4
|
+
* 🛑 Only paths that are actually referenced through this constant belong here.
|
|
5
|
+
* Five keys were removed on 2026-08-29 — `PRESENCE`, `CHAT`, `NOTIFICATIONS`,
|
|
6
|
+
* `LIVE_UPDATES`, `SEED_EVENTS` — every one with **zero consumers**, because
|
|
7
|
+
* each live call site hardcodes its path string instead. A constant nobody uses
|
|
8
|
+
* is not a source of truth; it is a second place for the value to be wrong, and
|
|
9
|
+
* three of those five named RTDB nodes that no longer exist at all.
|
|
10
|
+
*
|
|
11
|
+
* If you add a key, migrate the call sites to it in the same change.
|
|
12
|
+
*
|
|
13
|
+
* This module has NO imports on purpose — that is what lets `browser-stub.ts`
|
|
14
|
+
* re-export it rather than keep the byte-for-byte duplicate it used to carry.
|
|
15
|
+
* The stub is the `browser` resolution target, so that duplicate meant client
|
|
16
|
+
* code and server code were reading two independently-maintained copies.
|
|
3
17
|
*/
|
|
4
18
|
export const RTDB_PATHS = {
|
|
5
|
-
PRESENCE: "presence",
|
|
6
|
-
CHAT: "chat",
|
|
7
|
-
NOTIFICATIONS: "notifications",
|
|
8
|
-
LIVE_UPDATES: "live_updates",
|
|
9
19
|
AUTH_EVENTS: "auth_events",
|
|
10
20
|
PAYMENT_EVENTS: "payment_events",
|
|
11
21
|
BULK_EVENTS: "bulk_events",
|
|
12
|
-
SEED_EVENTS: "seed_events",
|
|
13
22
|
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the Realtime Database URL from the environment.
|
|
3
|
+
*
|
|
4
|
+
* Its own module, with **no imports**, because both `admin.ts` and
|
|
5
|
+
* `admin-app-lite.ts` need it and `admin-app-lite` exists precisely to avoid
|
|
6
|
+
* pulling in the full admin module. They previously carried byte-for-byte
|
|
7
|
+
* duplicate resolution logic, which meant the bug below existed twice.
|
|
8
|
+
*
|
|
9
|
+
* 🛑 Returns `undefined` rather than guessing.
|
|
10
|
+
*
|
|
11
|
+
* All six copies used to fall back to `https://${projectId}-default-rtdb.firebaseio.com`.
|
|
12
|
+
* That is the **us-central1** URL shape; this project's instance lives at
|
|
13
|
+
* `…-default-rtdb.asia-southeast1.firebasedatabase.app`. So whenever the env var
|
|
14
|
+
* was missing, every server RTDB write silently targeted a database that does
|
|
15
|
+
* not exist — and since all nine RTDB writers wrap their call in a swallowing
|
|
16
|
+
* try/catch, messaging, bids, job status and payment signalling would every one
|
|
17
|
+
* of them degrade to "nothing ever updates", with clean warn-level logs.
|
|
18
|
+
*
|
|
19
|
+
* A guessed URL cannot be correct for a non-default region, so guessing buys
|
|
20
|
+
* nothing and costs the ability to notice. With no `databaseURL`,
|
|
21
|
+
* `getAdminRealtimeDb()` fails loudly at first use instead, which is the
|
|
22
|
+
* outcome you want.
|
|
23
|
+
*
|
|
24
|
+
* `||` not `??`: an env var that is present but empty must fall through, and
|
|
25
|
+
* `??` only falls through on null/undefined.
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveRtdbUrl(): string | undefined;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the Realtime Database URL from the environment.
|
|
3
|
+
*
|
|
4
|
+
* Its own module, with **no imports**, because both `admin.ts` and
|
|
5
|
+
* `admin-app-lite.ts` need it and `admin-app-lite` exists precisely to avoid
|
|
6
|
+
* pulling in the full admin module. They previously carried byte-for-byte
|
|
7
|
+
* duplicate resolution logic, which meant the bug below existed twice.
|
|
8
|
+
*
|
|
9
|
+
* 🛑 Returns `undefined` rather than guessing.
|
|
10
|
+
*
|
|
11
|
+
* All six copies used to fall back to `https://${projectId}-default-rtdb.firebaseio.com`.
|
|
12
|
+
* That is the **us-central1** URL shape; this project's instance lives at
|
|
13
|
+
* `…-default-rtdb.asia-southeast1.firebasedatabase.app`. So whenever the env var
|
|
14
|
+
* was missing, every server RTDB write silently targeted a database that does
|
|
15
|
+
* not exist — and since all nine RTDB writers wrap their call in a swallowing
|
|
16
|
+
* try/catch, messaging, bids, job status and payment signalling would every one
|
|
17
|
+
* of them degrade to "nothing ever updates", with clean warn-level logs.
|
|
18
|
+
*
|
|
19
|
+
* A guessed URL cannot be correct for a non-default region, so guessing buys
|
|
20
|
+
* nothing and costs the ability to notice. With no `databaseURL`,
|
|
21
|
+
* `getAdminRealtimeDb()` fails loudly at first use instead, which is the
|
|
22
|
+
* outcome you want.
|
|
23
|
+
*
|
|
24
|
+
* `||` not `??`: an env var that is present but empty must fall through, and
|
|
25
|
+
* `??` only falls through on null/undefined.
|
|
26
|
+
*/
|
|
27
|
+
export function resolveRtdbUrl() {
|
|
28
|
+
// 🛑 Emulator first.
|
|
29
|
+
//
|
|
30
|
+
// `firebase.json` has declared a database emulator on port 9000 for a long
|
|
31
|
+
// time and NOTHING ever connected to it. Client writes were shielded only
|
|
32
|
+
// because every rule is `.write: false`; server writes use the Admin SDK and
|
|
33
|
+
// bypass rules entirely — so `npm run dev` wrote to PRODUCTION. A local bid
|
|
34
|
+
// overwrote the prod `auction-bids` node that live auction pages subscribe to,
|
|
35
|
+
// and a local message send wrote prod `chats/**`.
|
|
36
|
+
//
|
|
37
|
+
// `FIREBASE_DATABASE_EMULATOR_HOST` is the standard variable the Firebase CLI
|
|
38
|
+
// exports for exactly this, so honouring it costs one branch and makes
|
|
39
|
+
// `firebase emulators:start` do what everyone already assumed it did.
|
|
40
|
+
const emulatorHost = process.env.FIREBASE_DATABASE_EMULATOR_HOST?.trim();
|
|
41
|
+
if (emulatorHost) {
|
|
42
|
+
const ns = process.env.FIREBASE_ADMIN_PROJECT_ID?.trim() ||
|
|
43
|
+
process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID?.trim() ||
|
|
44
|
+
"default";
|
|
45
|
+
return `http://${emulatorHost}?ns=${ns}`;
|
|
46
|
+
}
|
|
47
|
+
return (process.env.FIREBASE_ADMIN_DATABASE_URL?.trim() ||
|
|
48
|
+
process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL?.trim() ||
|
|
49
|
+
undefined);
|
|
50
|
+
}
|
|
@@ -11,7 +11,27 @@ export interface FirebaseClientRealtimeConfig {
|
|
|
11
11
|
export declare class FirebaseClientRealtimeProvider implements IClientRealtimeProvider {
|
|
12
12
|
private app;
|
|
13
13
|
private db;
|
|
14
|
+
private readonly config;
|
|
15
|
+
private readonly appName;
|
|
16
|
+
private readonly scoped;
|
|
14
17
|
constructor(config: FirebaseClientRealtimeConfig);
|
|
18
|
+
/**
|
|
19
|
+
* A provider bound to its OWN Firebase app for this claim scope.
|
|
20
|
+
*
|
|
21
|
+
* 🛑 This is what makes concurrent channels safe. Firebase Auth state is
|
|
22
|
+
* per-app, so with one shared app every `signInWithToken` REPLACED the claims
|
|
23
|
+
* of every other live subscriber: a bulk job signing in with a
|
|
24
|
+
* `{ bulkJobId }`-only token instantly stripped `conversationIds` from an open
|
|
25
|
+
* messages subscription, and vice versa. Whoever signed in last won.
|
|
26
|
+
*
|
|
27
|
+
* Reference-counting fixed only the sign-OUT half of that (a finishing job no
|
|
28
|
+
* longer ends someone else's session). Sign-IN needed separate apps, because
|
|
29
|
+
* there is nothing to coordinate — two channels legitimately need two
|
|
30
|
+
* different claim sets at the same time.
|
|
31
|
+
*
|
|
32
|
+
* Apps are cached per scope and reused, so a remount does not leak one.
|
|
33
|
+
*/
|
|
34
|
+
forScope(scope: string): IClientRealtimeProvider;
|
|
15
35
|
signInWithToken(token: string): Promise<void>;
|
|
16
36
|
signOut(): Promise<void>;
|
|
17
37
|
subscribe(path: string, onData: (snapshot: RealtimeSnapshot) => void, onError?: (error: Error) => void): Unsubscribe;
|
|
@@ -7,12 +7,44 @@ import { getAuth, signInWithCustomToken, signOut } from "firebase/auth";
|
|
|
7
7
|
*/
|
|
8
8
|
export class FirebaseClientRealtimeProvider {
|
|
9
9
|
constructor(config) {
|
|
10
|
+
this.scoped = new Map();
|
|
10
11
|
const appName = config.appName ?? "appkit-realtime";
|
|
12
|
+
this.config = config;
|
|
13
|
+
this.appName = appName;
|
|
11
14
|
this.app =
|
|
12
15
|
getApps().find((a) => a.name === appName) ??
|
|
13
16
|
initializeApp(config.firebaseConfig, appName);
|
|
14
17
|
this.db = getDatabase(this.app);
|
|
15
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* A provider bound to its OWN Firebase app for this claim scope.
|
|
21
|
+
*
|
|
22
|
+
* 🛑 This is what makes concurrent channels safe. Firebase Auth state is
|
|
23
|
+
* per-app, so with one shared app every `signInWithToken` REPLACED the claims
|
|
24
|
+
* of every other live subscriber: a bulk job signing in with a
|
|
25
|
+
* `{ bulkJobId }`-only token instantly stripped `conversationIds` from an open
|
|
26
|
+
* messages subscription, and vice versa. Whoever signed in last won.
|
|
27
|
+
*
|
|
28
|
+
* Reference-counting fixed only the sign-OUT half of that (a finishing job no
|
|
29
|
+
* longer ends someone else's session). Sign-IN needed separate apps, because
|
|
30
|
+
* there is nothing to coordinate — two channels legitimately need two
|
|
31
|
+
* different claim sets at the same time.
|
|
32
|
+
*
|
|
33
|
+
* Apps are cached per scope and reused, so a remount does not leak one.
|
|
34
|
+
*/
|
|
35
|
+
forScope(scope) {
|
|
36
|
+
const existing = this.scoped.get(scope);
|
|
37
|
+
if (existing)
|
|
38
|
+
return existing;
|
|
39
|
+
// Firebase app names are freeform, but keep them predictable in devtools.
|
|
40
|
+
const safe = scope.replace(/[^a-zA-Z0-9_-]/g, "-");
|
|
41
|
+
const child = new FirebaseClientRealtimeProvider({
|
|
42
|
+
...this.config,
|
|
43
|
+
appName: `${this.appName}-${safe}`,
|
|
44
|
+
});
|
|
45
|
+
this.scoped.set(scope, child);
|
|
46
|
+
return child;
|
|
47
|
+
}
|
|
16
48
|
async signInWithToken(token) {
|
|
17
49
|
await signInWithCustomToken(getAuth(this.app), token);
|
|
18
50
|
}
|
|
@@ -21,8 +53,9 @@ export class FirebaseClientRealtimeProvider {
|
|
|
21
53
|
await signOut(getAuth(this.app));
|
|
22
54
|
}
|
|
23
55
|
catch (_err) {
|
|
56
|
+
// A secondary Firebase app that was never signed in has nothing to sign
|
|
57
|
+
// out of; the caller's goal (no credential on this app) already holds.
|
|
24
58
|
void normalizeError(_err);
|
|
25
|
-
// no-op — signing out of a secondary app can fail harmlessly
|
|
26
59
|
}
|
|
27
60
|
}
|
|
28
61
|
subscribe(path, onData, onError) {
|
package/dist/providers.d.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
export { registerProviders, getProviders } from "./contracts/index";
|
|
13
13
|
export { configureMarketDefaults } from "./core/baseline-resolver";
|
|
14
14
|
export { firebaseAuthProvider, firebaseSessionProvider, createSessionCookieFromToken, createSessionCookie, verifyIdToken, verifySessionCookie, createMiddlewareAuthChain, requireAuth, requireRole, requireAuthUser, requireRoleUser, getUserFromRequest, requireAuthFromRequest, requireRoleFromRequest, revokeUserTokens, } from "./providers/auth-firebase/index";
|
|
15
|
-
export { firebaseDbProvider, getAdminApp, getAdminAuth, getAdminDb, getAdminStorage, getAdminRealtimeDb, _resetAdminSingletons, FirebaseRepository, BaseRepository, FirebaseSieveRepository,
|
|
15
|
+
export { firebaseDbProvider, getAdminApp, getAdminAuth, getAdminDb, getAdminStorage, getAdminRealtimeDb, _resetAdminSingletons, FirebaseRepository, BaseRepository, FirebaseSieveRepository, RTDB_PATHS, firebaseFieldOps, removeUndefined, prepareForFirestore, deserializeTimestamps, } from "./providers/db-firebase/index";
|
|
16
16
|
export type { DocumentSnapshot } from "firebase-admin/firestore";
|
|
17
17
|
export { firebaseStorageProvider } from "./providers/storage-firebase/index";
|
|
18
18
|
export { createResendProvider } from "./providers/email-resend/index";
|
package/dist/providers.js
CHANGED
|
@@ -15,7 +15,7 @@ export { configureMarketDefaults } from "./core/baseline-resolver";
|
|
|
15
15
|
// Auth (Firebase Admin)
|
|
16
16
|
export { firebaseAuthProvider, firebaseSessionProvider, createSessionCookieFromToken, createSessionCookie, verifyIdToken, verifySessionCookie, createMiddlewareAuthChain, requireAuth, requireRole, requireAuthUser, requireRoleUser, getUserFromRequest, requireAuthFromRequest, requireRoleFromRequest, revokeUserTokens, } from "./providers/auth-firebase/index";
|
|
17
17
|
// Database (Firebase Firestore / RTDB)
|
|
18
|
-
export { firebaseDbProvider, getAdminApp, getAdminAuth, getAdminDb, getAdminStorage, getAdminRealtimeDb, _resetAdminSingletons, FirebaseRepository, BaseRepository, FirebaseSieveRepository,
|
|
18
|
+
export { firebaseDbProvider, getAdminApp, getAdminAuth, getAdminDb, getAdminStorage, getAdminRealtimeDb, _resetAdminSingletons, FirebaseRepository, BaseRepository, FirebaseSieveRepository, RTDB_PATHS, firebaseFieldOps, removeUndefined, prepareForFirestore, deserializeTimestamps, } from "./providers/db-firebase/index";
|
|
19
19
|
// Storage (Firebase Admin)
|
|
20
20
|
export { firebaseStorageProvider } from "./providers/storage-firebase/index";
|
|
21
21
|
// Email (Resend)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { normalizeError } from "../../errors/normalize";
|
|
3
|
+
import { toUserMessage } from "../../errors/error-display-map";
|
|
3
4
|
import { useCallback } from "react";
|
|
4
5
|
import { useRouter } from "next/navigation";
|
|
5
6
|
import { useToast } from "../../ui";
|
|
@@ -33,8 +34,13 @@ export function useActionDispatch(options) {
|
|
|
33
34
|
showToast(action.successMessage, "success");
|
|
34
35
|
}
|
|
35
36
|
catch (err) {
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
const e = normalizeError(err);
|
|
38
|
+
// The action's own errorMessage still wins — it is the most
|
|
39
|
+
// specific authored copy available. Only the tail changed: it now
|
|
40
|
+
// resolves the error's CODE and ends in a constant, instead of
|
|
41
|
+
// ending in the thrown value's developer-facing text.
|
|
42
|
+
showToast(action.errorMessage ??
|
|
43
|
+
toUserMessage(e.code, undefined, { fallback: "Something went wrong." }), "error");
|
|
38
44
|
}
|
|
39
45
|
break;
|
|
40
46
|
}
|
|
@@ -28,10 +28,12 @@ export function useFormStatePreservation({ values, onRestore, stripFields = [],
|
|
|
28
28
|
try {
|
|
29
29
|
const decoded = JSON.parse(atob(encoded));
|
|
30
30
|
onRestore(decoded);
|
|
31
|
+
// The payload is user-supplied via the URL, so corrupt input is expected
|
|
32
|
+
// rather than exceptional. Discarding it restores the empty form, which is
|
|
33
|
+
// the same state as arriving without one.
|
|
31
34
|
}
|
|
32
35
|
catch (_err) {
|
|
33
36
|
void normalizeError(_err);
|
|
34
|
-
// ignore corrupt payloads
|
|
35
37
|
}
|
|
36
38
|
}, [paramName, onRestore]);
|
|
37
39
|
useEffect(() => {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { normalizeError } from "../../errors/normalize";
|
|
3
3
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
4
4
|
import { getClientRealtimeProvider, } from "../../contracts/client-realtime";
|
|
5
|
-
import {
|
|
5
|
+
import { acquireRealtimeSession } from "../../contracts/realtime-session";
|
|
6
6
|
import { RealtimeEventType, RealtimeEventStatus, RTDBPayloadStatus, } from "./realtime-event-constants";
|
|
7
7
|
// Re-exported for existing client import paths — the canonical definitions
|
|
8
8
|
// now live in realtime-event-constants.ts (a plain, non-"use client" module)
|
|
@@ -32,8 +32,10 @@ export function useRealtimeEvent(config) {
|
|
|
32
32
|
const unsubscribeRef = useRef(null);
|
|
33
33
|
const timeoutRef = useRef(null);
|
|
34
34
|
const eventIdRef = useRef(null);
|
|
35
|
-
/** Lease on
|
|
35
|
+
/** Lease on this event's realtime session — see cleanup() for why. */
|
|
36
36
|
const releaseSessionRef = useRef(null);
|
|
37
|
+
/** Provider bound to THIS scope's app; subscribe through it, not the global. */
|
|
38
|
+
const scopedProviderRef = useRef(null);
|
|
37
39
|
const cleanup = useCallback(() => {
|
|
38
40
|
if (unsubscribeRef.current) {
|
|
39
41
|
unsubscribeRef.current();
|
|
@@ -57,6 +59,7 @@ export function useRealtimeEvent(config) {
|
|
|
57
59
|
// end one, and only once the last consumer has let go.
|
|
58
60
|
releaseSessionRef.current?.();
|
|
59
61
|
releaseSessionRef.current = null;
|
|
62
|
+
scopedProviderRef.current = null;
|
|
60
63
|
}, []);
|
|
61
64
|
const subscribe = useCallback((eventId, customToken) => {
|
|
62
65
|
const { type, rtdbPath, realtimeProvider, timeoutMs = DEFAULT_TIMEOUT_MS, extractData, messages, onLogError, } = configRef.current;
|
|
@@ -72,12 +75,20 @@ export function useRealtimeEvent(config) {
|
|
|
72
75
|
setData(null);
|
|
73
76
|
(async () => {
|
|
74
77
|
try {
|
|
75
|
-
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
|
|
78
|
+
// Sign in through the session, which gives this event its OWN Firebase
|
|
79
|
+
// app for the `event:*` scope.
|
|
80
|
+
//
|
|
81
|
+
// That isolation is the fix. Firebase Auth state is per-app, so while
|
|
82
|
+
// every channel shared one app, this hook's `{ bulkJobId }`-only token
|
|
83
|
+
// replaced the claims of any live messages or chat subscription the
|
|
84
|
+
// moment a job started — and theirs replaced this one's in return.
|
|
85
|
+
// Ref-counting had only stopped a FINISHING channel from signing the
|
|
86
|
+
// others out; it could not stop a STARTING one from signing itself in
|
|
87
|
+
// over them, because there was nothing to coordinate: two channels
|
|
88
|
+
// legitimately need two different claim sets at the same time.
|
|
89
|
+
const lease = await acquireRealtimeSession(`event:${type}:${eventId}`, () => Promise.resolve({ customToken, expiresAt: Date.now() + 3600000 }));
|
|
90
|
+
releaseSessionRef.current = lease.release;
|
|
91
|
+
scopedProviderRef.current = lease.provider;
|
|
81
92
|
}
|
|
82
93
|
catch (authErr) {
|
|
83
94
|
void normalizeError(authErr);
|
|
@@ -102,7 +113,7 @@ export function useRealtimeEvent(config) {
|
|
|
102
113
|
return;
|
|
103
114
|
}
|
|
104
115
|
setStatus(RealtimeEventStatus.PENDING);
|
|
105
|
-
unsubscribeRef.current = provider.subscribe(`${rtdbPath}/${eventId}`, (snapshot) => {
|
|
116
|
+
unsubscribeRef.current = (scopedProviderRef.current ?? provider).subscribe(`${rtdbPath}/${eventId}`, (snapshot) => {
|
|
106
117
|
if (!snapshot.exists())
|
|
107
118
|
return;
|
|
108
119
|
const raw = snapshot.val();
|
|
@@ -48,11 +48,10 @@ export function readCollapsedPref(id) {
|
|
|
48
48
|
export function writeCollapsedPref(id, value) {
|
|
49
49
|
try {
|
|
50
50
|
window.sessionStorage.setItem(STORAGE_PREFIX + id, value ? "1" : "0");
|
|
51
|
+
// sessionStorage blocked (private browsing). Collapsing still works this render, it just will not survive a re-mount.
|
|
51
52
|
}
|
|
52
53
|
catch (_err) {
|
|
53
54
|
void normalizeError(_err);
|
|
54
|
-
// sessionStorage unavailable (private browsing, etc.) — collapsing still
|
|
55
|
-
// works for the current render, it just doesn't survive a re-mount.
|
|
56
55
|
}
|
|
57
56
|
}
|
|
58
57
|
export function defaultCollapsedForViewport() {
|