@mohasinac/appkit 4.30.17 → 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/_internal/server/jobs/core/testerSandboxRefresh.js +27 -2
- 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
|
@@ -3728,7 +3728,22 @@ const rawTesterChecklistItems = [
|
|
|
3728
3728
|
href: "/admin/site",
|
|
3729
3729
|
},
|
|
3730
3730
|
{ key: "admin-dashboard-widgets", label: "Admin dashboard widgets show accurate data", href: "/admin/dashboard" },
|
|
3731
|
-
{
|
|
3731
|
+
{
|
|
3732
|
+
key: "analytics-admin",
|
|
3733
|
+
label: "Admin analytics dashboard shows accurate data",
|
|
3734
|
+
href: "/admin/analytics",
|
|
3735
|
+
},
|
|
3736
|
+
{
|
|
3737
|
+
key: "analytics-traffic-card-has-numbers",
|
|
3738
|
+
label: "The \"Traffic today\" card shows a real number, and it MATCHES the Page Views tab beside it",
|
|
3739
|
+
description: "Fixed 2026-08-29. BEFORE: the card showed an em-dash forever — never even a 0. It subscribed to two Realtime Database paths written by a presence hook whose writes had been denied since the day it shipped (the RTDB rules are backend-write-only), and both subscriptions passed no error callback, so a permission denial fired nothing at all and the tiles simply never left their initial state. AFTER: the card reads the same Firestore pageview counter that backs the Page Views tab in the same tab strip, so the two figures must agree — compare them. If the card errors it now SAYS so instead of showing a dash.",
|
|
3740
|
+
},
|
|
3741
|
+
{
|
|
3742
|
+
key: "analytics-no-permission-denied",
|
|
3743
|
+
label: "Opening /admin/analytics logs no PERMISSION_DENIED in the browser console",
|
|
3744
|
+
description: "Fixed 2026-08-29. BEFORE: every admin page load produced two Firebase permission_denied warnings from the dead presence/analytics subscriptions. AFTER: none — those subscriptions are gone entirely. Open devtools, hard-reload the page, and confirm the console is clean of Firebase permission errors.",
|
|
3745
|
+
href: "/admin/analytics",
|
|
3746
|
+
},
|
|
3732
3747
|
{
|
|
3733
3748
|
key: "pageviews-report-listing-standard",
|
|
3734
3749
|
label: "The \"Page Views\" tab in Admin Analytics is a full standard listing — a search box (by entity id or URL), an entity-type filter drawer, a \"Most views\"/\"Fewest views\" sort dropdown, and real pagination — not a bare unsearchable table",
|
|
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useApiMutation, useBulkEvent, RTDB_PATHS } from "@mohasinac/appkit/client";
|
|
4
4
|
import React from "react";
|
|
5
5
|
import { normalizeError } from "../../../errors/normalize";
|
|
6
|
+
import { toUserMessage } from "../../../errors/error-display-map";
|
|
6
7
|
const __P = {
|
|
7
8
|
p3: "p-[var(--appkit-space-3)]",
|
|
8
9
|
};
|
|
@@ -156,9 +157,9 @@ export function SellerWhatsAppSettingsView({ hasCapability }) {
|
|
|
156
157
|
}
|
|
157
158
|
}
|
|
158
159
|
catch (err) {
|
|
159
|
-
|
|
160
|
+
const e = normalizeError(err);
|
|
160
161
|
setImporting(false);
|
|
161
|
-
showToast(
|
|
162
|
+
showToast(toUserMessage(e.code, undefined, { fallback: "Failed to start catalog import." }), "error");
|
|
162
163
|
}
|
|
163
164
|
};
|
|
164
165
|
React.useEffect(() => {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useCallback, useState } from "react";
|
|
3
3
|
import { useToast } from "../../../ui";
|
|
4
|
+
import { normalizeError } from "../../../errors/normalize";
|
|
5
|
+
import { toUserMessage } from "../../../errors/error-display-map";
|
|
4
6
|
export function useWishlistToggle(productId, initial = false, actions) {
|
|
5
7
|
const [inWishlist, setInWishlist] = useState(initial);
|
|
6
8
|
const [isLoading, setIsLoading] = useState(false);
|
|
@@ -25,8 +27,13 @@ export function useWishlistToggle(productId, initial = false, actions) {
|
|
|
25
27
|
}
|
|
26
28
|
}
|
|
27
29
|
catch (err) {
|
|
30
|
+
const e = normalizeError(err);
|
|
28
31
|
setInWishlist(prev);
|
|
29
|
-
showToast(
|
|
32
|
+
showToast(toUserMessage(e.code, undefined, {
|
|
33
|
+
fallback: prev
|
|
34
|
+
? "Could not remove this item from your wishlist."
|
|
35
|
+
: "Could not add this item to your wishlist.",
|
|
36
|
+
}), "error");
|
|
30
37
|
throw err;
|
|
31
38
|
}
|
|
32
39
|
finally {
|
package/dist/index.d.ts
CHANGED
|
@@ -333,6 +333,7 @@ export { RazorpayUnreachableError } from "./errors/index";
|
|
|
333
333
|
export { mapToHttpError, HTTP_ERROR_CODES } from "./errors/index";
|
|
334
334
|
export type { MappedError, HttpErrorCode } from "./errors/index";
|
|
335
335
|
export { ERROR_DISPLAY_MAP, getErrorDisplay } from "./errors/index";
|
|
336
|
+
export { toUserMessage, GENERIC_USER_MESSAGE } from "./errors/index";
|
|
336
337
|
export type { ErrorDisplayEntry } from "./errors/index";
|
|
337
338
|
export { parseJsonBody } from "./next/api/parseJsonBody";
|
|
338
339
|
export { SEO_CONFIG } from "./seo/index";
|
|
@@ -849,7 +850,6 @@ export { requireRoleUser } from "./providers/auth-firebase/index";
|
|
|
849
850
|
export { revokeUserTokens } from "./providers/auth-firebase/index";
|
|
850
851
|
export { verifyIdToken } from "./providers/auth-firebase/index";
|
|
851
852
|
export { verifySessionCookie } from "./providers/auth-firebase/index";
|
|
852
|
-
export { FirebaseRealtimeRepository } from "./providers/db-firebase/index";
|
|
853
853
|
export { FirebaseRepository } from "./providers/db-firebase/index";
|
|
854
854
|
export { FirebaseSieveRepository } from "./providers/db-firebase/index";
|
|
855
855
|
export { RTDB_PATHS } from "./providers/db-firebase/index";
|
package/dist/index.js
CHANGED
|
@@ -568,6 +568,11 @@ export { mapToHttpError, HTTP_ERROR_CODES } from "./errors/index";
|
|
|
568
568
|
// [UTIL]-Pure utility — client-safe lookup of inline-field + i18n key per error code.
|
|
569
569
|
// ERROR_DISPLAY_MAP - CLAUDE.md Rule #9 source-of-truth for client error rendering.
|
|
570
570
|
export { ERROR_DISPLAY_MAP, getErrorDisplay } from "./errors/index";
|
|
571
|
+
// [UTIL]-Pure utility — the ONLY sanctioned way to turn a failure into user copy.
|
|
572
|
+
// toUserMessage - resolves a stable error code through errors.codes.*; terminates
|
|
573
|
+
// in a constant, so a thrown value's own .message can never reach a user
|
|
574
|
+
// (audit-raw-error-text).
|
|
575
|
+
export { toUserMessage, GENERIC_USER_MESSAGE } from "./errors/index";
|
|
571
576
|
// [SERVER-ONLY] parseJsonBody — strict JSON request-body parser for API routes.
|
|
572
577
|
// Replaces the silent `request.json().catch(() => ({}))` anti-pattern. Throws
|
|
573
578
|
// ValidationError on malformed JSON → routeHandler wrapper maps to 400.
|
|
@@ -1726,8 +1731,6 @@ export { verifyIdToken } from "./providers/auth-firebase/index";
|
|
|
1726
1731
|
export { verifySessionCookie } from "./providers/auth-firebase/index";
|
|
1727
1732
|
// ./providers/db-firebase/index
|
|
1728
1733
|
// [DB]-Database layer â€" uses firebase-admin or another server-side DB SDK; can only run in a trusted server environment.
|
|
1729
|
-
// FirebaseRealtimeRepository - Shared export for firebase realtime repository.
|
|
1730
|
-
export { FirebaseRealtimeRepository } from "./providers/db-firebase/index";
|
|
1731
1734
|
// [DB]-Database layer â€" uses firebase-admin or another server-side DB SDK; can only run in a trusted server environment.
|
|
1732
1735
|
// FirebaseRepository - Shared export for firebase repository.
|
|
1733
1736
|
export { FirebaseRepository } from "./providers/db-firebase/index";
|
|
@@ -182,10 +182,12 @@ export function createRouteHandler(options) {
|
|
|
182
182
|
else if (options.authOptional) {
|
|
183
183
|
try {
|
|
184
184
|
user = await verifySession(request);
|
|
185
|
+
// Deliberate: this is the OPTIONAL-auth path. A bad or absent session means
|
|
186
|
+
// anonymous, which is a legitimate outcome here — routes that require a
|
|
187
|
+
// user use `auth: true`, which throws instead.
|
|
185
188
|
}
|
|
186
189
|
catch (_err) {
|
|
187
190
|
void normalizeError(_err);
|
|
188
|
-
// anonymous
|
|
189
191
|
}
|
|
190
192
|
}
|
|
191
193
|
// -- Role check --------------------------------------------------------
|
|
@@ -71,8 +71,10 @@ export const firebaseSessionProvider = {
|
|
|
71
71
|
await getAdminAuthLite().revokeRefreshTokens(decoded.uid);
|
|
72
72
|
}
|
|
73
73
|
catch (_err) {
|
|
74
|
+
// Best-effort teardown: an expired/revoked/malformed cookie cannot be
|
|
75
|
+
// verified, and that is the state destroySession is trying to reach —
|
|
76
|
+
// reporting it would alarm on the success case.
|
|
74
77
|
void normalizeError(_err);
|
|
75
|
-
// Best-effort: if cookie is already invalid, treat as already destroyed
|
|
76
78
|
}
|
|
77
79
|
},
|
|
78
80
|
};
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { initializeApp, getApps, cert } from "firebase-admin/app";
|
|
2
|
+
// One resolver, not a second copy — this file duplicated admin.ts's URL
|
|
3
|
+
// logic byte-for-byte, so the region-wrong fallback bug existed twice.
|
|
4
|
+
// Imported from its own dependency-free module, NOT from ./admin, because the
|
|
5
|
+
// whole point of admin-app-lite is not pulling the full admin module in.
|
|
6
|
+
import { resolveRtdbUrl } from "./rtdb-url";
|
|
2
7
|
function nodePath() { return require("path"); }
|
|
3
8
|
function nodeFs() { return require("fs"); }
|
|
4
9
|
// process.cwd() is Node.js-only; access via module.require so the Edge
|
|
@@ -26,17 +31,14 @@ export function getAdminAppLite() {
|
|
|
26
31
|
let app;
|
|
27
32
|
if (nodeFs().existsSync(keyPath)) {
|
|
28
33
|
const sa = JSON.parse(nodeFs().readFileSync(keyPath, "utf8"));
|
|
29
|
-
const dbUrl =
|
|
30
|
-
`https://${sa.project_id}-default-rtdb.firebaseio.com`;
|
|
34
|
+
const dbUrl = resolveRtdbUrl();
|
|
31
35
|
app = initializeApp({ credential: cert(keyPath), databaseURL: dbUrl, ...(storageBucket && { storageBucket }) });
|
|
32
36
|
}
|
|
33
37
|
else if (process.env.FIREBASE_ADMIN_PROJECT_ID &&
|
|
34
38
|
process.env.FIREBASE_ADMIN_CLIENT_EMAIL &&
|
|
35
39
|
process.env.FIREBASE_ADMIN_PRIVATE_KEY) {
|
|
36
40
|
const projectId = process.env.FIREBASE_ADMIN_PROJECT_ID.trim();
|
|
37
|
-
const dbUrl =
|
|
38
|
-
process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL?.trim() ??
|
|
39
|
-
`https://${projectId}-default-rtdb.firebaseio.com`;
|
|
41
|
+
const dbUrl = resolveRtdbUrl();
|
|
40
42
|
app = initializeApp({
|
|
41
43
|
credential: cert({
|
|
42
44
|
projectId,
|
|
@@ -63,9 +65,7 @@ export function getAdminAppLite() {
|
|
|
63
65
|
: {};
|
|
64
66
|
const projectId = firebaseConfig.projectId ?? process.env.GCLOUD_PROJECT ?? "";
|
|
65
67
|
const dbUrl = firebaseConfig.databaseURL ??
|
|
66
|
-
|
|
67
|
-
process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL ??
|
|
68
|
-
(projectId ? `https://${projectId}-default-rtdb.firebaseio.com` : undefined);
|
|
68
|
+
resolveRtdbUrl();
|
|
69
69
|
app = initializeApp({
|
|
70
70
|
...(dbUrl && { databaseURL: dbUrl }),
|
|
71
71
|
...(storageBucket && { storageBucket }),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalizeError } from "../../errors/normalize";
|
|
2
|
+
import { resolveRtdbUrl } from "./rtdb-url";
|
|
2
3
|
function nodePath() { return require("path"); }
|
|
3
4
|
function nodeFs() { return require("fs"); }
|
|
4
5
|
// process.cwd() is Node.js-only; access via module.require so the Edge
|
|
@@ -69,17 +70,16 @@ export function getAdminApp() {
|
|
|
69
70
|
try {
|
|
70
71
|
if (nodeFs().existsSync(keyPath)) {
|
|
71
72
|
const sa = JSON.parse(nodeFs().readFileSync(keyPath, "utf8"));
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
// Was NEXT_PUBLIC-only here while the other two branches honoured
|
|
74
|
+
// FIREBASE_ADMIN_DATABASE_URL, so setting it had no effect on this path.
|
|
75
|
+
const dbUrl = resolveRtdbUrl();
|
|
74
76
|
app = initializeApp({ credential: cert(keyPath), databaseURL: dbUrl, ...(storageBucket && { storageBucket }) });
|
|
75
77
|
}
|
|
76
78
|
else if (process.env.FIREBASE_ADMIN_PROJECT_ID &&
|
|
77
79
|
process.env.FIREBASE_ADMIN_CLIENT_EMAIL &&
|
|
78
80
|
process.env.FIREBASE_ADMIN_PRIVATE_KEY) {
|
|
79
81
|
const projectId = process.env.FIREBASE_ADMIN_PROJECT_ID.trim();
|
|
80
|
-
const dbUrl =
|
|
81
|
-
process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL?.trim() ??
|
|
82
|
-
`https://${projectId}-default-rtdb.firebaseio.com`;
|
|
82
|
+
const dbUrl = resolveRtdbUrl();
|
|
83
83
|
app = initializeApp({
|
|
84
84
|
credential: cert({
|
|
85
85
|
projectId,
|
|
@@ -105,12 +105,9 @@ export function getAdminApp() {
|
|
|
105
105
|
? JSON.parse(process.env.FIREBASE_CONFIG)
|
|
106
106
|
: {};
|
|
107
107
|
const projectId = firebaseConfig.projectId ?? process.env.GCLOUD_PROJECT ?? "";
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
(projectId
|
|
112
|
-
? `https://${projectId}-default-rtdb.firebaseio.com`
|
|
113
|
-
: undefined);
|
|
108
|
+
// FIREBASE_CONFIG is injected by the Functions runtime and is
|
|
109
|
+
// authoritative there; env vars are the fallback for other runtimes.
|
|
110
|
+
const dbUrl = firebaseConfig.databaseURL || resolveRtdbUrl();
|
|
114
111
|
app = initializeApp({
|
|
115
112
|
...(dbUrl && { databaseURL: dbUrl }),
|
|
116
113
|
...(storageBucket && { storageBucket }),
|
|
@@ -153,8 +150,11 @@ export function getAdminDb() {
|
|
|
153
150
|
db.settings({ ignoreUndefinedProperties: true });
|
|
154
151
|
}
|
|
155
152
|
catch (_err) {
|
|
153
|
+
// Firestore rejects a second settings() call on an already-initialised
|
|
154
|
+
// instance, which is the only way this throws (HMR reload / duplicate
|
|
155
|
+
// module instance re-entering the memoised bootstrap). The instance is
|
|
156
|
+
// fully usable either way, so there is nothing degraded to report.
|
|
156
157
|
void normalizeError(_err);
|
|
157
|
-
// Already configured — safe to ignore.
|
|
158
158
|
}
|
|
159
159
|
set("__mohasinac_firebase_admin_db__", db);
|
|
160
160
|
return db;
|
|
@@ -22,17 +22,7 @@ export declare function prepareForFirestore(data: any): any;
|
|
|
22
22
|
export declare function deserializeTimestamps<T>(data: any): T;
|
|
23
23
|
export declare const FirebaseRepository: any;
|
|
24
24
|
export declare const FirebaseSieveRepository: any;
|
|
25
|
-
export declare const FirebaseRealtimeRepository: any;
|
|
26
25
|
export declare const firebaseDbProvider: any;
|
|
27
|
-
export
|
|
28
|
-
readonly PRESENCE: "presence";
|
|
29
|
-
readonly CHAT: "chat";
|
|
30
|
-
readonly NOTIFICATIONS: "notifications";
|
|
31
|
-
readonly LIVE_UPDATES: "live_updates";
|
|
32
|
-
readonly AUTH_EVENTS: "auth_events";
|
|
33
|
-
readonly PAYMENT_EVENTS: "payment_events";
|
|
34
|
-
readonly BULK_EVENTS: "bulk_events";
|
|
35
|
-
readonly SEED_EVENTS: "seed_events";
|
|
36
|
-
};
|
|
26
|
+
export { RTDB_PATHS } from "./rtdb-paths";
|
|
37
27
|
export { buildFirebaseClientConfig, normalizeFirebaseConfigValue } from './client-config';
|
|
38
28
|
export type { FirebaseClientConfig } from './client-config';
|
|
@@ -40,17 +40,12 @@ export function deserializeTimestamps(data) {
|
|
|
40
40
|
}
|
|
41
41
|
export const FirebaseRepository = null;
|
|
42
42
|
export const FirebaseSieveRepository = null;
|
|
43
|
-
export const FirebaseRealtimeRepository = null;
|
|
44
43
|
export const firebaseDbProvider = null;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
PAYMENT_EVENTS: "payment_events",
|
|
52
|
-
BULK_EVENTS: "bulk_events",
|
|
53
|
-
SEED_EVENTS: "seed_events",
|
|
54
|
-
};
|
|
44
|
+
// Re-exported, NOT re-declared. This file is the `browser` resolution target, so
|
|
45
|
+
// the byte-for-byte copy that used to live here meant client code and server
|
|
46
|
+
// code read two independently-maintained definitions of the same constant, with
|
|
47
|
+
// nothing guarding the drift. `rtdb-paths.ts` has no imports, so it is safe to
|
|
48
|
+
// pull into a browser bundle directly.
|
|
49
|
+
export { RTDB_PATHS } from "./rtdb-paths";
|
|
55
50
|
// Client config builder � safe in all environments
|
|
56
51
|
export { buildFirebaseClientConfig, normalizeFirebaseConfigValue } from './client-config';
|
|
@@ -20,7 +20,6 @@ export type { DocumentSnapshot } from "firebase-admin/firestore";
|
|
|
20
20
|
export { FirebaseRepository } from "./base";
|
|
21
21
|
export { BaseRepository } from "./base.repository";
|
|
22
22
|
export { FirebaseSieveRepository } from "./sieve";
|
|
23
|
-
export { FirebaseRealtimeRepository } from "./realtime";
|
|
24
23
|
export { RTDB_PATHS } from "./rtdb-paths";
|
|
25
24
|
import type { IDbProvider } from "../../contracts";
|
|
26
25
|
export declare const firebaseDbProvider: IDbProvider;
|
|
@@ -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(() => {
|