@mohasinac/appkit 4.41.0 → 4.41.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.
Files changed (166) hide show
  1. package/dist/features/shell/SectionForm.js +44 -5
  2. package/package.json +2 -2
  3. package/dist/_internal/server/features/classified/actions.d.ts +0 -12
  4. package/dist/_internal/server/features/classified/actions.js +0 -33
  5. package/dist/_internal/server/features/digital-code/actions.d.ts +0 -12
  6. package/dist/_internal/server/features/digital-code/actions.js +0 -33
  7. package/dist/_internal/server/features/homepage/data.d.ts +0 -18
  8. package/dist/_internal/server/features/homepage/data.js +0 -70
  9. package/dist/_internal/server/features/live/actions.d.ts +0 -6
  10. package/dist/_internal/server/features/live/actions.js +0 -27
  11. package/dist/_internal/server/features/messages/actions.d.ts +0 -6
  12. package/dist/_internal/server/features/messages/actions.js +0 -6
  13. package/dist/_internal/server/features/messages/data.d.ts +0 -10
  14. package/dist/_internal/server/features/messages/data.js +0 -24
  15. package/dist/_internal/server/features/messages/index.d.ts +0 -2
  16. package/dist/_internal/server/features/messages/index.js +0 -2
  17. package/dist/_internal/server/jobs/core/testerSandboxRefresh.d.ts +0 -12
  18. package/dist/_internal/server/jobs/core/testerSandboxRefresh.js +0 -112
  19. package/dist/_internal/server/jobs/handlers/testerSandboxRefresh.d.ts +0 -2
  20. package/dist/_internal/server/jobs/handlers/testerSandboxRefresh.js +0 -2
  21. package/dist/_internal/shared/features/homepage/section-gate.d.ts +0 -14
  22. package/dist/_internal/shared/features/homepage/section-gate.js +0 -51
  23. package/dist/_internal/shared/features/lottery/config-form.d.ts +0 -58
  24. package/dist/_internal/shared/features/lottery/config-form.js +0 -71
  25. package/dist/client/api/useFeatureFlags.d.ts +0 -22
  26. package/dist/client/api/useFeatureFlags.js +0 -27
  27. package/dist/features/account/address-validation.d.ts +0 -20
  28. package/dist/features/account/address-validation.js +0 -35
  29. package/dist/features/account/components/ChatList.d.ts +0 -14
  30. package/dist/features/account/components/ChatList.js +0 -9
  31. package/dist/features/account/components/ChatWindow.d.ts +0 -17
  32. package/dist/features/account/components/ChatWindow.js +0 -6
  33. package/dist/features/account/components/MessagesView.d.ts +0 -16
  34. package/dist/features/account/components/MessagesView.js +0 -5
  35. package/dist/features/account/components/UserOffersView.d.ts +0 -10
  36. package/dist/features/account/components/UserOffersView.js +0 -5
  37. package/dist/features/account/constants/addresses.d.ts +0 -13
  38. package/dist/features/account/constants/addresses.js +0 -43
  39. package/dist/features/account/hooks/useAddressForm.d.ts +0 -24
  40. package/dist/features/account/hooks/useAddressForm.js +0 -58
  41. package/dist/features/admin/actions/chat-actions.d.ts +0 -29
  42. package/dist/features/admin/actions/chat-actions.js +0 -101
  43. package/dist/features/admin/components/AdminFeatureFlagsView.d.ts +0 -9
  44. package/dist/features/admin/components/AdminFeatureFlagsView.js +0 -109
  45. package/dist/features/admin/components/AdminSiteView.d.ts +0 -10
  46. package/dist/features/admin/components/AdminSiteView.js +0 -52
  47. package/dist/features/admin/hooks/useChat.d.ts +0 -24
  48. package/dist/features/admin/hooks/useChat.js +0 -100
  49. package/dist/features/admin/permission-map.d.ts +0 -3
  50. package/dist/features/admin/permission-map.js +0 -26
  51. package/dist/features/admin/repository/chat.repository.d.ts +0 -50
  52. package/dist/features/admin/repository/chat.repository.js +0 -223
  53. package/dist/features/auth/actions/password-change-otp-actions.d.ts +0 -18
  54. package/dist/features/auth/actions/password-change-otp-actions.js +0 -88
  55. package/dist/features/auth/actions/realtime-token-actions.d.ts +0 -11
  56. package/dist/features/auth/actions/realtime-token-actions.js +0 -38
  57. package/dist/features/auth/password-change-otp.d.ts +0 -34
  58. package/dist/features/auth/password-change-otp.js +0 -56
  59. package/dist/features/classified/components/ClassifiedContactSellerPanel.d.ts +0 -12
  60. package/dist/features/classified/components/ClassifiedContactSellerPanel.js +0 -49
  61. package/dist/features/events/api/route.d.ts +0 -12
  62. package/dist/features/events/api/route.js +0 -90
  63. package/dist/features/faq/api/route.d.ts +0 -11
  64. package/dist/features/faq/api/route.js +0 -52
  65. package/dist/features/faq/hooks/useFAQs.d.ts +0 -22
  66. package/dist/features/faq/hooks/useFAQs.js +0 -41
  67. package/dist/features/media/upload/video-poster.test.d.ts +0 -1
  68. package/dist/features/media/upload/video-poster.test.js +0 -42
  69. package/dist/features/messages/actions/index.d.ts +0 -2
  70. package/dist/features/messages/actions/index.js +0 -2
  71. package/dist/features/messages/actions/messages-actions.d.ts +0 -15
  72. package/dist/features/messages/actions/messages-actions.js +0 -33
  73. package/dist/features/messages/actions/ping-rtdb.d.ts +0 -2
  74. package/dist/features/messages/actions/ping-rtdb.js +0 -26
  75. package/dist/features/messages/hooks/useConversation.d.ts +0 -22
  76. package/dist/features/messages/hooks/useConversation.js +0 -155
  77. package/dist/features/messages/hooks/useConversations.d.ts +0 -15
  78. package/dist/features/messages/hooks/useConversations.js +0 -104
  79. package/dist/features/messages/index.d.ts +0 -6
  80. package/dist/features/messages/index.js +0 -4
  81. package/dist/features/messages/realtime.d.ts +0 -32
  82. package/dist/features/messages/realtime.js +0 -39
  83. package/dist/features/messages/repository/conversations.repository.d.ts +0 -71
  84. package/dist/features/messages/repository/conversations.repository.js +0 -284
  85. package/dist/features/messages/schemas/firestore.d.ts +0 -34
  86. package/dist/features/messages/schemas/firestore.js +0 -16
  87. package/dist/features/messages/schemas/index.d.ts +0 -177
  88. package/dist/features/messages/schemas/index.js +0 -35
  89. package/dist/features/messages/server.d.ts +0 -6
  90. package/dist/features/messages/server.js +0 -6
  91. package/dist/features/products/components/MakeOfferForm.d.ts +0 -20
  92. package/dist/features/products/components/MakeOfferForm.js +0 -9
  93. package/dist/features/reviews/api/route.d.ts +0 -21
  94. package/dist/features/reviews/api/route.js +0 -174
  95. package/dist/features/seller/api/offers/route.d.ts +0 -17
  96. package/dist/features/seller/api/offers/route.js +0 -63
  97. package/dist/features/seller/components/SellerAuctionsView.d.ts +0 -9
  98. package/dist/features/seller/components/SellerAuctionsView.js +0 -113
  99. package/dist/features/seller/components/SellerOffersPanel.d.ts +0 -21
  100. package/dist/features/seller/components/SellerOffersPanel.js +0 -160
  101. package/dist/features/seller/components/SellerPreOrdersView.d.ts +0 -6
  102. package/dist/features/seller/components/SellerPreOrdersView.js +0 -148
  103. package/dist/features/seller/components/SellerPrizeDrawsView.d.ts +0 -6
  104. package/dist/features/seller/components/SellerPrizeDrawsView.js +0 -152
  105. package/dist/features/seller/permission-map.d.ts +0 -5
  106. package/dist/features/seller/permission-map.js +0 -17
  107. package/dist/features/shell/StepForm.d.ts +0 -80
  108. package/dist/features/shell/StepForm.js +0 -136
  109. package/dist/features/stores/api/route.d.ts +0 -12
  110. package/dist/features/stores/api/route.js +0 -110
  111. package/dist/features/tester/seed-data/authored/account-auth__testing-program--admin.d.ts +0 -2
  112. package/dist/features/tester/seed-data/authored/account-auth__testing-program--admin.js +0 -33
  113. package/dist/features/tester/seed-data/authored/account-auth__testing-program.d.ts +0 -2
  114. package/dist/features/tester/seed-data/authored/account-auth__testing-program.js +0 -138
  115. package/dist/features/tester/seed-data/bids-tester-seed-data.d.ts +0 -2
  116. package/dist/features/tester/seed-data/bids-tester-seed-data.js +0 -37
  117. package/dist/features/tester/seed-data/blog-tester-seed-data.d.ts +0 -2
  118. package/dist/features/tester/seed-data/blog-tester-seed-data.js +0 -40
  119. package/dist/features/tester/seed-data/carts-tester-seed-data.d.ts +0 -3
  120. package/dist/features/tester/seed-data/carts-tester-seed-data.js +0 -70
  121. package/dist/features/tester/seed-data/categories-tester-seed-data.d.ts +0 -2
  122. package/dist/features/tester/seed-data/categories-tester-seed-data.js +0 -218
  123. package/dist/features/tester/seed-data/coupons-tester-seed-data.d.ts +0 -2
  124. package/dist/features/tester/seed-data/coupons-tester-seed-data.js +0 -76
  125. package/dist/features/tester/seed-data/events-tester-seed-data.d.ts +0 -2
  126. package/dist/features/tester/seed-data/events-tester-seed-data.js +0 -193
  127. package/dist/features/tester/seed-data/offers-tester-seed-data.d.ts +0 -2
  128. package/dist/features/tester/seed-data/offers-tester-seed-data.js +0 -191
  129. package/dist/features/tester/seed-data/orders-tester-seed-data.d.ts +0 -2
  130. package/dist/features/tester/seed-data/orders-tester-seed-data.js +0 -329
  131. package/dist/features/tester/seed-data/products-tester-seed-data.d.ts +0 -2
  132. package/dist/features/tester/seed-data/products-tester-seed-data.js +0 -815
  133. package/dist/features/tester/seed-data/stores-tester-seed-data.d.ts +0 -2
  134. package/dist/features/tester/seed-data/stores-tester-seed-data.js +0 -79
  135. package/dist/features/wishlist/components/WishlistToggleButton.d.ts +0 -12
  136. package/dist/features/wishlist/components/WishlistToggleButton.js +0 -36
  137. package/dist/monitoring/analytics.d.ts +0 -80
  138. package/dist/monitoring/analytics.js +0 -176
  139. package/dist/next/middleware/pii-redactor.d.ts +0 -32
  140. package/dist/next/middleware/pii-redactor.js +0 -84
  141. package/dist/providers/db-firebase/realtime.d.ts +0 -42
  142. package/dist/providers/db-firebase/realtime.js +0 -138
  143. package/dist/security/rbac/Can.d.ts +0 -31
  144. package/dist/security/rbac/Can.js +0 -22
  145. package/dist/security/rbac/default-roles.d.ts +0 -2
  146. package/dist/security/rbac/default-roles.js +0 -83
  147. package/dist/security/rbac/hook.d.ts +0 -20
  148. package/dist/security/rbac/hook.js +0 -30
  149. package/dist/security/rbac/index.d.ts +0 -8
  150. package/dist/security/rbac/index.js +0 -6
  151. package/dist/security/rbac/middleware.d.ts +0 -51
  152. package/dist/security/rbac/middleware.js +0 -51
  153. package/dist/security/rbac/resolver.d.ts +0 -5
  154. package/dist/security/rbac/resolver.js +0 -38
  155. package/dist/security/rbac/server.d.ts +0 -24
  156. package/dist/security/rbac/server.js +0 -42
  157. package/dist/security/rbac/types.d.ts +0 -22
  158. package/dist/security/rbac/types.js +0 -2
  159. package/dist/seed/conversations-seed-data.d.ts +0 -2
  160. package/dist/seed/conversations-seed-data.js +0 -292
  161. package/dist/utils/filter.helper.d.ts +0 -18
  162. package/dist/utils/filter.helper.js +0 -21
  163. package/dist/utils/pagination.helper.d.ts +0 -23
  164. package/dist/utils/pagination.helper.js +0 -24
  165. package/dist/utils/search-tokens.d.ts +0 -2
  166. package/dist/utils/search-tokens.js +0 -21
