@arcadiasystems/morse-uploader 0.1.0

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.
@@ -0,0 +1,562 @@
1
+ import { createContext, useContext, useState, useRef, useCallback, useEffect, useMemo } from 'react';
2
+ import { buildRecipientFileSealId, SealError, uploadEncryptedRecipientFileFromBytes, uploadRecipientFileFromBytes } from '@arcadiasystems/morse-sdk';
3
+ import { SessionKey } from '@mysten/seal';
4
+
5
+ // src/constants/default-encryption-overhead-bytes.ts
6
+ var DEFAULT_ENCRYPTION_OVERHEAD_BYTES = 150;
7
+
8
+ // src/utils/bytes-to-hex.ts
9
+ function bytesToHex(bytes) {
10
+ let out = "";
11
+ for (const byte of bytes) {
12
+ out += byte.toString(16).padStart(2, "0");
13
+ }
14
+ return out;
15
+ }
16
+
17
+ // src/utils/resolve-origin.ts
18
+ function resolveOrigin() {
19
+ if (typeof window === "undefined" || typeof window.location === "undefined") {
20
+ return null;
21
+ }
22
+ const { origin } = window.location;
23
+ return origin.length > 0 ? origin : null;
24
+ }
25
+
26
+ // src/utils/trim-trailing-slashes.ts
27
+ function trimTrailingSlashes(value) {
28
+ return value.replace(/\/+$/, "");
29
+ }
30
+
31
+ // src/files/build-share-link.ts
32
+ var DEFAULT_PATTERN = "/#/f/:fileId";
33
+ function buildShareLink(args) {
34
+ const base = trimTrailingSlashes(args.baseUrl ?? resolveOrigin() ?? "");
35
+ const path = (args.pattern ?? DEFAULT_PATTERN).replace(
36
+ ":fileId",
37
+ encodeURIComponent(args.fileId)
38
+ );
39
+ const hasSeal = args.sealIdPrefix !== void 0 && args.sealNonce !== void 0;
40
+ const seal = hasSeal ? `?p=${bytesToHex(args.sealIdPrefix)}&n=${bytesToHex(
41
+ args.sealNonce
42
+ )}` : "";
43
+ return `${base}${path}${seal}`;
44
+ }
45
+
46
+ // src/files/epoch-duration-ms.ts
47
+ var ONE_DAY_MS = 24 * 60 * 60 * 1e3;
48
+ var EPOCH_DURATION_MS = {
49
+ testnet: ONE_DAY_MS,
50
+ mainnet: 14 * ONE_DAY_MS
51
+ };
52
+ function epochDurationMs(network) {
53
+ return EPOCH_DURATION_MS[network];
54
+ }
55
+
56
+ // src/files/estimate-storage-usd.ts
57
+ var DEFAULT_USD_PER_GB_PER_MONTH = 0.023;
58
+ var BYTES_PER_GB = 1e9;
59
+ var MS_PER_DAY = 24 * 60 * 60 * 1e3;
60
+ var MS_PER_MONTH = 30 * MS_PER_DAY;
61
+ function estimateStorageUsd(args) {
62
+ const sizeBytes = args.sizeBytes + (args.encryptionOverheadBytes ?? 0);
63
+ const gb = sizeBytes / BYTES_PER_GB;
64
+ const totalMs = args.epochs * args.epochDurationMs;
65
+ const months = totalMs / MS_PER_MONTH;
66
+ const days = totalMs / MS_PER_DAY;
67
+ const rate = args.usdPerGbPerMonth ?? DEFAULT_USD_PER_GB_PER_MONTH;
68
+ return { usd: gb * months * rate, gb, months, days, sizeBytes };
69
+ }
70
+
71
+ // src/files/upload-phase-label.ts
72
+ var LABELS = {
73
+ encrypting: "Encrypting",
74
+ uploading: "Uploading to Walrus",
75
+ submitting: "Submitting transaction",
76
+ complete: "Complete"
77
+ };
78
+ function uploadPhaseLabel(phase) {
79
+ return LABELS[phase];
80
+ }
81
+
82
+ // src/files/upload-phases.ts
83
+ var ENCRYPTED_PHASES = [
84
+ "encrypting",
85
+ "uploading",
86
+ "submitting",
87
+ "complete"
88
+ ];
89
+ var PUBLIC_PHASES = [
90
+ "uploading",
91
+ "submitting",
92
+ "complete"
93
+ ];
94
+ function uploadPhases(encrypted) {
95
+ return encrypted ? ENCRYPTED_PHASES : PUBLIC_PHASES;
96
+ }
97
+ var MorseFilesContext = createContext(null);
98
+
99
+ // src/files/use-morse-files-context.ts
100
+ function useMorseFilesContext() {
101
+ const handles = useContext(MorseFilesContext);
102
+ if (handles === null) {
103
+ throw new Error(
104
+ "useMorseFilesContext must be used within a MorseFilesProvider."
105
+ );
106
+ }
107
+ return handles;
108
+ }
109
+ var DEFAULT_TTL_MIN = 10;
110
+ function useFileDecrypt() {
111
+ const { seal, walrusRead, signer, client, config, address } = useMorseFilesContext();
112
+ const [status, setStatus] = useState("idle");
113
+ const [data, setData] = useState(null);
114
+ const [error, setError] = useState(null);
115
+ const sessionRef = useRef(null);
116
+ const reset = useCallback(() => {
117
+ setStatus("idle");
118
+ setData(null);
119
+ setError(null);
120
+ }, []);
121
+ const decrypt = useCallback(
122
+ async (input) => {
123
+ setStatus("working");
124
+ setData(null);
125
+ setError(null);
126
+ const newSession = () => SessionKey.create({
127
+ address,
128
+ packageId: config.originalPackageId ?? config.packageId,
129
+ ttlMin: input.ttlMin ?? DEFAULT_TTL_MIN,
130
+ signer,
131
+ suiClient: client
132
+ });
133
+ const getSession = async () => {
134
+ if (sessionRef.current !== null) {
135
+ return sessionRef.current;
136
+ }
137
+ const key = await newSession();
138
+ sessionRef.current = key;
139
+ return key;
140
+ };
141
+ try {
142
+ const ciphertext = await walrusRead.readBlob(input.file.blobId);
143
+ const options = {
144
+ sealId: buildRecipientFileSealId(input.sealIdPrefix, input.sealNonce),
145
+ fileId: input.file.id
146
+ };
147
+ try {
148
+ const plaintext = await seal.decryptUnderRecipientFile(ciphertext, {
149
+ ...options,
150
+ sessionKey: await getSession()
151
+ });
152
+ setData(plaintext);
153
+ setStatus("success");
154
+ return plaintext;
155
+ } catch (err) {
156
+ if (err instanceof SealError && err.code === "session-expired") {
157
+ sessionRef.current = null;
158
+ const plaintext = await seal.decryptUnderRecipientFile(ciphertext, {
159
+ ...options,
160
+ sessionKey: await getSession()
161
+ });
162
+ setData(plaintext);
163
+ setStatus("success");
164
+ return plaintext;
165
+ }
166
+ throw err;
167
+ }
168
+ } catch (err) {
169
+ setError(err);
170
+ setStatus("error");
171
+ throw err;
172
+ }
173
+ },
174
+ [seal, walrusRead, signer, client, config, address]
175
+ );
176
+ return { status, data, error, decrypt, reset };
177
+ }
178
+
179
+ // src/utils/is-previewable-image.ts
180
+ var PREVIEWABLE_IMAGE_TYPES = /* @__PURE__ */ new Set([
181
+ "image/png",
182
+ "image/jpeg",
183
+ "image/gif",
184
+ "image/webp",
185
+ "image/avif",
186
+ "image/svg+xml"
187
+ ]);
188
+ function isPreviewableImage(contentType) {
189
+ return PREVIEWABLE_IMAGE_TYPES.has(contentType.toLowerCase());
190
+ }
191
+
192
+ // src/utils/trigger-download.ts
193
+ function triggerDownload(objectUrl, filename) {
194
+ if (typeof document === "undefined") {
195
+ return;
196
+ }
197
+ const anchor = document.createElement("a");
198
+ anchor.href = objectUrl;
199
+ anchor.download = filename;
200
+ anchor.rel = "noopener";
201
+ document.body.appendChild(anchor);
202
+ anchor.click();
203
+ anchor.remove();
204
+ }
205
+ function useRecipientFile(fileId) {
206
+ const { reader } = useMorseFilesContext();
207
+ const [status, setStatus] = useState("idle");
208
+ const [file, setFile] = useState(null);
209
+ const [error, setError] = useState(null);
210
+ const refresh = useCallback(async () => {
211
+ if (fileId === null) {
212
+ return null;
213
+ }
214
+ setStatus("loading");
215
+ setError(null);
216
+ try {
217
+ const loaded = await reader.getRecipientFile(fileId);
218
+ setFile(loaded);
219
+ setStatus("success");
220
+ return loaded;
221
+ } catch (err) {
222
+ setError(err);
223
+ setStatus("error");
224
+ throw err;
225
+ }
226
+ }, [reader, fileId]);
227
+ useEffect(() => {
228
+ if (fileId !== null) {
229
+ refresh().catch(() => void 0);
230
+ }
231
+ }, [refresh, fileId]);
232
+ return { status, file, error, refresh };
233
+ }
234
+ function useFileDownload(fileId, seal) {
235
+ const { walrusRead } = useMorseFilesContext();
236
+ const { status: metaStatus, file } = useRecipientFile(fileId);
237
+ const { decrypt } = useFileDecrypt();
238
+ const [status, setStatus] = useState("idle");
239
+ const [objectUrl, setObjectUrl] = useState(null);
240
+ const [error, setError] = useState(null);
241
+ const urlRef = useRef(null);
242
+ useEffect(() => {
243
+ if (objectUrl === null) {
244
+ return;
245
+ }
246
+ return () => URL.revokeObjectURL(objectUrl);
247
+ }, [objectUrl]);
248
+ const encrypted = seal !== null;
249
+ const isImage = file === null ? false : isPreviewableImage(file.contentType);
250
+ const fetchToUrl = useCallback(async () => {
251
+ if (file === null) {
252
+ return null;
253
+ }
254
+ setStatus("loading");
255
+ setError(null);
256
+ try {
257
+ let bytes;
258
+ if (seal !== null) {
259
+ bytes = await decrypt({
260
+ file,
261
+ sealIdPrefix: seal.sealIdPrefix,
262
+ sealNonce: seal.sealNonce
263
+ });
264
+ } else {
265
+ bytes = await walrusRead.readBlob(file.blobId);
266
+ }
267
+ const url = URL.createObjectURL(
268
+ new Blob([bytes], { type: file.contentType })
269
+ );
270
+ urlRef.current = url;
271
+ setObjectUrl(url);
272
+ setStatus("ready");
273
+ return url;
274
+ } catch (err) {
275
+ setError(err);
276
+ setStatus("error");
277
+ return null;
278
+ }
279
+ }, [file, seal, decrypt, walrusRead]);
280
+ const load = useCallback(async () => {
281
+ await fetchToUrl();
282
+ }, [fetchToUrl]);
283
+ const save = useCallback(() => {
284
+ if (urlRef.current !== null && file !== null) {
285
+ triggerDownload(urlRef.current, file.name);
286
+ }
287
+ }, [file]);
288
+ const download = useCallback(async () => {
289
+ const url = urlRef.current ?? await fetchToUrl();
290
+ if (url !== null && file !== null) {
291
+ triggerDownload(url, file.name);
292
+ }
293
+ }, [fetchToUrl, file]);
294
+ return {
295
+ metaStatus,
296
+ file,
297
+ status,
298
+ objectUrl,
299
+ encrypted,
300
+ isImage,
301
+ error,
302
+ load,
303
+ save,
304
+ download
305
+ };
306
+ }
307
+ var FALLBACK_CONTENT_TYPE = "application/octet-stream";
308
+ function useFileUpload() {
309
+ const { adapter, config, walrusWrite, seal } = useMorseFilesContext();
310
+ const [status, setStatus] = useState("idle");
311
+ const [phase, setPhase] = useState(null);
312
+ const [data, setData] = useState(null);
313
+ const [error, setError] = useState(null);
314
+ const reset = useCallback(() => {
315
+ setStatus("idle");
316
+ setPhase(null);
317
+ setData(null);
318
+ setError(null);
319
+ }, []);
320
+ const upload = useCallback(
321
+ async (input) => {
322
+ setStatus("uploading");
323
+ setPhase(null);
324
+ setData(null);
325
+ setError(null);
326
+ try {
327
+ const bytes = new Uint8Array(await input.file.arrayBuffer());
328
+ const contentType = input.file.type || FALLBACK_CONTENT_TYPE;
329
+ const recipients = input.recipients ?? [];
330
+ const uploadOptions = {
331
+ epochs: input.epochs,
332
+ deletable: input.deletable ?? true
333
+ };
334
+ const onProgress = (event) => setPhase(event.phase);
335
+ let completed;
336
+ if (input.encrypt === true) {
337
+ const result = await uploadEncryptedRecipientFileFromBytes(
338
+ adapter,
339
+ config,
340
+ {
341
+ walrus: walrusWrite,
342
+ seal,
343
+ plaintext: bytes,
344
+ recipients,
345
+ name: input.file.name,
346
+ contentType,
347
+ upload: uploadOptions,
348
+ onProgress
349
+ }
350
+ );
351
+ completed = {
352
+ fileId: result.fileId,
353
+ blobId: result.blobId,
354
+ encrypted: true,
355
+ sealIdPrefix: result.sealIdPrefix,
356
+ sealNonce: result.sealNonce,
357
+ shareLink: buildShareLink({
358
+ fileId: result.fileId,
359
+ sealIdPrefix: result.sealIdPrefix,
360
+ sealNonce: result.sealNonce
361
+ })
362
+ };
363
+ } else {
364
+ const result = await uploadRecipientFileFromBytes(adapter, config, {
365
+ walrus: walrusWrite,
366
+ bytes,
367
+ recipients,
368
+ name: input.file.name,
369
+ contentType,
370
+ upload: uploadOptions,
371
+ onProgress
372
+ });
373
+ completed = {
374
+ fileId: result.fileId,
375
+ blobId: result.blobId,
376
+ encrypted: false,
377
+ sealIdPrefix: null,
378
+ sealNonce: null,
379
+ shareLink: buildShareLink({ fileId: result.fileId })
380
+ };
381
+ }
382
+ setData(completed);
383
+ setStatus("success");
384
+ return completed;
385
+ } catch (err) {
386
+ setError(err);
387
+ setStatus("error");
388
+ throw err;
389
+ }
390
+ },
391
+ [adapter, config, walrusWrite, seal]
392
+ );
393
+ return { status, phase, data, error, upload, reset };
394
+ }
395
+ function useStorageCostEstimate(options) {
396
+ const { network } = useMorseFilesContext();
397
+ const { sizeBytes, epochs, encrypt, usdPerGbPerMonth } = options;
398
+ const estimate = useMemo(
399
+ () => estimateStorageUsd({
400
+ sizeBytes,
401
+ epochs,
402
+ epochDurationMs: epochDurationMs(network),
403
+ encryptionOverheadBytes: encrypt ? DEFAULT_ENCRYPTION_OVERHEAD_BYTES : 0,
404
+ ...usdPerGbPerMonth === void 0 ? {} : { usdPerGbPerMonth }
405
+ }),
406
+ [network, sizeBytes, epochs, encrypt, usdPerGbPerMonth]
407
+ );
408
+ return { estimate };
409
+ }
410
+ function useClipboard(resetMs = 2e3) {
411
+ const [copied, setCopied] = useState(false);
412
+ const timer = useRef(void 0);
413
+ useEffect(() => () => clearTimeout(timer.current), []);
414
+ const copy = useCallback(
415
+ async (text) => {
416
+ if (typeof navigator === "undefined" || navigator.clipboard === void 0) {
417
+ return false;
418
+ }
419
+ try {
420
+ await navigator.clipboard.writeText(text);
421
+ } catch {
422
+ return false;
423
+ }
424
+ setCopied(true);
425
+ clearTimeout(timer.current);
426
+ timer.current = setTimeout(() => setCopied(false), resetMs);
427
+ return true;
428
+ },
429
+ [resetMs]
430
+ );
431
+ return { copied, copy };
432
+ }
433
+ function useDropzone(options) {
434
+ const {
435
+ onFiles,
436
+ accept,
437
+ multiple = true,
438
+ disabled = false,
439
+ ariaLabel
440
+ } = options;
441
+ const inputRef = useRef(null);
442
+ const [isDragging, setIsDragging] = useState(false);
443
+ const open = useCallback(() => {
444
+ if (disabled) {
445
+ return;
446
+ }
447
+ inputRef.current?.click();
448
+ }, [disabled]);
449
+ const emit = useCallback(
450
+ (list) => {
451
+ if (list === null || list.length === 0) {
452
+ return;
453
+ }
454
+ onFiles(Array.from(list));
455
+ },
456
+ [onFiles]
457
+ );
458
+ const getRootProps = useCallback(
459
+ () => ({
460
+ role: "button",
461
+ tabIndex: disabled ? -1 : 0,
462
+ "aria-disabled": disabled,
463
+ "data-dragging": isDragging ? "true" : void 0,
464
+ ...ariaLabel === void 0 ? {} : { "aria-label": ariaLabel },
465
+ onClick: () => open(),
466
+ onKeyDown: (event) => {
467
+ if (disabled) {
468
+ return;
469
+ }
470
+ if (event.key === "Enter" || event.key === " ") {
471
+ event.preventDefault();
472
+ open();
473
+ }
474
+ },
475
+ onDragEnter: (event) => {
476
+ if (disabled) {
477
+ return;
478
+ }
479
+ event.preventDefault();
480
+ setIsDragging(true);
481
+ },
482
+ onDragOver: (event) => {
483
+ if (!disabled) {
484
+ event.preventDefault();
485
+ }
486
+ },
487
+ onDragLeave: (event) => {
488
+ if (disabled) {
489
+ return;
490
+ }
491
+ event.preventDefault();
492
+ setIsDragging(false);
493
+ },
494
+ onDrop: (event) => {
495
+ if (disabled) {
496
+ return;
497
+ }
498
+ event.preventDefault();
499
+ setIsDragging(false);
500
+ emit(event.dataTransfer?.files ?? null);
501
+ }
502
+ }),
503
+ [disabled, isDragging, open, emit, ariaLabel]
504
+ );
505
+ const getInputProps = useCallback(
506
+ () => ({
507
+ ref: inputRef,
508
+ type: "file",
509
+ multiple,
510
+ tabIndex: -1,
511
+ disabled,
512
+ ...accept === void 0 ? {} : { accept },
513
+ onChange: (event) => emit(event.target.files),
514
+ // The root's onClick calls input.click(); stop that programmatic click
515
+ // from bubbling back to the root and re-opening the dialog.
516
+ onClick: (event) => event.stopPropagation()
517
+ }),
518
+ [accept, multiple, disabled, emit]
519
+ );
520
+ return { isDragging, open, getRootProps, getInputProps, inputRef };
521
+ }
522
+
523
+ // src/utils/format-bytes.ts
524
+ var UNITS = ["B", "KB", "MB", "GB", "TB", "PB"];
525
+ var BASE = 1024;
526
+ function formatBytes(bytes, fractionDigits = 2) {
527
+ if (!Number.isFinite(bytes) || bytes <= 0) {
528
+ return "0 B";
529
+ }
530
+ const exponent = Math.min(
531
+ Math.floor(Math.log(bytes) / Math.log(BASE)),
532
+ UNITS.length - 1
533
+ );
534
+ const value = bytes / BASE ** exponent;
535
+ const unit = UNITS[exponent] ?? "B";
536
+ if (exponent === 0) {
537
+ return `${value} ${unit}`;
538
+ }
539
+ return `${value.toFixed(fractionDigits)} ${unit}`;
540
+ }
541
+
542
+ // src/utils/format-usd.ts
543
+ var TINY_FLOOR = 1e-6;
544
+ function formatUsd(usd) {
545
+ if (usd <= 0) {
546
+ return "$0.00";
547
+ }
548
+ if (usd >= 0.01) {
549
+ return `$${usd.toFixed(2)}`;
550
+ }
551
+ if (usd < TINY_FLOOR) {
552
+ return `< $${TINY_FLOOR}`;
553
+ }
554
+ return `$${usd.toLocaleString("en-US", {
555
+ maximumSignificantDigits: 2,
556
+ useGrouping: false
557
+ })}`;
558
+ }
559
+
560
+ export { DEFAULT_ENCRYPTION_OVERHEAD_BYTES, MorseFilesContext, buildShareLink, bytesToHex, epochDurationMs, estimateStorageUsd, formatBytes, formatUsd, isPreviewableImage, resolveOrigin, triggerDownload, uploadPhaseLabel, uploadPhases, useClipboard, useDropzone, useFileDecrypt, useFileDownload, useFileUpload, useMorseFilesContext, useRecipientFile, useStorageCostEstimate };
561
+ //# sourceMappingURL=chunk-2FYBTRTH.js.map
562
+ //# sourceMappingURL=chunk-2FYBTRTH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/constants/default-encryption-overhead-bytes.ts","../src/utils/bytes-to-hex.ts","../src/utils/resolve-origin.ts","../src/utils/trim-trailing-slashes.ts","../src/files/build-share-link.ts","../src/files/epoch-duration-ms.ts","../src/files/estimate-storage-usd.ts","../src/files/upload-phase-label.ts","../src/files/upload-phases.ts","../src/files/context.ts","../src/files/use-morse-files-context.ts","../src/files/use-file-decrypt.ts","../src/utils/is-previewable-image.ts","../src/utils/trigger-download.ts","../src/files/use-recipient-file.ts","../src/files/use-file-download.ts","../src/files/use-file-upload.ts","../src/files/use-storage-cost-estimate.ts","../src/primitives/use-clipboard.ts","../src/primitives/use-dropzone.ts","../src/utils/format-bytes.ts","../src/utils/format-usd.ts"],"names":["useState","useCallback","useRef","useEffect"],"mappings":"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","file":"chunk-2FYBTRTH.js","sourcesContent":["/**\n * Approximate bytes the Seal envelope adds to a ciphertext, used to nudge the\n * cost estimate when encryption is enabled. Small relative to most files;\n * consumers override per-call via `encryptionOverheadBytes`.\n */\nexport const DEFAULT_ENCRYPTION_OVERHEAD_BYTES = 150;\n","/** Encode bytes as a lowercase hex string (no `0x` prefix). */\nexport function bytesToHex(bytes: Uint8Array): string {\n\tlet out = \"\";\n\tfor (const byte of bytes) {\n\t\tout += byte.toString(16).padStart(2, \"0\");\n\t}\n\treturn out;\n}\n","/**\n * Return the current browser origin (e.g. \"https://app.example.com\"), or null\n * when running outside a browser (SSR, Node, web worker without a location).\n *\n * SSR-safe: never throws on a missing `window`.\n */\nexport function resolveOrigin(): string | null {\n\t// `typeof` guards are the SSR-safe way to probe globals that may be absent.\n\tif (typeof window === \"undefined\" || typeof window.location === \"undefined\") {\n\t\treturn null;\n\t}\n\tconst { origin } = window.location;\n\treturn origin.length > 0 ? origin : null;\n}\n","/** Remove any trailing slashes from a URL or path segment. */\nexport function trimTrailingSlashes(value: string): string {\n\treturn value.replace(/\\/+$/, \"\");\n}\n","import { bytesToHex } from \"../utils/bytes-to-hex.js\";\nimport { resolveOrigin } from \"../utils/resolve-origin.js\";\nimport { trimTrailingSlashes } from \"../utils/trim-trailing-slashes.js\";\nimport type { BuildShareLinkArgs } from \"./types.js\";\n\nconst DEFAULT_PATTERN = \"/#/f/:fileId\";\n\n/**\n * Build a share link for a RecipientFile. The fileId goes in the URL fragment\n * and the Seal material (prefix + nonce, if any) as hex `p`/`n` queries inside\n * the fragment, so they never reach a server log. The seal material is not a\n * secret: decryption is still gated by the file's recipient set. SSR-safe\n * (falls back to an empty base outside a browser).\n */\nexport function buildShareLink(args: BuildShareLinkArgs): string {\n\tconst base = trimTrailingSlashes(args.baseUrl ?? resolveOrigin() ?? \"\");\n\tconst path = (args.pattern ?? DEFAULT_PATTERN).replace(\n\t\t\":fileId\",\n\t\tencodeURIComponent(args.fileId),\n\t);\n\tconst hasSeal =\n\t\targs.sealIdPrefix !== undefined && args.sealNonce !== undefined;\n\tconst seal = hasSeal\n\t\t? `?p=${bytesToHex(args.sealIdPrefix as Uint8Array)}&n=${bytesToHex(\n\t\t\t\targs.sealNonce as Uint8Array,\n\t\t\t)}`\n\t\t: \"\";\n\treturn `${base}${path}${seal}`;\n}\n","/** One day in milliseconds. */\nconst ONE_DAY_MS = 24 * 60 * 60 * 1000;\n\n/** Walrus epoch length per network: testnet ~1 day, mainnet ~2 weeks. */\nconst EPOCH_DURATION_MS: Record<\"testnet\" | \"mainnet\", number> = {\n\ttestnet: ONE_DAY_MS,\n\tmainnet: 14 * ONE_DAY_MS,\n};\n\n/**\n * Nominal Walrus epoch length in milliseconds for a network. Used by the USD\n * cost estimate to convert an epoch count into months. These are the documented\n * nominal lengths (testnet 1 day, mainnet 2 weeks), not a live chain read.\n */\nexport function epochDurationMs(network: \"testnet\" | \"mainnet\"): number {\n\treturn EPOCH_DURATION_MS[network];\n}\n","import type { UsdCostArgs, UsdCostEstimate } from \"./types.js\";\n\n// Walrus predictable pricing: $0.023 per GB per month, paid in WAL but pegged.\n// https://blog.walrus.xyz/announcing-predictable-pricing-in-usd-on-walrus/\nconst DEFAULT_USD_PER_GB_PER_MONTH = 0.023;\nconst BYTES_PER_GB = 1_000_000_000;\nconst MS_PER_DAY = 24 * 60 * 60 * 1000;\nconst MS_PER_MONTH = 30 * MS_PER_DAY;\n\n/**\n * Estimate Walrus storage cost in USD from a blob size and a storage duration.\n * Duration is given in epochs plus the epoch length (read from chain), since an\n * epoch is 1 day on testnet and 2 weeks on mainnet. Encryption overhead can be\n * folded in so the encrypt toggle moves the figure.\n */\nexport function estimateStorageUsd(args: UsdCostArgs): UsdCostEstimate {\n\tconst sizeBytes = args.sizeBytes + (args.encryptionOverheadBytes ?? 0);\n\tconst gb = sizeBytes / BYTES_PER_GB;\n\tconst totalMs = args.epochs * args.epochDurationMs;\n\tconst months = totalMs / MS_PER_MONTH;\n\tconst days = totalMs / MS_PER_DAY;\n\tconst rate = args.usdPerGbPerMonth ?? DEFAULT_USD_PER_GB_PER_MONTH;\n\treturn { usd: gb * months * rate, gb, months, days, sizeBytes };\n}\n","import type { FilePhase } from \"./types.js\";\n\nconst LABELS: Record<FilePhase, string> = {\n\tencrypting: \"Encrypting\",\n\tuploading: \"Uploading to Walrus\",\n\tsubmitting: \"Submitting transaction\",\n\tcomplete: \"Complete\",\n};\n\n/** Human-readable label for an upload phase, for the progress stepper. */\nexport function uploadPhaseLabel(phase: FilePhase): string {\n\treturn LABELS[phase];\n}\n","import type { FilePhase } from \"./types.js\";\n\nconst ENCRYPTED_PHASES: readonly FilePhase[] = [\n\t\"encrypting\",\n\t\"uploading\",\n\t\"submitting\",\n\t\"complete\",\n];\nconst PUBLIC_PHASES: readonly FilePhase[] = [\n\t\"uploading\",\n\t\"submitting\",\n\t\"complete\",\n];\n\n/**\n * The ordered upload phases for the progress stepper. Public uploads skip the\n * `encrypting` phase since there is no Seal step.\n */\nexport function uploadPhases(encrypted: boolean): readonly FilePhase[] {\n\treturn encrypted ? ENCRYPTED_PHASES : PUBLIC_PHASES;\n}\n","import { createContext } from \"react\";\nimport type { MorseFilesHandles } from \"./types.js\";\n\n/**\n * Holds the injected SDK handles for the files flow. Null until a\n * {@link MorseFilesProvider} supplies them; the consumer hook throws on null.\n */\nexport const MorseFilesContext = createContext<MorseFilesHandles | null>(null);\n","import { useContext } from \"react\";\nimport { MorseFilesContext } from \"./context.js\";\nimport type { MorseFilesHandles } from \"./types.js\";\n\n/**\n * Read the files-flow handles.\n *\n * @throws {Error} when used outside a {@link MorseFilesProvider}.\n */\nexport function useMorseFilesContext(): MorseFilesHandles {\n\tconst handles = useContext(MorseFilesContext);\n\tif (handles === null) {\n\t\tthrow new Error(\n\t\t\t\"useMorseFilesContext must be used within a MorseFilesProvider.\",\n\t\t);\n\t}\n\treturn handles;\n}\n","import { buildRecipientFileSealId, SealError } from \"@arcadiasystems/morse-sdk\";\nimport { SessionKey } from \"@mysten/seal\";\nimport { useCallback, useRef, useState } from \"react\";\nimport type {\n\tFileDecryptInput,\n\tFileDecryptStatus,\n\tUseFileDecryptResult,\n} from \"./types.js\";\nimport { useMorseFilesContext } from \"./use-morse-files-context.js\";\n\nconst DEFAULT_TTL_MIN = 10;\n\n/**\n * Decrypt + return the plaintext of an encrypted RecipientFile. Reads the\n * ciphertext from Walrus, creates (and reuses) a Seal SessionKey, and decrypts\n * under the file's recipient policy. Re-signs once on a session-expired error.\n * The SessionKey is reusable across files for the same package, so it is cached\n * for the lifetime of the hook.\n */\nexport function useFileDecrypt(): UseFileDecryptResult {\n\tconst { seal, walrusRead, signer, client, config, address } =\n\t\tuseMorseFilesContext();\n\tconst [status, setStatus] = useState<FileDecryptStatus>(\"idle\");\n\tconst [data, setData] = useState<Uint8Array | null>(null);\n\tconst [error, setError] = useState<unknown>(null);\n\tconst sessionRef = useRef<SessionKey | null>(null);\n\n\tconst reset = useCallback(() => {\n\t\tsetStatus(\"idle\");\n\t\tsetData(null);\n\t\tsetError(null);\n\t}, []);\n\n\tconst decrypt = useCallback(\n\t\tasync (input: FileDecryptInput): Promise<Uint8Array> => {\n\t\t\tsetStatus(\"working\");\n\t\t\tsetData(null);\n\t\t\tsetError(null);\n\n\t\t\tconst newSession = () =>\n\t\t\t\tSessionKey.create({\n\t\t\t\t\taddress,\n\t\t\t\t\tpackageId: config.originalPackageId ?? config.packageId,\n\t\t\t\t\tttlMin: input.ttlMin ?? DEFAULT_TTL_MIN,\n\t\t\t\t\tsigner,\n\t\t\t\t\tsuiClient: client,\n\t\t\t\t});\n\t\t\tconst getSession = async () => {\n\t\t\t\tif (sessionRef.current !== null) {\n\t\t\t\t\treturn sessionRef.current;\n\t\t\t\t}\n\t\t\t\tconst key = await newSession();\n\t\t\t\tsessionRef.current = key;\n\t\t\t\treturn key;\n\t\t\t};\n\n\t\t\ttry {\n\t\t\t\tconst ciphertext = await walrusRead.readBlob(input.file.blobId);\n\t\t\t\tconst options = {\n\t\t\t\t\tsealId: buildRecipientFileSealId(input.sealIdPrefix, input.sealNonce),\n\t\t\t\t\tfileId: input.file.id,\n\t\t\t\t};\n\t\t\t\ttry {\n\t\t\t\t\tconst plaintext = await seal.decryptUnderRecipientFile(ciphertext, {\n\t\t\t\t\t\t...options,\n\t\t\t\t\t\tsessionKey: await getSession(),\n\t\t\t\t\t});\n\t\t\t\t\tsetData(plaintext);\n\t\t\t\t\tsetStatus(\"success\");\n\t\t\t\t\treturn plaintext;\n\t\t\t\t} catch (err) {\n\t\t\t\t\t// Stale session: drop the cache, re-sign once, and retry.\n\t\t\t\t\tif (err instanceof SealError && err.code === \"session-expired\") {\n\t\t\t\t\t\tsessionRef.current = null;\n\t\t\t\t\t\tconst plaintext = await seal.decryptUnderRecipientFile(ciphertext, {\n\t\t\t\t\t\t\t...options,\n\t\t\t\t\t\t\tsessionKey: await getSession(),\n\t\t\t\t\t\t});\n\t\t\t\t\t\tsetData(plaintext);\n\t\t\t\t\t\tsetStatus(\"success\");\n\t\t\t\t\t\treturn plaintext;\n\t\t\t\t\t}\n\t\t\t\t\tthrow err;\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tsetError(err);\n\t\t\t\tsetStatus(\"error\");\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t},\n\t\t[seal, walrusRead, signer, client, config, address],\n\t);\n\n\treturn { status, data, error, decrypt, reset };\n}\n","/** Content types the downloader renders inline as an image preview. */\nconst PREVIEWABLE_IMAGE_TYPES = new Set([\n\t\"image/png\",\n\t\"image/jpeg\",\n\t\"image/gif\",\n\t\"image/webp\",\n\t\"image/avif\",\n\t\"image/svg+xml\",\n]);\n\n/** True when a content type is a browser-previewable image. */\nexport function isPreviewableImage(contentType: string): boolean {\n\treturn PREVIEWABLE_IMAGE_TYPES.has(contentType.toLowerCase());\n}\n","/**\n * Trigger a browser download of an object URL under a given filename. Creates a\n * transient anchor, clicks it, and removes it. No-op outside a DOM.\n */\nexport function triggerDownload(objectUrl: string, filename: string): void {\n\tif (typeof document === \"undefined\") {\n\t\treturn;\n\t}\n\tconst anchor = document.createElement(\"a\");\n\tanchor.href = objectUrl;\n\tanchor.download = filename;\n\tanchor.rel = \"noopener\";\n\tdocument.body.appendChild(anchor);\n\tanchor.click();\n\tanchor.remove();\n}\n","import type { RecipientFile, RecipientFileId } from \"@arcadiasystems/morse-sdk\";\nimport { useCallback, useEffect, useState } from \"react\";\nimport type { LoadStatus, UseRecipientFileResult } from \"./types.js\";\nimport { useMorseFilesContext } from \"./use-morse-files-context.js\";\n\n/** Load a RecipientFile's on-chain metadata by id. Loads on mount. */\nexport function useRecipientFile(\n\tfileId: RecipientFileId | null,\n): UseRecipientFileResult {\n\tconst { reader } = useMorseFilesContext();\n\tconst [status, setStatus] = useState<LoadStatus>(\"idle\");\n\tconst [file, setFile] = useState<RecipientFile | null>(null);\n\tconst [error, setError] = useState<unknown>(null);\n\n\tconst refresh = useCallback(async (): Promise<RecipientFile | null> => {\n\t\tif (fileId === null) {\n\t\t\treturn null;\n\t\t}\n\t\tsetStatus(\"loading\");\n\t\tsetError(null);\n\t\ttry {\n\t\t\tconst loaded = await reader.getRecipientFile(fileId);\n\t\t\tsetFile(loaded);\n\t\t\tsetStatus(\"success\");\n\t\t\treturn loaded;\n\t\t} catch (err) {\n\t\t\tsetError(err);\n\t\t\tsetStatus(\"error\");\n\t\t\tthrow err;\n\t\t}\n\t}, [reader, fileId]);\n\n\tuseEffect(() => {\n\t\tif (fileId !== null) {\n\t\t\trefresh().catch(() => undefined);\n\t\t}\n\t}, [refresh, fileId]);\n\n\treturn { status, file, error, refresh };\n}\n","import type { RecipientFileId } from \"@arcadiasystems/morse-sdk\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { isPreviewableImage } from \"../utils/is-previewable-image.js\";\nimport { triggerDownload } from \"../utils/trigger-download.js\";\nimport type {\n\tFileDownloadStatus,\n\tSealMaterial,\n\tUseFileDownloadResult,\n} from \"./types.js\";\nimport { useFileDecrypt } from \"./use-file-decrypt.js\";\nimport { useMorseFilesContext } from \"./use-morse-files-context.js\";\nimport { useRecipientFile } from \"./use-recipient-file.js\";\n\n/**\n * Drive the single-file download page. Loads the on-chain metadata, then\n * fetches the bytes on demand. A RecipientFile has no on-chain \"encrypted\"\n * flag, so the caller signals encryption by passing the Seal material from the\n * share link: when present, the bytes are decrypted (one SessionKey signature);\n * when null, they are read straight from Walrus as a public file.\n *\n * @param fileId The on-chain RecipientFile id (from the share link).\n * @param seal The Seal material (prefix + nonce) for encrypted files; null for public.\n */\nexport function useFileDownload(\n\tfileId: RecipientFileId | null,\n\tseal: SealMaterial | null,\n): UseFileDownloadResult {\n\tconst { walrusRead } = useMorseFilesContext();\n\tconst { status: metaStatus, file } = useRecipientFile(fileId);\n\tconst { decrypt } = useFileDecrypt();\n\tconst [status, setStatus] = useState<FileDownloadStatus>(\"idle\");\n\tconst [objectUrl, setObjectUrl] = useState<string | null>(null);\n\tconst [error, setError] = useState<unknown>(null);\n\tconst urlRef = useRef<string | null>(null);\n\n\t// Revoke the object URL when it is replaced or the component unmounts.\n\tuseEffect(() => {\n\t\tif (objectUrl === null) {\n\t\t\treturn;\n\t\t}\n\t\treturn () => URL.revokeObjectURL(objectUrl);\n\t}, [objectUrl]);\n\n\tconst encrypted = seal !== null;\n\tconst isImage = file === null ? false : isPreviewableImage(file.contentType);\n\n\t// Fetch the plaintext bytes and build an object URL. Shared by load/download.\n\tconst fetchToUrl = useCallback(async (): Promise<string | null> => {\n\t\tif (file === null) {\n\t\t\treturn null;\n\t\t}\n\t\tsetStatus(\"loading\");\n\t\tsetError(null);\n\t\ttry {\n\t\t\tlet bytes: Uint8Array;\n\t\t\tif (seal !== null) {\n\t\t\t\tbytes = await decrypt({\n\t\t\t\t\tfile,\n\t\t\t\t\tsealIdPrefix: seal.sealIdPrefix,\n\t\t\t\t\tsealNonce: seal.sealNonce,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tbytes = await walrusRead.readBlob(file.blobId);\n\t\t\t}\n\t\t\tconst url = URL.createObjectURL(\n\t\t\t\tnew Blob([bytes as BlobPart], { type: file.contentType }),\n\t\t\t);\n\t\t\turlRef.current = url;\n\t\t\tsetObjectUrl(url);\n\t\t\tsetStatus(\"ready\");\n\t\t\treturn url;\n\t\t} catch (err) {\n\t\t\tsetError(err);\n\t\t\tsetStatus(\"error\");\n\t\t\treturn null;\n\t\t}\n\t}, [file, seal, decrypt, walrusRead]);\n\n\tconst load = useCallback(async (): Promise<void> => {\n\t\tawait fetchToUrl();\n\t}, [fetchToUrl]);\n\n\tconst save = useCallback(() => {\n\t\tif (urlRef.current !== null && file !== null) {\n\t\t\ttriggerDownload(urlRef.current, file.name);\n\t\t}\n\t}, [file]);\n\n\tconst download = useCallback(async (): Promise<void> => {\n\t\tconst url = urlRef.current ?? (await fetchToUrl());\n\t\tif (url !== null && file !== null) {\n\t\t\ttriggerDownload(url, file.name);\n\t\t}\n\t}, [fetchToUrl, file]);\n\n\treturn {\n\t\tmetaStatus,\n\t\tfile,\n\t\tstatus,\n\t\tobjectUrl,\n\t\tencrypted,\n\t\tisImage,\n\t\terror,\n\t\tload,\n\t\tsave,\n\t\tdownload,\n\t};\n}\n","import {\n\tuploadEncryptedRecipientFileFromBytes,\n\tuploadRecipientFileFromBytes,\n} from \"@arcadiasystems/morse-sdk\";\nimport { useCallback, useState } from \"react\";\nimport { buildShareLink } from \"./build-share-link.js\";\nimport type {\n\tCompletedFileUpload,\n\tFilePhase,\n\tFileUploadInput,\n\tFileUploadStatus,\n\tUseFileUploadResult,\n} from \"./types.js\";\nimport { useMorseFilesContext } from \"./use-morse-files-context.js\";\n\nconst FALLBACK_CONTENT_TYPE = \"application/octet-stream\";\n\n/**\n * Upload one file as a RecipientFile. With `encrypt`, it encrypts under Seal\n * (only the recipients, plus the connected wallet, can decrypt); otherwise it\n * uploads a public file. Either way the recipient set is set at creation in two\n * wallet popups. Tracks the phase stepper and returns the fileId, the seal\n * material (encrypted only), and a share link.\n */\nexport function useFileUpload(): UseFileUploadResult {\n\tconst { adapter, config, walrusWrite, seal } = useMorseFilesContext();\n\tconst [status, setStatus] = useState<FileUploadStatus>(\"idle\");\n\tconst [phase, setPhase] = useState<FilePhase | null>(null);\n\tconst [data, setData] = useState<CompletedFileUpload | null>(null);\n\tconst [error, setError] = useState<unknown>(null);\n\n\tconst reset = useCallback(() => {\n\t\tsetStatus(\"idle\");\n\t\tsetPhase(null);\n\t\tsetData(null);\n\t\tsetError(null);\n\t}, []);\n\n\tconst upload = useCallback(\n\t\tasync (input: FileUploadInput): Promise<CompletedFileUpload> => {\n\t\t\tsetStatus(\"uploading\");\n\t\t\tsetPhase(null);\n\t\t\tsetData(null);\n\t\t\tsetError(null);\n\t\t\ttry {\n\t\t\t\tconst bytes = new Uint8Array(await input.file.arrayBuffer());\n\t\t\t\tconst contentType = input.file.type || FALLBACK_CONTENT_TYPE;\n\t\t\t\tconst recipients = input.recipients ?? [];\n\t\t\t\tconst uploadOptions = {\n\t\t\t\t\tepochs: input.epochs,\n\t\t\t\t\tdeletable: input.deletable ?? true,\n\t\t\t\t};\n\t\t\t\tconst onProgress = (event: { phase: FilePhase }) =>\n\t\t\t\t\tsetPhase(event.phase);\n\n\t\t\t\tlet completed: CompletedFileUpload;\n\t\t\t\tif (input.encrypt === true) {\n\t\t\t\t\tconst result = await uploadEncryptedRecipientFileFromBytes(\n\t\t\t\t\t\tadapter,\n\t\t\t\t\t\tconfig,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\twalrus: walrusWrite,\n\t\t\t\t\t\t\tseal,\n\t\t\t\t\t\t\tplaintext: bytes,\n\t\t\t\t\t\t\trecipients,\n\t\t\t\t\t\t\tname: input.file.name,\n\t\t\t\t\t\t\tcontentType,\n\t\t\t\t\t\t\tupload: uploadOptions,\n\t\t\t\t\t\t\tonProgress,\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t\tcompleted = {\n\t\t\t\t\t\tfileId: result.fileId,\n\t\t\t\t\t\tblobId: result.blobId,\n\t\t\t\t\t\tencrypted: true,\n\t\t\t\t\t\tsealIdPrefix: result.sealIdPrefix,\n\t\t\t\t\t\tsealNonce: result.sealNonce,\n\t\t\t\t\t\tshareLink: buildShareLink({\n\t\t\t\t\t\t\tfileId: result.fileId,\n\t\t\t\t\t\t\tsealIdPrefix: result.sealIdPrefix,\n\t\t\t\t\t\t\tsealNonce: result.sealNonce,\n\t\t\t\t\t\t}),\n\t\t\t\t\t};\n\t\t\t\t} else {\n\t\t\t\t\tconst result = await uploadRecipientFileFromBytes(adapter, config, {\n\t\t\t\t\t\twalrus: walrusWrite,\n\t\t\t\t\t\tbytes,\n\t\t\t\t\t\trecipients,\n\t\t\t\t\t\tname: input.file.name,\n\t\t\t\t\t\tcontentType,\n\t\t\t\t\t\tupload: uploadOptions,\n\t\t\t\t\t\tonProgress,\n\t\t\t\t\t});\n\t\t\t\t\tcompleted = {\n\t\t\t\t\t\tfileId: result.fileId,\n\t\t\t\t\t\tblobId: result.blobId,\n\t\t\t\t\t\tencrypted: false,\n\t\t\t\t\t\tsealIdPrefix: null,\n\t\t\t\t\t\tsealNonce: null,\n\t\t\t\t\t\tshareLink: buildShareLink({ fileId: result.fileId }),\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tsetData(completed);\n\t\t\t\tsetStatus(\"success\");\n\t\t\t\treturn completed;\n\t\t\t} catch (err) {\n\t\t\t\tsetError(err);\n\t\t\t\tsetStatus(\"error\");\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t},\n\t\t[adapter, config, walrusWrite, seal],\n\t);\n\n\treturn { status, phase, data, error, upload, reset };\n}\n","import { useMemo } from \"react\";\nimport { DEFAULT_ENCRYPTION_OVERHEAD_BYTES } from \"../constants/default-encryption-overhead-bytes.js\";\nimport { epochDurationMs } from \"./epoch-duration-ms.js\";\nimport { estimateStorageUsd } from \"./estimate-storage-usd.js\";\nimport type {\n\tUseStorageCostEstimateOptions,\n\tUseStorageCostEstimateResult,\n} from \"./types.js\";\nimport { useMorseFilesContext } from \"./use-morse-files-context.js\";\n\n/**\n * Deterministic USD storage-cost estimate for a pending upload. Uses Walrus\n * predictable pricing ($0.023/GB/month) with the network's nominal epoch length\n * from the active handles. When `encrypt` is set, the Seal envelope overhead is\n * folded into the priced size so the figure tracks the encrypt toggle.\n */\nexport function useStorageCostEstimate(\n\toptions: UseStorageCostEstimateOptions,\n): UseStorageCostEstimateResult {\n\tconst { network } = useMorseFilesContext();\n\tconst { sizeBytes, epochs, encrypt, usdPerGbPerMonth } = options;\n\n\tconst estimate = useMemo(\n\t\t() =>\n\t\t\testimateStorageUsd({\n\t\t\t\tsizeBytes,\n\t\t\t\tepochs,\n\t\t\t\tepochDurationMs: epochDurationMs(network),\n\t\t\t\tencryptionOverheadBytes: encrypt\n\t\t\t\t\t? DEFAULT_ENCRYPTION_OVERHEAD_BYTES\n\t\t\t\t\t: 0,\n\t\t\t\t...(usdPerGbPerMonth === undefined ? {} : { usdPerGbPerMonth }),\n\t\t\t}),\n\t\t[network, sizeBytes, epochs, encrypt, usdPerGbPerMonth],\n\t);\n\n\treturn { estimate };\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { UseClipboardResult } from \"../types.js\";\n\n/**\n * Copy text to the clipboard and flag success for a short window so a button\n * can show \"Copied\". The `copied` flag resets after `resetMs`. No-op (returns\n * false) when the Clipboard API is unavailable or the write is rejected.\n */\nexport function useClipboard(resetMs = 2000): UseClipboardResult {\n\tconst [copied, setCopied] = useState(false);\n\tconst timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n\n\tuseEffect(() => () => clearTimeout(timer.current), []);\n\n\tconst copy = useCallback(\n\t\tasync (text: string): Promise<boolean> => {\n\t\t\tif (\n\t\t\t\ttypeof navigator === \"undefined\" ||\n\t\t\t\tnavigator.clipboard === undefined\n\t\t\t) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait navigator.clipboard.writeText(text);\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tsetCopied(true);\n\t\t\tclearTimeout(timer.current);\n\t\t\ttimer.current = setTimeout(() => setCopied(false), resetMs);\n\t\t\treturn true;\n\t\t},\n\t\t[resetMs],\n\t);\n\n\treturn { copied, copy };\n}\n","import { useCallback, useRef, useState } from \"react\";\nimport type {\n\tDropzoneInputProps,\n\tDropzoneRootProps,\n\tUseDropzoneOptions,\n\tUseDropzoneResult,\n} from \"../types.js\";\n\n/**\n * Headless drag-and-drop file selection. Returns prop-getters for a root\n * element (keyboard-operable button with ARIA + drag state) and a hidden file\n * input, so consumers own all markup and styling. No `window`/`document`\n * access; SSR-safe.\n */\nexport function useDropzone(options: UseDropzoneOptions): UseDropzoneResult {\n\tconst {\n\t\tonFiles,\n\t\taccept,\n\t\tmultiple = true,\n\t\tdisabled = false,\n\t\tariaLabel,\n\t} = options;\n\tconst inputRef = useRef<HTMLInputElement>(null);\n\tconst [isDragging, setIsDragging] = useState(false);\n\n\tconst open = useCallback(() => {\n\t\tif (disabled) {\n\t\t\treturn;\n\t\t}\n\t\tinputRef.current?.click();\n\t}, [disabled]);\n\n\tconst emit = useCallback(\n\t\t(list: FileList | null) => {\n\t\t\tif (list === null || list.length === 0) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tonFiles(Array.from(list));\n\t\t},\n\t\t[onFiles],\n\t);\n\n\tconst getRootProps = useCallback(\n\t\t(): DropzoneRootProps => ({\n\t\t\trole: \"button\",\n\t\t\ttabIndex: disabled ? -1 : 0,\n\t\t\t\"aria-disabled\": disabled,\n\t\t\t\"data-dragging\": isDragging ? \"true\" : undefined,\n\t\t\t...(ariaLabel === undefined ? {} : { \"aria-label\": ariaLabel }),\n\t\t\tonClick: () => open(),\n\t\t\tonKeyDown: (event) => {\n\t\t\t\tif (disabled) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// Enter and Space activate a role=button element.\n\t\t\t\tif (event.key === \"Enter\" || event.key === \" \") {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\topen();\n\t\t\t\t}\n\t\t\t},\n\t\t\tonDragEnter: (event) => {\n\t\t\t\tif (disabled) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tevent.preventDefault();\n\t\t\t\tsetIsDragging(true);\n\t\t\t},\n\t\t\tonDragOver: (event) => {\n\t\t\t\t// preventDefault is required for the drop event to fire.\n\t\t\t\tif (!disabled) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t}\n\t\t\t},\n\t\t\tonDragLeave: (event) => {\n\t\t\t\tif (disabled) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tevent.preventDefault();\n\t\t\t\tsetIsDragging(false);\n\t\t\t},\n\t\t\tonDrop: (event) => {\n\t\t\t\tif (disabled) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tevent.preventDefault();\n\t\t\t\tsetIsDragging(false);\n\t\t\t\temit(event.dataTransfer?.files ?? null);\n\t\t\t},\n\t\t}),\n\t\t[disabled, isDragging, open, emit, ariaLabel],\n\t);\n\n\tconst getInputProps = useCallback(\n\t\t(): DropzoneInputProps => ({\n\t\t\tref: inputRef,\n\t\t\ttype: \"file\",\n\t\t\tmultiple,\n\t\t\ttabIndex: -1,\n\t\t\tdisabled,\n\t\t\t...(accept === undefined ? {} : { accept }),\n\t\t\tonChange: (event) => emit(event.target.files),\n\t\t\t// The root's onClick calls input.click(); stop that programmatic click\n\t\t\t// from bubbling back to the root and re-opening the dialog.\n\t\t\tonClick: (event) => event.stopPropagation(),\n\t\t}),\n\t\t[accept, multiple, disabled, emit],\n\t);\n\n\treturn { isDragging, open, getRootProps, getInputProps, inputRef };\n}\n","// Binary (1024-based) units, ordered from smallest to largest.\nconst UNITS = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\", \"PB\"] as const;\nconst BASE = 1024;\n\n/**\n * Format a byte count as a human-readable size (e.g. 2348 -> \"2.29 KB\").\n *\n * Uses binary units. Whole bytes are shown without a fraction; larger units use\n * `fractionDigits` decimals. Non-finite or non-positive input returns \"0 B\".\n */\nexport function formatBytes(bytes: number, fractionDigits = 2): string {\n\tif (!Number.isFinite(bytes) || bytes <= 0) {\n\t\treturn \"0 B\";\n\t}\n\tconst exponent = Math.min(\n\t\tMath.floor(Math.log(bytes) / Math.log(BASE)),\n\t\tUNITS.length - 1,\n\t);\n\tconst value = bytes / BASE ** exponent;\n\tconst unit = UNITS[exponent] ?? \"B\";\n\t// Bytes are always whole, so skip the fractional part for the base unit.\n\tif (exponent === 0) {\n\t\treturn `${value} ${unit}`;\n\t}\n\treturn `${value.toFixed(fractionDigits)} ${unit}`;\n}\n","// Below this the figure is not worth showing precisely; display a floor.\nconst TINY_FLOOR = 0.000001;\n\n/**\n * Format a USD amount for display. A cent or more uses two decimals; sub-cent\n * amounts (common for short Walrus leases) show two significant digits in plain\n * decimal notation; amounts below a millionth of a dollar show a `< $` floor.\n * Never uses scientific notation.\n */\nexport function formatUsd(usd: number): string {\n\tif (usd <= 0) {\n\t\treturn \"$0.00\";\n\t}\n\tif (usd >= 0.01) {\n\t\treturn `$${usd.toFixed(2)}`;\n\t}\n\tif (usd < TINY_FLOOR) {\n\t\treturn `< $${TINY_FLOOR}`;\n\t}\n\treturn `$${usd.toLocaleString(\"en-US\", {\n\t\tmaximumSignificantDigits: 2,\n\t\tuseGrouping: false,\n\t})}`;\n}\n"]}