@ttt-productions/upload-ui 0.7.17 → 0.7.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/index.d.ts.map +1 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +1 -1
- package/dist/react/guard/guarded-link.d.ts +0 -27
- package/dist/react/guard/guarded-link.d.ts.map +1 -1
- package/dist/react/guard/guarded-link.js.map +1 -1
- package/dist/react/guard/local-upload-guard-provider.d.ts +0 -81
- package/dist/react/guard/local-upload-guard-provider.d.ts.map +1 -1
- package/dist/react/guard/local-upload-guard-provider.js +3 -60
- package/dist/react/guard/local-upload-guard-provider.js.map +1 -1
- package/dist/react/guard/use-guarded-navigation.d.ts +0 -7
- package/dist/react/guard/use-guarded-navigation.d.ts.map +1 -1
- package/dist/react/guard/use-guarded-navigation.js +0 -28
- package/dist/react/guard/use-guarded-navigation.js.map +1 -1
- package/dist/react/tray/in-flight-uploads-provider.d.ts +1 -126
- package/dist/react/tray/in-flight-uploads-provider.d.ts.map +1 -1
- package/dist/react/tray/in-flight-uploads-provider.js +10 -133
- package/dist/react/tray/in-flight-uploads-provider.js.map +1 -1
- package/dist/react/tray/use-clear-upload-activity.d.ts +1 -26
- package/dist/react/tray/use-clear-upload-activity.d.ts.map +1 -1
- package/dist/react/tray/use-clear-upload-activity.js +2 -20
- package/dist/react/tray/use-clear-upload-activity.js.map +1 -1
- package/dist/react/tray/use-mark-upload-activity-seen.d.ts +0 -4
- package/dist/react/tray/use-mark-upload-activity-seen.d.ts.map +1 -1
- package/dist/react/tray/use-mark-upload-activity-seen.js +5 -25
- package/dist/react/tray/use-mark-upload-activity-seen.js.map +1 -1
- package/dist/react/tray/use-upload-processing.d.ts +0 -10
- package/dist/react/tray/use-upload-processing.d.ts.map +1 -1
- package/dist/react/tray/use-upload-processing.js +0 -18
- package/dist/react/tray/use-upload-processing.js.map +1 -1
- package/dist/react/upload/deferred-upload-form-shell.d.ts +1 -19
- package/dist/react/upload/deferred-upload-form-shell.d.ts.map +1 -1
- package/dist/react/upload/deferred-upload-form-shell.js +0 -1
- package/dist/react/upload/deferred-upload-form-shell.js.map +1 -1
- package/dist/react/upload/use-guarded-upload.d.ts +0 -29
- package/dist/react/upload/use-guarded-upload.d.ts.map +1 -1
- package/dist/react/upload/use-guarded-upload.js +0 -13
- package/dist/react/upload/use-guarded-upload.js.map +1 -1
- package/dist/upload-display.d.ts +0 -14
- package/dist/upload-display.d.ts.map +1 -1
- package/dist/upload-display.js +2 -5
- package/dist/upload-display.js.map +1 -1
- package/package.json +13 -6
|
@@ -1,45 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generic in-flight uploads provider.
|
|
3
|
-
*
|
|
4
|
-
* Provides a global Map of in-flight upload state by `pendingMediaId`, derived
|
|
5
|
-
* from a Firestore listener over the consumer-supplied collection. On terminal
|
|
6
|
-
* status transitions observed THIS SESSION, fires consumer-supplied success /
|
|
7
|
-
* failure / rejection callbacks. Records monitoring breadcrumbs and parse
|
|
8
|
-
* errors through the injected monitoring callback.
|
|
9
|
-
*
|
|
10
|
-
* EVERY app-specific value is supplied by the consumer:
|
|
11
|
-
* - userId resolution
|
|
12
|
-
* - Firestore `db` reference
|
|
13
|
-
* - collection path
|
|
14
|
-
* - listener time window (ms)
|
|
15
|
-
* - parser/schema for raw Firestore doc → typed PendingMedia
|
|
16
|
-
* - success copy per origin
|
|
17
|
-
* - notification callback (cache invalidation, etc.)
|
|
18
|
-
* - toast callback
|
|
19
|
-
* - rejection callback (for the media-rejection dialog or equivalent)
|
|
20
|
-
* - monitoring callback (breadcrumbs + captured errors)
|
|
21
|
-
*
|
|
22
|
-
* Read APIs:
|
|
23
|
-
* - `useInFlightUploadsState()` — full Map.
|
|
24
|
-
* - `useInFlightUpload(id)` — single doc, narrowable on status.
|
|
25
|
-
* - `useUploadActivityState()` — tray-visible items, sorted newest-first.
|
|
26
|
-
*
|
|
27
|
-
* The package owns the deduplication, initial-snapshot suppression, and
|
|
28
|
-
* non-terminal → terminal transition tracking. It does NOT own anything about
|
|
29
|
-
* what a file-origin is or what message text appears in toasts.
|
|
30
|
-
*/
|
|
31
1
|
import { type ReactNode } from 'react';
|
|
32
2
|
import { PendingMediaStatusSchema, type PendingMediaNonTerminalStatus, type PendingMediaStatus } from '@ttt-productions/media-schemas';
|
|
33
|
-
/** Generic pending-media status — media-schemas' one declaration. */
|
|
34
3
|
export type InFlightUploadStatus = PendingMediaStatus;
|
|
35
|
-
/**
|
|
36
|
-
* Generic in-flight upload record. The provider keeps a Map<id, InFlightUpload>
|
|
37
|
-
* built from each parsed PendingMedia. Consumers parameterize the `fileOrigin`
|
|
38
|
-
* type via the parser's return type (the provider treats it as opaque).
|
|
39
|
-
*
|
|
40
|
-
* `TFileOrigin` defaults to `string` so unannotated consumers get a permissive
|
|
41
|
-
* shape; consuming apps bind it to their own `FileOrigin` union at the call site.
|
|
42
|
-
*/
|
|
43
4
|
export type InFlightUploadBase<TFileOrigin extends string = string> = {
|
|
44
5
|
id: string;
|
|
45
6
|
fileOrigin: TFileOrigin;
|
|
@@ -82,14 +43,6 @@ export type InFlightUploadRejected<TFileOrigin extends string = string> = InFlig
|
|
|
82
43
|
uploadTraySeenBy?: string;
|
|
83
44
|
};
|
|
84
45
|
export type InFlightUpload<TFileOrigin extends string = string> = InFlightUploadActive<TFileOrigin> | InFlightUploadCompleted<TFileOrigin> | InFlightUploadFailed<TFileOrigin> | InFlightUploadRejected<TFileOrigin>;
|
|
85
|
-
/**
|
|
86
|
-
* Result of the consumer-supplied parser. The provider keeps the raw parsed
|
|
87
|
-
* record for callbacks (so consumers can read domain events, target IDs, etc.)
|
|
88
|
-
* AND derives the generic InFlightUpload from it for the public Map.
|
|
89
|
-
*
|
|
90
|
-
* The parser MUST throw if validation fails; the provider catches and routes
|
|
91
|
-
* the error to the monitoring callback.
|
|
92
|
-
*/
|
|
93
46
|
export type ParsedPendingMedia<TFileOrigin extends string = string> = {
|
|
94
47
|
id: string;
|
|
95
48
|
userId: string;
|
|
@@ -111,43 +64,17 @@ export type ParsedPendingMedia<TFileOrigin extends string = string> = {
|
|
|
111
64
|
uploadTrayClearedBy?: string;
|
|
112
65
|
uploadTraySeenAt?: number;
|
|
113
66
|
uploadTraySeenBy?: string;
|
|
114
|
-
/**
|
|
115
|
-
* Opaque consumer-controlled domain-event payload, surfaced to the
|
|
116
|
-
* `onUploadCompleted` and `onUploadRejected` callbacks for cache
|
|
117
|
-
* invalidation. The provider does not interpret it.
|
|
118
|
-
*/
|
|
119
67
|
domainEvents?: unknown;
|
|
120
68
|
};
|
|
121
69
|
export type InFlightUploadsAdapter<TFileOrigin extends string = string> = {
|
|
122
|
-
/** Returns the current user's uid, or null if not signed in. */
|
|
123
70
|
getCurrentUserId: () => string | null;
|
|
124
|
-
/** Firestore database adapter (loose typing — `unknown` keeps zero firebase coupling at the type level). */
|
|
125
71
|
db: unknown;
|
|
126
|
-
/** Collection path holding pending-media docs (e.g. "pendingMedia"). */
|
|
127
72
|
collectionPath: string;
|
|
128
|
-
/** Listener window in milliseconds — only docs with `createdAt >= now - window` are tracked. */
|
|
129
73
|
listenerWindowMs: number;
|
|
130
|
-
/**
|
|
131
|
-
* Parse a raw Firestore doc snapshot's `.data()` into the generic shape the
|
|
132
|
-
* provider tracks. Throws on validation failure; the provider routes thrown
|
|
133
|
-
* errors to `onMonitoringEvent`.
|
|
134
|
-
*/
|
|
135
74
|
parsePendingMedia: (raw: unknown) => ParsedPendingMedia<TFileOrigin>;
|
|
136
|
-
/** Called once per observed non-terminal → terminal completed transition. */
|
|
137
75
|
onUploadCompleted: (parsed: ParsedPendingMedia<TFileOrigin>) => void;
|
|
138
|
-
/** Called once per observed non-terminal → terminal failed transition. */
|
|
139
76
|
onUploadFailed: (parsed: ParsedPendingMedia<TFileOrigin>) => void;
|
|
140
|
-
/** Called once per observed non-terminal → terminal rejected transition. */
|
|
141
77
|
onUploadRejected: (parsed: ParsedPendingMedia<TFileOrigin>) => void;
|
|
142
|
-
/**
|
|
143
|
-
* Monitoring callback. Receives:
|
|
144
|
-
* - `breadcrumb`: informational, fired on every observed terminal transition.
|
|
145
|
-
* - `parse-error`: when `parsePendingMedia` throws.
|
|
146
|
-
* - `listener-error`: when the Firestore listener's error callback fires.
|
|
147
|
-
*
|
|
148
|
-
* Consumers wire this to Sentry, OpenTelemetry, or any other observability
|
|
149
|
-
* provider. The provider does NOT import any monitoring SDK directly.
|
|
150
|
-
*/
|
|
151
78
|
onMonitoringEvent: (event: InFlightUploadsMonitoringEvent<TFileOrigin>) => void;
|
|
152
79
|
};
|
|
153
80
|
export type InFlightUploadsMonitoringEvent<TFileOrigin extends string = string> = {
|
|
@@ -170,13 +97,6 @@ export type InFlightUploadsMonitoringEvent<TFileOrigin extends string = string>
|
|
|
170
97
|
type: 'listener-error';
|
|
171
98
|
error: unknown;
|
|
172
99
|
};
|
|
173
|
-
/**
|
|
174
|
-
* Minimal subscription contract used by the provider. The default
|
|
175
|
-
* implementation lives in `firestore-subscribe.ts` and uses the standard
|
|
176
|
-
* `firebase/firestore` `onSnapshot` + `query` + `where` builders.
|
|
177
|
-
*
|
|
178
|
-
* Tests inject a fake to drive the listener without spinning up Firestore.
|
|
179
|
-
*/
|
|
180
100
|
export type FirestoreSubscribeFn = (args: {
|
|
181
101
|
db: unknown;
|
|
182
102
|
collectionPath: string;
|
|
@@ -187,26 +107,11 @@ export type FirestoreSubscribeFn = (args: {
|
|
|
187
107
|
}) => () => void;
|
|
188
108
|
export type FirestoreLikeSnapshot = {
|
|
189
109
|
docChanges: () => FirestoreLikeDocChange[];
|
|
190
|
-
/**
|
|
191
|
-
* Snapshot metadata. Present when the subscription is opened with
|
|
192
|
-
* `{ includeMetadataChanges: true }` (the default firebase implementation does).
|
|
193
|
-
* `fromCache === false` means a server-confirmed snapshot. Optional so injected
|
|
194
|
-
* test fakes need not supply it (absent ⇒ treated as server-confirmed).
|
|
195
|
-
*/
|
|
196
110
|
metadata?: {
|
|
197
111
|
fromCache: boolean;
|
|
198
112
|
};
|
|
199
113
|
};
|
|
200
|
-
/**
|
|
201
|
-
* Metadata-derived liveness of the uploads listener, mirroring query-core's
|
|
202
|
-
* `FirestoreSourceState`. Firestore serves cached snapshots offline WITHOUT
|
|
203
|
-
* firing the error callback, so the Files tray source needs this to show a
|
|
204
|
-
* degraded indicator instead of a false "all caught up":
|
|
205
|
-
* - `connecting` — no server-confirmed snapshot yet.
|
|
206
|
-
* - `live` — at least one server-confirmed snapshot (`fromCache === false`).
|
|
207
|
-
* - `offline` — only cached data after having been live.
|
|
208
|
-
* - `error` — the listener error callback fired.
|
|
209
|
-
*/
|
|
114
|
+
/** Firestore serves cached snapshots offline without firing the error callback, so liveness comes from metadata. */
|
|
210
115
|
export type UploadsSourceState = 'connecting' | 'live' | 'offline' | 'error';
|
|
211
116
|
export type FirestoreLikeDocChange = {
|
|
212
117
|
type: 'added' | 'modified' | 'removed';
|
|
@@ -215,52 +120,22 @@ export type FirestoreLikeDocChange = {
|
|
|
215
120
|
data: () => unknown;
|
|
216
121
|
};
|
|
217
122
|
};
|
|
218
|
-
/** Restarting the uploads listener after it failed, and whether that restart is running. */
|
|
219
123
|
export interface UploadsSourceRetry {
|
|
220
|
-
/**
|
|
221
|
-
* Tear the listener down and subscribe again on a fresh retry ladder. While the restart runs the
|
|
222
|
-
* source state stays as it was (an `error` stays `error`), so a failure shown on it stays on
|
|
223
|
-
* screen with its control pending.
|
|
224
|
-
*/
|
|
225
124
|
retry: () => void;
|
|
226
|
-
/** A `retry` is running: true until the restarted listener answers or its ladder fails again. */
|
|
227
125
|
retrying: boolean;
|
|
228
126
|
}
|
|
229
127
|
export interface InFlightUploadsProviderProps<TFileOrigin extends string = string> {
|
|
230
128
|
adapter: InFlightUploadsAdapter<TFileOrigin>;
|
|
231
|
-
/** Firestore subscription function. Defaults to the firebase/firestore implementation. */
|
|
232
129
|
subscribe?: FirestoreSubscribeFn;
|
|
233
130
|
children: ReactNode;
|
|
234
131
|
}
|
|
235
132
|
export declare function InFlightUploadsProvider<TFileOrigin extends string = string>(props: InFlightUploadsProviderProps<TFileOrigin>): import("react").JSX.Element;
|
|
236
133
|
export declare function useInFlightUploadsState<TFileOrigin extends string = string>(): Map<string, InFlightUpload<TFileOrigin>>;
|
|
237
134
|
export declare function useInFlightUpload<TFileOrigin extends string = string>(id: string | null | undefined): InFlightUpload<TFileOrigin> | undefined;
|
|
238
|
-
/**
|
|
239
|
-
* Metadata-derived source state of the uploads listener
|
|
240
|
-
* (`connecting | live | offline | error`). The Files tray uses this to show a
|
|
241
|
-
* degraded indicator and never display "all caught up" while the source is
|
|
242
|
-
* `offline`/`error`/`connecting`.
|
|
243
|
-
*/
|
|
244
135
|
export declare function useUploadsSourceState(): UploadsSourceState;
|
|
245
|
-
/**
|
|
246
|
-
* The uploads listener's restart — the Try again of a failure shown on `useUploadsSourceState` —
|
|
247
|
-
* and whether it is running.
|
|
248
|
-
*/
|
|
249
136
|
export declare function useUploadsSourceRetry(): UploadsSourceRetry;
|
|
250
|
-
/**
|
|
251
|
-
* `true` for `completed` / `failed` / `rejected`. Used by tray UI to decide
|
|
252
|
-
* whether a Clear action is available for an item.
|
|
253
|
-
*/
|
|
254
137
|
export declare function isTerminalUpload<TFileOrigin extends string = string>(item: InFlightUpload<TFileOrigin>): item is Exclude<InFlightUpload<TFileOrigin>, {
|
|
255
138
|
status: PendingMediaNonTerminalStatus;
|
|
256
139
|
}>;
|
|
257
|
-
/**
|
|
258
|
-
* Tray-visible items. Includes every pending/processing upload, plus terminal
|
|
259
|
-
* uploads that have NOT been cleared from the tray. Sorted newest-first by
|
|
260
|
-
* createdAt.
|
|
261
|
-
*
|
|
262
|
-
* The public selector name in upload-ui is `useUploadActivityState`. The
|
|
263
|
-
* mutation hook is `useClearUploadActivity` (in a separate file).
|
|
264
|
-
*/
|
|
265
140
|
export declare function useUploadActivityState<TFileOrigin extends string = string>(): InFlightUpload<TFileOrigin>[];
|
|
266
141
|
//# sourceMappingURL=in-flight-uploads-provider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"in-flight-uploads-provider.d.ts","sourceRoot":"","sources":["../../../src/react/tray/in-flight-uploads-provider.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"in-flight-uploads-provider.d.ts","sourceRoot":"","sources":["../../../src/react/tray/in-flight-uploads-provider.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
//
|
|
4
|
-
// SANCTIONED EXCEPTION to ARCH-206 (packages read and write Firestore only
|
|
3
|
+
// SANCTIONED EXCEPTION to RULE-STD-ARCH-206 (packages read and write Firestore only
|
|
5
4
|
// through @ttt-productions/query-core).
|
|
6
5
|
//
|
|
7
6
|
// WHY this provider reads Firestore directly instead of via a query-core hook:
|
|
@@ -17,41 +16,9 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
17
16
|
// Turbopack module identity (see defaultFirestoreSubscribe's comment).
|
|
18
17
|
//
|
|
19
18
|
// Do NOT "migrate" this onto query-core without preserving docChanges() semantics.
|
|
20
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
21
19
|
import { collection, onSnapshot, query, where } from 'firebase/firestore';
|
|
22
|
-
/**
|
|
23
|
-
* Generic in-flight uploads provider.
|
|
24
|
-
*
|
|
25
|
-
* Provides a global Map of in-flight upload state by `pendingMediaId`, derived
|
|
26
|
-
* from a Firestore listener over the consumer-supplied collection. On terminal
|
|
27
|
-
* status transitions observed THIS SESSION, fires consumer-supplied success /
|
|
28
|
-
* failure / rejection callbacks. Records monitoring breadcrumbs and parse
|
|
29
|
-
* errors through the injected monitoring callback.
|
|
30
|
-
*
|
|
31
|
-
* EVERY app-specific value is supplied by the consumer:
|
|
32
|
-
* - userId resolution
|
|
33
|
-
* - Firestore `db` reference
|
|
34
|
-
* - collection path
|
|
35
|
-
* - listener time window (ms)
|
|
36
|
-
* - parser/schema for raw Firestore doc → typed PendingMedia
|
|
37
|
-
* - success copy per origin
|
|
38
|
-
* - notification callback (cache invalidation, etc.)
|
|
39
|
-
* - toast callback
|
|
40
|
-
* - rejection callback (for the media-rejection dialog or equivalent)
|
|
41
|
-
* - monitoring callback (breadcrumbs + captured errors)
|
|
42
|
-
*
|
|
43
|
-
* Read APIs:
|
|
44
|
-
* - `useInFlightUploadsState()` — full Map.
|
|
45
|
-
* - `useInFlightUpload(id)` — single doc, narrowable on status.
|
|
46
|
-
* - `useUploadActivityState()` — tray-visible items, sorted newest-first.
|
|
47
|
-
*
|
|
48
|
-
* The package owns the deduplication, initial-snapshot suppression, and
|
|
49
|
-
* non-terminal → terminal transition tracking. It does NOT own anything about
|
|
50
|
-
* what a file-origin is or what message text appears in toasts.
|
|
51
|
-
*/
|
|
52
20
|
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react';
|
|
53
21
|
import { isPendingMediaNonTerminalStatus, isPendingMediaTerminalStatus, PendingMediaStatusSchema, } from '@ttt-productions/media-schemas';
|
|
54
|
-
// Status values derive from media-schemas' one declaration.
|
|
55
22
|
const STATUS = PendingMediaStatusSchema.enum;
|
|
56
23
|
const InFlightUploadsContext = createContext(undefined);
|
|
57
24
|
function fromParsed(parsed) {
|
|
@@ -91,7 +58,6 @@ function fromParsed(parsed) {
|
|
|
91
58
|
uploadTraySeenBy: parsed.uploadTraySeenBy,
|
|
92
59
|
};
|
|
93
60
|
}
|
|
94
|
-
// rejected
|
|
95
61
|
return {
|
|
96
62
|
...base,
|
|
97
63
|
status: STATUS.rejected,
|
|
@@ -101,10 +67,7 @@ function fromParsed(parsed) {
|
|
|
101
67
|
violationId: parsed.violationId,
|
|
102
68
|
uploadTrayClearedAt: parsed.uploadTrayClearedAt,
|
|
103
69
|
uploadTrayClearedBy: parsed.uploadTrayClearedBy,
|
|
104
|
-
//
|
|
105
|
-
// `failed` do: without them a seen rejected upload relights the Files dot
|
|
106
|
-
// forever, because the tray's unseen-terminal check never sees
|
|
107
|
-
// `uploadTraySeenAt`.
|
|
70
|
+
// Without the seen fields a seen rejected upload relights the Files dot forever.
|
|
108
71
|
uploadTraySeenAt: parsed.uploadTraySeenAt,
|
|
109
72
|
uploadTraySeenBy: parsed.uploadTraySeenBy,
|
|
110
73
|
};
|
|
@@ -115,36 +78,19 @@ export function InFlightUploadsProvider(props) {
|
|
|
115
78
|
const [sourceState, setSourceState] = useState('connecting');
|
|
116
79
|
const [restarts, setRestarts] = useState(0);
|
|
117
80
|
const [retrying, setRetrying] = useState(false);
|
|
118
|
-
/** Set by `retry` for the subscription the restart opens; that run keeps the source state. */
|
|
119
81
|
const restartRequestedRef = useRef(false);
|
|
120
82
|
const seenTerminalIdsRef = useRef(new Set());
|
|
121
83
|
const statusByIdRef = useRef(new Map());
|
|
122
84
|
const hasProcessedInitialSnapshotRef = useRef(false);
|
|
123
|
-
/** The identity the dedup/tracking refs belong to — they outlive resubscribes
|
|
124
|
-
* for the SAME user (auth-settling flaps) and reset only on a real identity
|
|
125
|
-
* change. See the reset block in the subscription effect. */
|
|
126
85
|
const trackedUserIdRef = useRef(null);
|
|
127
|
-
// Stable refs for adapter callbacks — read from latest adapter inside effect.
|
|
128
86
|
const adapterRef = useRef(adapter);
|
|
129
87
|
useEffect(() => {
|
|
130
88
|
adapterRef.current = adapter;
|
|
131
89
|
});
|
|
132
90
|
const userId = adapter.getCurrentUserId();
|
|
133
91
|
useEffect(() => {
|
|
134
|
-
// The
|
|
135
|
-
//
|
|
136
|
-
// status-tracking refs are IDENTITY-scoped, not subscription-scoped: an
|
|
137
|
-
// auth-settling flap (uid → null → same uid re-subscribes) must neither
|
|
138
|
-
// re-fire terminals the previous subscription already fired (seen set
|
|
139
|
-
// persists) nor lose the observed non-terminal statuses that let the new
|
|
140
|
-
// subscription's initial listing detect a transition that completed during
|
|
141
|
-
// the gap. A real identity change — including UID-A → UID-B with no
|
|
142
|
-
// intermediate null user — starts clean; never rely on an intermediate
|
|
143
|
-
// sign-out to clear refs between two authenticated UIDs.
|
|
144
|
-
// A restart the person asked for keeps the source state it had (a failure stays a failure
|
|
145
|
-
// until the restarted listener answers); every other run starts over as `connecting`.
|
|
146
|
-
// A restart belongs to the identity it was asked for: one that lands with a sign-out or a switch
|
|
147
|
-
// of account is an ordinary new subscription, which starts over.
|
|
92
|
+
// The dedup and tracking refs are identity-scoped: they survive a same-user resubscribe and reset
|
|
93
|
+
// on any identity change, UID-A to UID-B with no sign-out between included.
|
|
148
94
|
let restarting = restartRequestedRef.current && userId !== null && userId === trackedUserIdRef.current;
|
|
149
95
|
restartRequestedRef.current = false;
|
|
150
96
|
setUploads(new Map());
|
|
@@ -194,12 +140,8 @@ export function InFlightUploadsProvider(props) {
|
|
|
194
140
|
const currentIsTerminal = isPendingMediaTerminalStatus(currentStatus);
|
|
195
141
|
const previousWasNonTerminal = isPendingMediaNonTerminalStatus(previousStatus);
|
|
196
142
|
if (isInitialSnapshot) {
|
|
197
|
-
// A
|
|
198
|
-
//
|
|
199
|
-
// refresh never re-toasts. ONE exception — this provider instance
|
|
200
|
-
// tracked the doc as non-terminal before (same-identity resubscribe
|
|
201
|
-
// gap; the tracking refs survive those), so the terminal listing IS
|
|
202
|
-
// the transition, observed across the gap, and must fire.
|
|
143
|
+
// A first snapshot re-lists current state: its terminal docs fire only when this instance tracked
|
|
144
|
+
// them as non-terminal before.
|
|
203
145
|
if (currentIsTerminal) {
|
|
204
146
|
if (previousWasNonTerminal && !seenTerminalIdsRef.current.has(parsed.id)) {
|
|
205
147
|
newlyTerminal.push(parsed);
|
|
@@ -208,23 +150,14 @@ export function InFlightUploadsProvider(props) {
|
|
|
208
150
|
}
|
|
209
151
|
}
|
|
210
152
|
else if (currentIsTerminal && !seenTerminalIdsRef.current.has(parsed.id)) {
|
|
211
|
-
//
|
|
212
|
-
// FIRST client-visible state is already terminal. A fast backend
|
|
213
|
-
// outcome (moderation rejection in ~2s) can coalesce created→terminal
|
|
214
|
-
// into one delivery, and the uploader is actively waiting on exactly
|
|
215
|
-
// this callback; swallowing it silently loses a rejection dialog.
|
|
153
|
+
// A fast outcome can coalesce created and terminal into one delivery, so any unseen terminal fires.
|
|
216
154
|
seenTerminalIdsRef.current.add(parsed.id);
|
|
217
155
|
newlyTerminal.push(parsed);
|
|
218
156
|
}
|
|
219
157
|
statusByIdRef.current.set(parsed.id, currentStatus);
|
|
220
158
|
}
|
|
221
159
|
hasProcessedInitialSnapshotRef.current = true;
|
|
222
|
-
// Metadata-derived source state: stay `connecting` until a server-confirmed
|
|
223
|
-
// snapshot, `offline` if only cached data arrives after being live.
|
|
224
160
|
const fromCache = snapshot.metadata?.fromCache ?? false;
|
|
225
|
-
// A restart ends on the server's answer: a cached snapshot during it keeps the failure on screen
|
|
226
|
-
// (the server may refuse again). Otherwise a snapshot after a failed listener comes from a new
|
|
227
|
-
// subscription: not yet live, never offline.
|
|
228
161
|
if (!(restarting && fromCache)) {
|
|
229
162
|
setSourceState((prev) => fromCache
|
|
230
163
|
? prev === 'connecting' || prev === 'error'
|
|
@@ -273,13 +206,6 @@ export function InFlightUploadsProvider(props) {
|
|
|
273
206
|
adapterRef.current.onUploadRejected(parsed);
|
|
274
207
|
}
|
|
275
208
|
}
|
|
276
|
-
// Bounded RESUBSCRIBE on listener error: a cold-session race (e.g. Firestore
|
|
277
|
-
// App Check enforcement rejecting the first attach before the token is
|
|
278
|
-
// minted) must not kill the listener for the whole session — observed live:
|
|
279
|
-
// one permission-denied at first login → sourceState 'error' forever, every
|
|
280
|
-
// tray file status dead until reload. Retry on a short ladder; a snapshot
|
|
281
|
-
// resets the ladder (healthy again); exhausting it goes terminal 'error'
|
|
282
|
-
// (a real rules denial stays denied — retries are cheap and bounded).
|
|
283
209
|
const RESUBSCRIBE_DELAYS_MS = [1_000, 3_000, 10_000, 30_000];
|
|
284
210
|
let attempt = 0;
|
|
285
211
|
let disposed = false;
|
|
@@ -292,7 +218,7 @@ export function InFlightUploadsProvider(props) {
|
|
|
292
218
|
userId,
|
|
293
219
|
windowStartMs,
|
|
294
220
|
onSnapshot: (snapshot) => {
|
|
295
|
-
attempt = 0;
|
|
221
|
+
attempt = 0;
|
|
296
222
|
handleSnapshot(snapshot);
|
|
297
223
|
},
|
|
298
224
|
onError: (error) => {
|
|
@@ -322,8 +248,6 @@ export function InFlightUploadsProvider(props) {
|
|
|
322
248
|
clearTimeout(retryTimer);
|
|
323
249
|
unsubscribe?.();
|
|
324
250
|
};
|
|
325
|
-
// adapter is intentionally NOT in the deps array — adapter is read via
|
|
326
|
-
// adapterRef on every snapshot, so identity changes don't re-subscribe.
|
|
327
251
|
}, [userId, subscribe, restarts]);
|
|
328
252
|
const retry = useCallback(() => {
|
|
329
253
|
restartRequestedRef.current = true;
|
|
@@ -333,9 +257,6 @@ export function InFlightUploadsProvider(props) {
|
|
|
333
257
|
const value = useMemo(() => ({ uploads, sourceState, retry, retrying }), [uploads, sourceState, retry, retrying]);
|
|
334
258
|
return (_jsx(InFlightUploadsContext.Provider, { value: value, children: children }));
|
|
335
259
|
}
|
|
336
|
-
// =============================================================================
|
|
337
|
-
// Read APIs
|
|
338
|
-
// =============================================================================
|
|
339
260
|
export function useInFlightUploadsState() {
|
|
340
261
|
const ctx = useContext(InFlightUploadsContext);
|
|
341
262
|
if (ctx === undefined) {
|
|
@@ -352,12 +273,6 @@ export function useInFlightUpload(id) {
|
|
|
352
273
|
return undefined;
|
|
353
274
|
return ctx.uploads.get(id);
|
|
354
275
|
}
|
|
355
|
-
/**
|
|
356
|
-
* Metadata-derived source state of the uploads listener
|
|
357
|
-
* (`connecting | live | offline | error`). The Files tray uses this to show a
|
|
358
|
-
* degraded indicator and never display "all caught up" while the source is
|
|
359
|
-
* `offline`/`error`/`connecting`.
|
|
360
|
-
*/
|
|
361
276
|
export function useUploadsSourceState() {
|
|
362
277
|
const ctx = useContext(InFlightUploadsContext);
|
|
363
278
|
if (ctx === undefined) {
|
|
@@ -365,10 +280,6 @@ export function useUploadsSourceState() {
|
|
|
365
280
|
}
|
|
366
281
|
return ctx.sourceState;
|
|
367
282
|
}
|
|
368
|
-
/**
|
|
369
|
-
* The uploads listener's restart — the Try again of a failure shown on `useUploadsSourceState` —
|
|
370
|
-
* and whether it is running.
|
|
371
|
-
*/
|
|
372
283
|
export function useUploadsSourceRetry() {
|
|
373
284
|
const ctx = useContext(InFlightUploadsContext);
|
|
374
285
|
if (ctx === undefined) {
|
|
@@ -376,24 +287,9 @@ export function useUploadsSourceRetry() {
|
|
|
376
287
|
}
|
|
377
288
|
return { retry: ctx.retry, retrying: ctx.retrying };
|
|
378
289
|
}
|
|
379
|
-
// =============================================================================
|
|
380
|
-
// Tray helpers
|
|
381
|
-
// =============================================================================
|
|
382
|
-
/**
|
|
383
|
-
* `true` for `completed` / `failed` / `rejected`. Used by tray UI to decide
|
|
384
|
-
* whether a Clear action is available for an item.
|
|
385
|
-
*/
|
|
386
290
|
export function isTerminalUpload(item) {
|
|
387
291
|
return isPendingMediaTerminalStatus(item.status);
|
|
388
292
|
}
|
|
389
|
-
/**
|
|
390
|
-
* Tray-visible items. Includes every pending/processing upload, plus terminal
|
|
391
|
-
* uploads that have NOT been cleared from the tray. Sorted newest-first by
|
|
392
|
-
* createdAt.
|
|
393
|
-
*
|
|
394
|
-
* The public selector name in upload-ui is `useUploadActivityState`. The
|
|
395
|
-
* mutation hook is `useClearUploadActivity` (in a separate file).
|
|
396
|
-
*/
|
|
397
293
|
export function useUploadActivityState() {
|
|
398
294
|
const uploads = useInFlightUploadsState();
|
|
399
295
|
return useMemo(() => {
|
|
@@ -406,29 +302,10 @@ export function useUploadActivityState() {
|
|
|
406
302
|
.sort((a, b) => b.createdAt - a.createdAt);
|
|
407
303
|
}, [uploads]);
|
|
408
304
|
}
|
|
409
|
-
//
|
|
410
|
-
//
|
|
411
|
-
// =============================================================================
|
|
412
|
-
/**
|
|
413
|
-
* Default Firestore subscription using the consumer's `firebase/firestore`
|
|
414
|
-
* module instance (via static ESM import at the top of this file).
|
|
415
|
-
*
|
|
416
|
-
* Using static ESM import — NOT `require()` — is load-bearing. CommonJS
|
|
417
|
-
* `require('firebase/firestore')` and ESM `import 'firebase/firestore'` resolve
|
|
418
|
-
* to different module instances under Next.js + Turbopack. If the consumer
|
|
419
|
-
* constructs `db` via ESM and we receive it here, but we then call
|
|
420
|
-
* `collection()` from a CJS-loaded copy of firebase, the firebase runtime
|
|
421
|
-
* does not recognize the db as a Firestore instance and throws
|
|
422
|
-
* "Expected first argument to collection() to be a CollectionReference,
|
|
423
|
-
* a DocumentReference or FirebaseFirestore".
|
|
424
|
-
*
|
|
425
|
-
* Consumers who want to skip the firebase code path entirely (tests, alt
|
|
426
|
-
* backends) supply their own `subscribe` prop to `InFlightUploadsProvider`.
|
|
427
|
-
*/
|
|
305
|
+
// A static ESM import, never require(): under Next.js + Turbopack a CJS-loaded firebase/firestore
|
|
306
|
+
// is another module instance and rejects a db built through ESM.
|
|
428
307
|
const defaultFirestoreSubscribe = (args) => {
|
|
429
308
|
const q = query(collection(args.db, args.collectionPath), where('userId', '==', args.userId), where('createdAt', '>=', args.windowStartMs));
|
|
430
|
-
// includeMetadataChanges so the provider can derive a `connecting|live|offline|error`
|
|
431
|
-
// source state from `snapshot.metadata.fromCache` (the Files tray degraded indicator).
|
|
432
309
|
return onSnapshot(q, { includeMetadataChanges: true }, args.onSnapshot, args.onError);
|
|
433
310
|
};
|
|
434
311
|
//# sourceMappingURL=in-flight-uploads-provider.js.map
|