@@ -1,26 +0,0 @@
1
- /**
2
- * pingConversationRtdb — server-side fan-out for conversation updates.
3
- *
4
- * Writes `Date.now()` to each ping path so RTDB-subscribed clients refetch.
5
- * Best-effort: failures are logged and swallowed (clients fall back to focus /
6
- * explicit refetch).
7
- */
8
- import { getAdminRealtimeDb } from "../../../providers/db-firebase";
9
- import { serverLogger } from "../../../monitoring";
10
- import { buildConversationPingPaths, } from "../realtime";
11
- import { normalizeError } from "../../../errors/normalize";
12
- export async function pingConversationRtdb(targets) {
13
- const paths = buildConversationPingPaths(targets);
14
- try {
15
- const db = getAdminRealtimeDb();
16
- const now = Date.now();
17
- await Promise.all(paths.filter(Boolean).map((p) => db.ref(p).set(now)));
18
- }
19
- catch (err) {
20
- void normalizeError(err);
21
- serverLogger.warn("conversations: RTDB ping failed", {
22
- paths,
23
- error: err instanceof Error ? err.message : String(err),
24
- });
25
- }
26
- }
@@ -1,22 +0,0 @@
1
- import { conversationPingPath, userConversationsPingPath } from "../realtime";
2
- import type { ConversationDocument } from "../schemas/firestore";
3
- /**
4
- * @deprecated Re-exported for backwards compat — import `conversationPingPath`
5
- * from `@mohasinac/appkit` instead.
6
- */
7
- export declare const CONVERSATIONS_PING_PATH: typeof conversationPingPath;
8
- /**
9
- * @deprecated Re-exported for backwards compat — import
10
- * `userConversationsPingPath` from `@mohasinac/appkit` instead.
11
- */
12
- export declare const CONVERSATIONS_PING_USER_PATH: typeof userConversationsPingPath;
13
- export interface UseConversationReturn {
14
- conversation: ConversationDocument | null;
15
- isLoading: boolean;
16
- error: Error | null;
17
- isConnected: boolean;
18
- sendMessage: (body: string) => Promise<void>;
19
- markRead: () => Promise<void>;
20
- refetch: () => Promise<void>;
21
- }
22
- export declare function useConversation(conversationId: string | null): UseConversationReturn;
@@ -1,155 +0,0 @@
1
- "use client";
2
- /**
3
- * useConversation — load + subscribe to a single conversation.
4
- *
5
- * Strategy: Firestore is the canonical store; messages live embedded in the
6
- * conversation document. The realtime layer is a single RTDB "ping" path
7
- * (`chats/{conversationId}/lastUpdate`) that the send/mark-read API routes
8
- * bump on every write. This hook subscribes to that ping and re-fetches the
9
- * doc through the API on each tick. Zero RTDB schema duplication and the
10
- * canonical Firestore message ordering wins.
11
- */
12
- import { useCallback, useEffect, useState } from "react";
13
- import { acquireRealtimeSession } from "../../../contracts/realtime-session";
14
- // One scope for both message hooks, deliberately: /api/realtime/token issues a
15
- // SINGLE token covering all of this user's conversationIds, so a per-id scope
16
- // would make the list and the detail hook re-sign against each other on the
17
- // same page for no gain.
18
- //
19
- // 🛑 Known limitation, not fixed here: because the scope is constant, a
20
- // conversation created AFTER sign-in is not in the cached token's claims and
21
- // its subscription is denied until the token's expiry skew forces a re-sign.
22
- // The denial is now surfaced (see the error callback below) rather than
23
- // swallowed. The durable fix is to re-acquire on a permission denial.
24
- const MESSAGES_SCOPE = "messages";
25
- import { conversationPingPath, userConversationsPingPath, } from "../realtime";
26
- import { normalizeError } from "../../../errors/normalize";
27
- import { apiClient, ApiClientError } from "../../../http/ApiClient";
28
- import { CONVERSATION_ENDPOINTS, ADMIN_ENDPOINTS, } from "../../../constants/api-endpoints";
29
- /**
30
- * @deprecated Re-exported for backwards compat — import `conversationPingPath`
31
- * from `@mohasinac/appkit` instead.
32
- */
33
- export const CONVERSATIONS_PING_PATH = conversationPingPath;
34
- /**
35
- * @deprecated Re-exported for backwards compat — import
36
- * `userConversationsPingPath` from `@mohasinac/appkit` instead.
37
- */
38
- export const CONVERSATIONS_PING_USER_PATH = userConversationsPingPath;
39
- const DETAIL_ENDPOINT = CONVERSATION_ENDPOINTS.BY_ID;
40
- const SEND_ENDPOINT = CONVERSATION_ENDPOINTS.MESSAGES;
41
- const READ_ENDPOINT = CONVERSATION_ENDPOINTS.READ;
42
- async function fetchDetail(id) {
43
- try {
44
- return await apiClient.get(DETAIL_ENDPOINT(id));
45
- }
46
- catch (err) {
47
- if (err instanceof ApiClientError && err.status === 404)
48
- return null;
49
- throw err;
50
- }
51
- }
52
- export function useConversation(conversationId) {
53
- const [conversation, setConversation] = useState(null);
54
- const [isLoading, setIsLoading] = useState(false);
55
- const [error, setError] = useState(null);
56
- const [isConnected, setIsConnected] = useState(false);
57
- const refetch = useCallback(async () => {
58
- if (!conversationId) {
59
- setConversation(null);
60
- return;
61
- }
62
- setIsLoading(true);
63
- setError(null);
64
- try {
65
- setConversation(await fetchDetail(conversationId));
66
- }
67
- catch (e) {
68
- void normalizeError(e);
69
- setError(e instanceof Error ? e : new Error(String(e)));
70
- }
71
- finally {
72
- setIsLoading(false);
73
- }
74
- }, [conversationId]);
75
- useEffect(() => {
76
- void refetch();
77
- }, [refetch]);
78
- useEffect(() => {
79
- if (!conversationId) {
80
- setIsConnected(false);
81
- return;
82
- }
83
- let unsubscribe;
84
- let release;
85
- let cancelled = false;
86
- // 🛑 Sign in BEFORE subscribing. Without a session `auth == null`, and the
87
- // `chats/$conversationId` rule requires
88
- // `auth.token.conversationIds[$conversationId]` — a claim that, until this
89
- // change, NOTHING issued (the token route only ever sent `chatIds`, from
90
- // the unrelated `chatRooms` collection). Two stacked faults on one channel:
91
- // no sign-in, and a claim with no issuer. The recipient of a message never
92
- // saw it until they manually refreshed; the sender only saw their own
93
- // because `send()` calls `refetch()` directly.
94
- void (async () => {
95
- try {
96
- const lease = await acquireRealtimeSession(MESSAGES_SCOPE, () => apiClient.post(ADMIN_ENDPOINTS.REALTIME_TOKEN, {}));
97
- release = lease.release;
98
- if (cancelled) {
99
- release();
100
- return;
101
- }
102
- // Subscribe through the LEASE's provider — it is bound to the app this
103
- // scope authenticated on. The global provider may be on a different app
104
- // carrying another channel's claims.
105
- unsubscribe = lease.provider.subscribe(conversationPingPath(conversationId), () => {
106
- setIsConnected(true);
107
- void refetch();
108
- }, (err) => {
109
- void normalizeError(err);
110
- setIsConnected(false);
111
- });
112
- }
113
- catch (_err) {
114
- void normalizeError(_err);
115
- // Provider unregistered or token fetch failed — falls back to the
116
- // one-shot fetch above.
117
- setIsConnected(false);
118
- }
119
- })();
120
- return () => {
121
- cancelled = true;
122
- setIsConnected(false);
123
- unsubscribe?.();
124
- release?.();
125
- };
126
- }, [conversationId, refetch]);
127
- const sendMessage = useCallback(async (body) => {
128
- if (!conversationId)
129
- return;
130
- const trimmed = body.trim();
131
- if (!trimmed)
132
- return;
133
- await apiClient.post(SEND_ENDPOINT(conversationId), { body: trimmed });
134
- // RTDB ping will trigger refetch — but kick off one immediately in case
135
- // the ping path isn't subscribed (e.g. provider not registered).
136
- await refetch();
137
- }, [conversationId, refetch]);
138
- const markRead = useCallback(() => {
139
- if (!conversationId)
140
- return Promise.resolve();
141
- return apiClient
142
- .post(READ_ENDPOINT(conversationId))
143
- .then(() => void refetch())
144
- .catch((err) => void normalizeError(err));
145
- }, [conversationId, refetch]);
146
- return {
147
- conversation,
148
- isLoading,
149
- error,
150
- isConnected,
151
- sendMessage,
152
- markRead,
153
- refetch,
154
- };
155
- }
@@ -1,15 +0,0 @@
1
- import type { ConversationDocument } from "../schemas/firestore";
2
- export interface UseConversationsOptions {
3
- /** Override the list endpoint — e.g. the seller-scoped /api/store/conversations. Defaults to the buyer endpoint. */
4
- endpoint?: string;
5
- /** Which per-conversation unread counter to sum into totalUnread. Defaults to "unreadBuyer". */
6
- unreadField?: "unreadBuyer" | "unreadSeller";
7
- }
8
- export interface UseConversationsReturn {
9
- conversations: ConversationDocument[];
10
- isLoading: boolean;
11
- error: Error | null;
12
- totalUnread: number;
13
- refetch: () => Promise<void>;
14
- }
15
- export declare function useConversations(userId: string | null | undefined, opts?: UseConversationsOptions): UseConversationsReturn;
@@ -1,104 +0,0 @@
1
- "use client";
2
- /**
3
- * useConversations — list the current buyer's conversations. Subscribes to a
4
- * single RTDB ping path so the list refreshes whenever any of the user's
5
- * conversations gets a new message.
6
- */
7
- import { useCallback, useEffect, useState } from "react";
8
- import { acquireRealtimeSession } from "../../../contracts/realtime-session";
9
- // One scope for both message hooks, deliberately: /api/realtime/token issues a
10
- // SINGLE token covering all of this user's conversationIds, so a per-id scope
11
- // would make the list and the detail hook re-sign against each other on the
12
- // same page for no gain.
13
- //
14
- // 🛑 Known limitation, not fixed here: because the scope is constant, a
15
- // conversation created AFTER sign-in is not in the cached token's claims and
16
- // its subscription is denied until the token's expiry skew forces a re-sign.
17
- // The denial is now surfaced (see the error callback below) rather than
18
- // swallowed. The durable fix is to re-acquire on a permission denial.
19
- const MESSAGES_SCOPE = "messages";
20
- import { userConversationsPingPath } from "../realtime";
21
- import { normalizeError } from "../../../errors/normalize";
22
- import { apiClient } from "../../../http/ApiClient";
23
- import { CONVERSATION_ENDPOINTS, ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
24
- const LIST_ENDPOINT = CONVERSATION_ENDPOINTS.LIST;
25
- async function fetchList(endpoint) {
26
- const data = await apiClient.get(endpoint);
27
- return data.items ?? [];
28
- }
29
- export function useConversations(userId, opts = {}) {
30
- const { endpoint = LIST_ENDPOINT, unreadField = "unreadBuyer" } = opts;
31
- const [conversations, setConversations] = useState([]);
32
- const [isLoading, setIsLoading] = useState(false);
33
- const [error, setError] = useState(null);
34
- const refetch = useCallback(async () => {
35
- if (!userId) {
36
- setConversations([]);
37
- return;
38
- }
39
- setIsLoading(true);
40
- setError(null);
41
- try {
42
- setConversations(await fetchList(endpoint));
43
- }
44
- catch (e) {
45
- void normalizeError(e);
46
- setError(e instanceof Error ? e : new Error(String(e)));
47
- }
48
- finally {
49
- setIsLoading(false);
50
- }
51
- }, [userId, endpoint]);
52
- useEffect(() => {
53
- void refetch();
54
- }, [refetch]);
55
- // Subscribe to the per-user ping path — any send/mark-read in any of the
56
- // user's conversations bumps this and triggers a fresh list fetch.
57
- useEffect(() => {
58
- if (!userId)
59
- return;
60
- let unsubscribe;
61
- let release;
62
- let cancelled = false;
63
- // 🛑 Sign in BEFORE subscribing. This hook used to call subscribe() with no
64
- // authenticated session at all, so `auth == null` and the
65
- // `chats/user/$userId` rule (`auth.uid == $userId`) could never pass. Every
66
- // update was permission-denied and the error handler below was a literal
67
- // `// ignore`, so the whole live-list channel was inert while looking wired.
68
- void (async () => {
69
- try {
70
- const lease = await acquireRealtimeSession(MESSAGES_SCOPE, () => apiClient.post(ADMIN_ENDPOINTS.REALTIME_TOKEN, {}));
71
- release = lease.release;
72
- if (cancelled) {
73
- release();
74
- return;
75
- }
76
- // Subscribe through the LEASE's provider — see useConversation.
77
- unsubscribe = lease.provider.subscribe(userConversationsPingPath(userId), () => {
78
- void refetch();
79
- }, (err) => {
80
- // A denied subscription is a real defect — silence is what hid this
81
- // channel being dead for months — so it is logged, not swallowed.
82
- // (This comment previously claimed "Surfaced, not ignored" while the
83
- // body only called normalizeError, which classifies without
84
- // reporting. That was the same documentation-that-lies pattern this
85
- // workstream exists to remove, so it now actually logs.)
86
- void normalizeError(err);
87
- console.warn("[messages] conversations live-update subscription denied; list falls back to fetch-on-load");
88
- });
89
- // Provider unregistered or token fetch failed — non-fatal; the list
90
- // still works via the one-shot fetch above, just without live updates.
91
- }
92
- catch (_err) {
93
- void normalizeError(_err);
94
- }
95
- })();
96
- return () => {
97
- cancelled = true;
98
- unsubscribe?.();
99
- release?.();
100
- };
101
- }, [userId, refetch]);
102
- const totalUnread = conversations.reduce((sum, c) => sum + (c[unreadField] ?? 0), 0);
103
- return { conversations, isLoading, error, totalUnread, refetch };
104
- }
@@ -1,6 +0,0 @@
1
- export * from "./hooks/useConversations";
2
- export * from "./hooks/useConversation";
3
- export type { ConversationDocument, ConversationMessage, } from "./schemas/firestore";
4
- export { CONVERSATIONS_COLLECTION, CONVERSATIONS_INDEXED_FIELDS, } from "./schemas/firestore";
5
- export { conversationPingPath, userConversationsPingPath, buildConversationPingPaths, } from "./realtime";
6
- export type { ConversationPingTargets } from "./realtime";
@@ -1,4 +0,0 @@
1
- export * from "./hooks/useConversations";
2
- export * from "./hooks/useConversation";
3
- export { CONVERSATIONS_COLLECTION, CONVERSATIONS_INDEXED_FIELDS, } from "./schemas/firestore";
4
- export { conversationPingPath, userConversationsPingPath, buildConversationPingPaths, } from "./realtime";
@@ -1,32 +0,0 @@
1
- /**
2
- * Conversations realtime channel — shared between client hooks and server
3
- * routes. Lives in a non-`"use client"` module so server route handlers can
4
- * import the path builders without dragging the React hooks into a server
5
- * bundle.
6
- *
7
- * Strategy (D5 + VC7):
8
- * - Firestore = canonical store; messages embedded in `conversations/{id}`.
9
- * - RTDB = "ping" channel only — server writes Date.now() to two paths on
10
- * every mutation, clients subscribe and refetch.
11
- *
12
- * chats/{id}/lastUpdate — single-conversation ping
13
- * chats/user/{userId}/lastUpdate — per-user fan-out ping
14
- *
15
- * Both ends import these constants instead of inlining the path strings.
16
- */
17
- /** RTDB path bumped on every write to a single conversation. */
18
- export declare function conversationPingPath(conversationId: string): string;
19
- /**
20
- * RTDB path bumped on every write across all of a user's conversations.
21
- * Subscribing to this is how the chat list / nav bell refresh without
22
- * subscribing to every individual conversation.
23
- */
24
- export declare function userConversationsPingPath(userId: string): string;
25
- /** All ping paths to bump on a single conversation write. */
26
- export interface ConversationPingTargets {
27
- conversationId: string;
28
- buyerId: string;
29
- /** Resolved store-owner UID for the seller side; null when not known. */
30
- sellerOwnerId: string | null;
31
- }
32
- export declare function buildConversationPingPaths(targets: ConversationPingTargets): string[];
@@ -1,39 +0,0 @@
1
- /**
2
- * Conversations realtime channel — shared between client hooks and server
3
- * routes. Lives in a non-`"use client"` module so server route handlers can
4
- * import the path builders without dragging the React hooks into a server
5
- * bundle.
6
- *
7
- * Strategy (D5 + VC7):
8
- * - Firestore = canonical store; messages embedded in `conversations/{id}`.
9
- * - RTDB = "ping" channel only — server writes Date.now() to two paths on
10
- * every mutation, clients subscribe and refetch.
11
- *
12
- * chats/{id}/lastUpdate — single-conversation ping
13
- * chats/user/{userId}/lastUpdate — per-user fan-out ping
14
- *
15
- * Both ends import these constants instead of inlining the path strings.
16
- */
17
- const PING_KEY = "lastUpdate";
18
- /** RTDB path bumped on every write to a single conversation. */
19
- export function conversationPingPath(conversationId) {
20
- return `chats/${conversationId}/${PING_KEY}`;
21
- }
22
- /**
23
- * RTDB path bumped on every write across all of a user's conversations.
24
- * Subscribing to this is how the chat list / nav bell refresh without
25
- * subscribing to every individual conversation.
26
- */
27
- export function userConversationsPingPath(userId) {
28
- return `chats/user/${userId}/${PING_KEY}`;
29
- }
30
- export function buildConversationPingPaths(targets) {
31
- const paths = [
32
- conversationPingPath(targets.conversationId),
33
- userConversationsPingPath(targets.buyerId),
34
- ];
35
- if (targets.sellerOwnerId) {
36
- paths.push(userConversationsPingPath(targets.sellerOwnerId));
37
- }
38
- return paths;
39
- }
@@ -1,71 +0,0 @@
1
- /**
2
- * ConversationsRepository — buyer↔seller messaging.
3
- *
4
- * One document per buyer×storeId×productId tuple. Messages are embedded in
5
- * the document as an array (good up to ~200 entries; migrate to subcollection
6
- * later if threads explode).
7
- *
8
- * All mutations are transactional so concurrent writes don't drop messages
9
- * or unread counters.
10
- */
11
- import { type ConversationDocument, type ConversationMessage } from "../schemas/firestore";
12
- export declare class ConversationFullError extends Error {
13
- readonly code: "CONVERSATION_FULL";
14
- }
15
- export declare class ConversationsRepository {
16
- private collection;
17
- /**
18
- * Find an existing buyer×store×product conversation or create a new one.
19
- * Idempotent — safe to call on every "Contact Seller" click.
20
- */
21
- findOrCreateByContext(params: {
22
- buyerId: string;
23
- buyerDisplayName: string;
24
- storeId: string;
25
- storeName: string;
26
- sellerDisplayName: string;
27
- productId?: string;
28
- productTitle?: string;
29
- }): Promise<ConversationDocument>;
30
- findById(id: string): Promise<ConversationDocument | null>;
31
- listByBuyer(buyerId: string): Promise<ConversationDocument[]>;
32
- listByStore(storeId: string): Promise<ConversationDocument[]>;
33
- /**
34
- * Conversation ids this user may subscribe to, for the RTDB `conversationIds`
35
- * custom-token claim.
36
- *
37
- * 🛑 This claim previously had NO ISSUER ANYWHERE. The `chats/$conversationId`
38
- * rule requires `auth.token.conversationIds[$conversationId] == true`, but
39
- * `/api/realtime/token` only ever issued `chatIds`, built by
40
- * `chatRepository.getChatIdsForUser()` over the **`chatRooms`** collection —
41
- * a different collection with different document ids. So the buyer↔seller
42
- * live-message channel was denied for every user, and both subscribing hooks
43
- * swallowed the permission error (one into `setIsConnected(false)`, one into
44
- * a literal `// ignore`). Messages only ever appeared for the sender, via an
45
- * unrelated `refetch()` after send.
46
- *
47
- * `.select()` with no fields fetches ids only — the document bodies are not
48
- * needed and this runs on every token issue.
49
- *
50
- * @param limit hard cap. Firebase custom claims are limited to 1000 BYTES
51
- * total; each entry costs roughly `"<20-char id>":true,` ≈ 28 bytes, so a
52
- * user in hundreds of conversations would make `createCustomToken` throw
53
- * and take the whole route down (see the route's own comment).
54
- */
55
- getConversationIdsForUser(userId: string, storeIds?: readonly string[], limit?: number): Promise<string[]>;
56
- /**
57
- * Append a message to a conversation in a transaction. Bumps the counterparty
58
- * unread counter; updates lastMessage / lastMessageAt / updatedAt.
59
- */
60
- appendMessage(conversationId: string, msg: Omit<ConversationMessage, "id" | "sentAt" | "isRead"> & {
61
- id?: string;
62
- sentAt?: Date;
63
- }): Promise<ConversationDocument>;
64
- /**
65
- * Zero out the unread counter for one party. Also flips `isRead` on the
66
- * embedded messages sent by the other party so the seller side can see what
67
- * the buyer has already read.
68
- */
69
- markRead(conversationId: string, role: "buyer" | "seller"): Promise<void>;
70
- }
71
- export declare const conversationsRepository: ConversationsRepository;