@arcadiasystems/morse-uploader 0.1.0 → 0.2.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.
- package/CHANGELOG.md +45 -0
- package/README.md +56 -10
- package/dist/{chunk-2FYBTRTH.js → chunk-CAUC2ROV.js} +118 -23
- package/dist/chunk-CAUC2ROV.js.map +1 -0
- package/dist/index.d.ts +55 -17
- package/dist/index.js +56 -14
- package/dist/index.js.map +1 -1
- package/dist/styles.css +18 -0
- package/dist/{types-COaFRpWK.d.ts → types-C6w30zCM.d.ts} +34 -3
- package/dist/ui.d.ts +1 -1
- package/dist/ui.js +4 -3
- package/dist/ui.js.map +1 -1
- package/package.json +6 -6
- package/dist/chunk-2FYBTRTH.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,51 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@arcadiasystems/morse-uploader` will be documented in this file. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
4
4
|
|
|
5
|
+
## [0.2.1] - 2026-10-17
|
|
6
|
+
|
|
7
|
+
README fixes only. The README ships in the package, so these needed a release; no code changed.
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **"Notes on Walrus" still said mainnet was unsupported** and told readers to pass `network: "testnet"`. Morse has run on Walrus Mainnet since 2026-09-09, and "Limitations and roadmap" in the same README already said so. The stale line is gone.
|
|
12
|
+
- **The peer install named no versions**, so it pulled the latest majors of `@arcadiasystems/morse-sdk` and `@mysten/*`, outside `peerDependencies`. It now pins each to the tilde range equivalent to its peer range: `@arcadiasystems/morse-sdk@~0.8.1 @mysten/sui@~2.16.2 @mysten/walrus@~1.1.6 @mysten/seal@~1.1.3`.
|
|
13
|
+
- **The upload relay example would have been refused on Walrus Mainnet.** It had no `sendTip`, and `@mysten/walrus` sends no tip without one, while the Walrus Mainnet relay requires a tip. The example now passes `sendTip: { max: 10_000_000 }` (0.01 SUI), the cap `examples/web` uses.
|
|
14
|
+
|
|
15
|
+
## [0.2.0] - 2026-09-10
|
|
16
|
+
|
|
17
|
+
Mainnet support, via `@arcadiasystems/morse-sdk` 0.8.1.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- **Peer dependency moves to `>=0.8.1 <0.9.0`** from `>=0.4.2 <0.5.0`. Nothing in the SDK's public surface broke across those four minors; the change that matters is that `morseConfig({ network: "mainnet" })` returns a real config instead of throwing, which is what kept this package on testnet.
|
|
22
|
+
- **`handles.seal` is now `SealAdapter | null`.** It is `null` when no Seal key servers are available, which on mainnet means no credential was supplied.
|
|
23
|
+
|
|
24
|
+
Nullable rather than failing setup, deliberately: the adapter used to be built unconditionally, so under SDK 0.6.0+ the whole `useMorseFiles` hook would throw on mainnet and take the unencrypted flows down with it. Unencrypted upload and download now keep working, and the encrypted hooks reject with a message naming `sealKeyServers`. Consumers touching `handles.seal` directly get a type error rather than a runtime surprise.
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- **`network` on `buildShareLink` and `parseShareLink`**, written into the link's fragment as `net`. `useFileUpload` fills it in from the provider's network, so `shareLink` is now self-describing.
|
|
29
|
+
- **`uploadErrorMessage(error, network)`**, used by `MorseFileUploader` and exported for custom UIs. An insufficient-balance failure reaches the SDK as `@mysten/sui`'s raw text, so it rendered as "Network issue" followed by a coin type, an address, and two unscaled integers. It now reads "Not enough WAL. This upload needs 0.016626709 WAL and the connected wallet has 0 WAL." plus how to top up on that network. Everything else still goes through the SDK's `formatUserMessage`.
|
|
30
|
+
- **`sealKeyServers`** on `useMorseFiles` / `MorseFilesProvider`, for supplying your own key servers. Needed on mainnet, where every operator is commercial and `morseConfig` therefore pins none; start from the SDK's `MAINNET_SEAL_COMMITTEE` and add the credential your operator issues.
|
|
31
|
+
- **`seal`** option for a prebuilt adapter, overriding `sealKeyServers`. This matches how `walrusWrite` and `walrusRead` already worked; Seal was the one adapter with no escape hatch.
|
|
32
|
+
- **`VITE_SEAL_API_KEY` in the example**, wiring Mysten's committee for encrypted mainnet files, with a warning in both the example README and the config that a credential in a browser bundle is visible to anyone who opens devtools and belongs behind a backend proxy in anything deployed.
|
|
33
|
+
- `docs-gate`, run by `prepublishOnly`, failing when the CHANGELOG's newest entry does not match `package.json` or when the README's "built on morse-sdk X.Y" claim does not match the peer range. That second rule exists because this package sat four minors behind its own README.
|
|
34
|
+
- `resolveSealServers`, exported and unit-tested, holding the "consumer's servers, else the config's, else none" decision so the hook stays thin.
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- **A share link did not say which network the file was on.** A file id resolves on one network only, so opening a mainnet link in an app sitting on testnet reported "Could not load this file. The id may be wrong or the record may have been deleted" for a file that exists. Links now carry `net`, `parseShareLink` returns it as `network`, and the example follows it instead of its own default. A link without `net` parses with `network: null`, as does one naming a network this version does not know.
|
|
39
|
+
- **A truncated share link was treated as a public file.** The Seal prefix and nonce are a pair, but `parseShareLink` only checked them together when both were present: a link that lost one of them (a chat client clipping the URL, a hand-edited fragment) parsed as a public file, so `useFileDownload` skipped decrypt and the UI showed raw ciphertext as if it were the file, with no error anywhere. A link carrying only one of the two, or either one in unparseable hex, is now `null` like any other damaged link.
|
|
40
|
+
- **`hexToBytes` silently corrupted bad input.** It leaned on `Number.parseInt` plus TypedArray's NaN-to-0 coercion, so `"zzzz"` decoded to `[0, 0]` and an odd-length `"abc"` to `[171]` with the trailing nibble dropped. Both produced a plausible byte array that failed much later inside Seal's decrypt, pointing nowhere near the cause. It now throws on non-hex digits and on an odd digit count.
|
|
41
|
+
- **The native file input was visible inside the styled dropzone.** It carried `.mu-input`, the class for visible text fields, so the browser's default "Choose file" control rendered on top of the drop target. It now has its own `.mu-file-input` class and is visually hidden while staying clickable and in the accessibility tree.
|
|
42
|
+
- **The cached Seal `SessionKey` survived a wallet switch.** `useFileDecrypt` cleared it only on a session-expired error, so after connecting a different account the hook kept signing reads with the previous account's session. The cache is now keyed by address and package and re-signs when either changes.
|
|
43
|
+
- **The example's network switcher did not move dapp-kit.** It drove only the SDK handles, while dapp-kit stayed on its default network, and dapp-kit's client is the one that resolves coins and serializes the transaction for the wallet. Switching to mainnet therefore built a mainnet transaction against the testnet node, which failed with "Insufficient balance of ...::wal::WAL ... Available: 0" on a wallet holding plenty of mainnet WAL. The switcher now calls dapp-kit's `selectNetwork`, and the provider's default comes from the same constant.
|
|
44
|
+
- **The example's network switcher did not move dapp-kit.** It drove only the SDK handles, while dapp-kit stayed on its default network, and dapp-kit's client is the one that resolves the coins a transaction spends. Switching to mainnet therefore resolved a mainnet transaction's coins on the testnet node, which failed with "Insufficient balance of ...::wal::WAL ... Available: 0" on a wallet holding plenty of mainnet WAL. The switcher now calls dapp-kit's `selectNetwork`, and the provider's default comes from the same constant.
|
|
45
|
+
- **The example could not sign a transaction on either network.** dapp-kit 1.0.6 serializes the transaction through its own JSON-RPC client, and Mysten's public fullnodes have retired JSON-RPC: the preflight answers without an `access-control-allow-origin` header, so the browser blocks the call and the upload fails with "Failed to fetch" before the wallet opens. The example now defaults to an endpoint that still serves JSON-RPC, overridable with `VITE_SUI_JSONRPC_URL`, and the limitation is documented for consumers wiring their own dapp-kit.
|
|
46
|
+
- **The example app was hardwired to testnet.** `MAINNET_ENABLED` was `false` with a note to flip it once the SDK shipped mainnet; the upload relay host was a testnet constant; the dapp-kit providers only registered testnet; and its own morse-sdk pin was still 0.4.2, so importing anything newer failed the Vite build. All four are fixed, and the relay host is now per-network.
|
|
47
|
+
- **The example's relay tip ceiling would have refused every mainnet upload.** It was 1,000,000 MIST, sized for testnet's flat tip. Mainnet prices the tip linearly in encoded size and a small blob already costs about 2,600,000, so `@mysten/walrus` would have rejected the upload before sending. Raised to 10,000,000.
|
|
48
|
+
- README claimed testnet only, described the package as built on SDK 0.4.0, and said `suix_queryEvents` was merely on a deprecation track. Public fullnodes have since retired it, so an event source now means Sui GraphQL or a dedicated indexer.
|
|
49
|
+
|
|
5
50
|
## [0.1.0] - 2026-06-05
|
|
6
51
|
|
|
7
52
|
Built on `@arcadiasystems/morse-sdk` `>=0.4.2 <0.5.0` (0.4.0 failed every upload via a type-origin lookup bug; 0.4.1 broke `getRecipientFile` over the gRPC client via an `Option<ID>` parse bug; 0.4.2 is the floor): encrypted file sharing where each file (a `RecipientFile`) carries its own recipient set. `useMorseFiles` passes the full `morseConfig` output (including `recipientFileEventOriginPackageId`) to the upload helpers, which they need to match created objects by their type-origin package after a contract upgrade. An encrypted upload to N recipients is 2 wallet popups and the connected wallet is always a recipient (owner can always decrypt). The 0.4.0 SDK also ships pure event-reconciliation helpers for file listing; a listing UI is intentionally deferred here pending a production indexer (see README "Limitations and roadmap").
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @arcadiasystems/morse-uploader
|
|
2
2
|
|
|
3
|
-
Headless-first React components and hooks for encrypted file sharing on Sui and Walrus, built on [`@arcadiasystems/morse-sdk`](https://github.com/arcadiasystems/morse-dcms) 0.
|
|
3
|
+
Headless-first React components and hooks for encrypted file sharing on Sui and Walrus, built on [`@arcadiasystems/morse-sdk`](https://github.com/arcadiasystems/morse-dcms) 0.8. Upload a file to Walrus, optionally encrypt it for a set of recipient wallets so only they can decrypt, and share a link. The connected wallet pays for and signs everything.
|
|
4
4
|
|
|
5
5
|
- **Headless-first**: composable hooks for every step (`useMorseFiles`, `useFileUpload`, `useFileDownload`, `useFileDecrypt`, `useRecipientFile`), plus an optional styled component set.
|
|
6
6
|
- **Recipient-scoped encryption**: each file carries its own recipient set; an encrypted upload to N recipients is **2 wallet popups**, regardless of N. The connected wallet is always a recipient, so the owner can always decrypt. Public (unencrypted) uploads need no recipients.
|
|
@@ -16,8 +16,8 @@ npm install @arcadiasystems/morse-uploader
|
|
|
16
16
|
Peer dependencies (install in your app, pinned to the same ranges as `@arcadiasystems/morse-sdk` so you do not end up with duplicate Sui/Walrus/Seal instances):
|
|
17
17
|
|
|
18
18
|
```sh
|
|
19
|
-
npm install react react-dom @arcadiasystems/morse-sdk \
|
|
20
|
-
@mysten/sui @mysten/walrus @mysten/seal
|
|
19
|
+
npm install react react-dom @arcadiasystems/morse-sdk@~0.8.1 \
|
|
20
|
+
@mysten/sui@~2.16.2 @mysten/walrus@~1.1.6 @mysten/seal@~1.1.3
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
## Quick start
|
|
@@ -55,10 +55,17 @@ function FilesApp({ account }) {
|
|
|
55
55
|
// Defaults to a gRPC client on the network's public RPC.
|
|
56
56
|
// suiClient: new SuiGrpcClient({ network: "testnet", baseUrl: DEDICATED_RPC_URL }),
|
|
57
57
|
// Browser direct writes go through the Walrus upload relay and need the wasm URL.
|
|
58
|
+
// The relay host is per-network: swap testnet for mainnet when you do.
|
|
58
59
|
walrusWriteConfig: {
|
|
59
60
|
wasmUrl: walrusWasmUrl,
|
|
60
|
-
uploadRelay: {
|
|
61
|
+
uploadRelay: {
|
|
62
|
+
host: "https://upload-relay.testnet.walrus.space",
|
|
63
|
+
// Caps the per-upload tip the Walrus Mainnet relay charges, in MIST (0.01 SUI).
|
|
64
|
+
sendTip: { max: 10_000_000 },
|
|
65
|
+
},
|
|
61
66
|
},
|
|
67
|
+
// On mainnet, encrypted files also need your own Seal key servers.
|
|
68
|
+
// See "Seal on mainnet" below. Omit on testnet.
|
|
62
69
|
});
|
|
63
70
|
|
|
64
71
|
if (setup.status !== "ready" || setup.handles === null) {
|
|
@@ -115,6 +122,10 @@ All hooks must be used inside `MorseFilesProvider`.
|
|
|
115
122
|
| `useDropzone(options)` | Generic headless drag-and-drop file selection (prop-getters). |
|
|
116
123
|
| `useClipboard(resetMs?)` | Copy text and flag `copied` for a short window (for "Copy link" buttons). |
|
|
117
124
|
|
|
125
|
+
### Rendering an upload error
|
|
126
|
+
|
|
127
|
+
`uploadErrorMessage(error, network)` returns the SDK's `{ title, description, cause }` for any failure, with one rewrite: an insufficient-balance failure arrives as raw `@mysten/sui` text (a coin type, an address, two unscaled integers) that the SDK can only classify as a network problem. This restates it as "Not enough WAL", with the amounts in whole coins and the network's way of topping up. `MorseFileUploader` already uses it; call it directly if you render your own errors.
|
|
128
|
+
|
|
118
129
|
## How encryption works
|
|
119
130
|
|
|
120
131
|
Each file is a **RecipientFile** that carries its own recipient set on chain (there is no separate allowlist object). You pass the recipients at upload time; the connected wallet is **auto-added**, so the owner can always decrypt. The upload helper encrypts under a random Seal identity and binds it to the file in a single transaction, so an encrypted upload to any number of recipients is just **2 wallet popups**. At decrypt time, Seal's key servers dry-run the file's recipient check on-chain; non-recipients cannot get the key.
|
|
@@ -123,14 +134,16 @@ Building the recipient list is entirely client-side (no signature per recipient)
|
|
|
123
134
|
|
|
124
135
|
## Share links
|
|
125
136
|
|
|
126
|
-
`buildShareLink({ fileId, sealIdPrefix, sealNonce })` produces a `#/f/<fileId>?p=<hex>&n=<hex>` link. The fileId and Seal material live in the URL **fragment**, so they never reach a server log. The Seal material is not a secret: decryption is still gated by the file's recipient set. Parse it back on your download route:
|
|
137
|
+
`buildShareLink({ fileId, network, sealIdPrefix, sealNonce })` produces a `#/f/<fileId>?net=<network>&p=<hex>&n=<hex>` link. Pass `network`: a file id resolves on one network only, so a link without it opens against whatever network the receiving app happens to be on and reports the file as missing. The fileId and Seal material live in the URL **fragment**, so they never reach a server log. The Seal material is not a secret: decryption is still gated by the file's recipient set. Parse it back on your download route:
|
|
127
138
|
|
|
128
139
|
```tsx
|
|
129
140
|
import { parseShareLink } from "@arcadiasystems/morse-uploader";
|
|
130
141
|
const parsed = parseShareLink(window.location.href);
|
|
131
|
-
// { fileId, sealIdPrefix, sealNonce } | null (prefix/nonce null for public files)
|
|
142
|
+
// { fileId, network, sealIdPrefix, sealNonce } | null (prefix/nonce null for public files)
|
|
132
143
|
```
|
|
133
144
|
|
|
145
|
+
`null` means the link is not one of ours or is damaged. A link carrying only one of `p` / `n`, or either one in unparseable hex, is `null` too rather than a public file: reporting it as public would make the download path skip decrypt and hand the user raw ciphertext.
|
|
146
|
+
|
|
134
147
|
## Wallet popups
|
|
135
148
|
|
|
136
149
|
- **Public or encrypted upload**: two popups (Walrus `register_blob`, then a combined certify + create-RecipientFile PTB). Adding recipients is free (client-side).
|
|
@@ -140,14 +153,47 @@ const parsed = parseShareLink(window.location.href);
|
|
|
140
153
|
|
|
141
154
|
- Storage is a **lease**, capped at 53 epochs ahead on both networks. An epoch is ~1 day on testnet and ~2 weeks on mainnet.
|
|
142
155
|
- Blobs are raw bytes with no filename or MIME type; this package stores the name and content type in the on-chain file record so downloads come back correctly named and typed.
|
|
143
|
-
|
|
156
|
+
|
|
157
|
+
## Seal on mainnet
|
|
158
|
+
|
|
159
|
+
Encrypted files need Seal key servers. Testnet pins an open set, so nothing is
|
|
160
|
+
required there. Mainnet pins none: every mainnet Seal operator is commercial,
|
|
161
|
+
so you bring your own credential.
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
import { MAINNET_SEAL_COMMITTEE } from "@arcadiasystems/morse-sdk";
|
|
165
|
+
|
|
166
|
+
const handles = useMorseFiles({
|
|
167
|
+
network: "mainnet",
|
|
168
|
+
account,
|
|
169
|
+
callbacks,
|
|
170
|
+
sealKeyServers: MAINNET_SEAL_COMMITTEE.map((s) => ({
|
|
171
|
+
...s,
|
|
172
|
+
apiKeyName: "X-API-Key",
|
|
173
|
+
apiKey: import.meta.env.VITE_SEAL_API_KEY,
|
|
174
|
+
})),
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Pass a prebuilt `seal` adapter instead if you want full control; it overrides
|
|
179
|
+
`sealKeyServers`.
|
|
180
|
+
|
|
181
|
+
Without either, `handles.seal` is `null` rather than the hook failing. That is
|
|
182
|
+
deliberate: building the adapter eagerly would fail setup on mainnet and take
|
|
183
|
+
the unencrypted flows down with it. Check for `null` before offering encryption
|
|
184
|
+
in your UI.
|
|
185
|
+
|
|
186
|
+
**Do not ship the key in client-side code you do not control.** A browser bundle
|
|
187
|
+
exposes it to anyone who opens devtools. Proxy through your own backend if the
|
|
188
|
+
credential is not meant to be public.
|
|
144
189
|
|
|
145
190
|
## Limitations and roadmap
|
|
146
191
|
|
|
147
|
-
- **Listing a wallet's files needs an indexer (deferred).** A `RecipientFile` is a shared Sui object with no owner-indexable field, so there is no direct "list my files" query. morse-sdk
|
|
192
|
+
- **Listing a wallet's files needs an indexer (deferred).** A `RecipientFile` is a shared Sui object with no owner-indexable field, so there is no direct "list my files" query. morse-sdk ships pure reconciliation helpers (`reconcileRecipientFilesOwnedBy`, `reconcileRecipientFilesAccessibleBy`) that turn a raw event stream into the current file set, but it deliberately does not fetch events. A production listing requires a Sui event source (a dedicated indexer; `suix_queryEvents` has since been retired by public fullnodes, so an event source now means Sui GraphQL or a dedicated indexer). This package does not ship a listing UI yet for that reason; it is planned once an indexer is in place.
|
|
148
193
|
- **Seal material is not on-chain.** The Seal identity needed to decrypt (prefix + nonce) is persisted out-of-band (in the share link, here). It is not recoverable from the chain or from an event listing, so a future indexer-backed list can surface encrypted files' metadata but cannot, by itself, decrypt them; decryption still requires the share-link material. Persist `sealIdPrefix`/`sealNonce` in your own store if you need list-and-open UX.
|
|
149
|
-
- **
|
|
150
|
-
- **
|
|
194
|
+
- **dapp-kit needs a JSON-RPC endpoint that still exists.** This package talks gRPC, which the public fullnodes still serve. dapp-kit 1.0.6 talks JSON-RPC only, and Mysten's public fullnodes have retired it: the preflight answers without an `access-control-allow-origin` header, so a browser call fails with `TypeError: Failed to fetch` from `@mysten/sui/jsonRpc` while dapp-kit serializes the transaction, before the wallet opens. Point `SuiClientProvider` at an endpoint that still serves JSON-RPC (your own node, a provider, or a public one such as `https://sui-rpc.publicnode.com`). Nothing in this package changes; the failure is in the wallet layer.
|
|
195
|
+
- **RPC consistency (use a dedicated RPC).** The SDK builds/reads over a gRPC client while the wallet (dapp-kit) executes over its own JSON-RPC client. When those are different nodes, they can sit at different checkpoints, so the certify step occasionally fails with an object-version conflict ("needs to be rebuilt"). For reliable uploads, point both at one **dedicated RPC**: set dapp-kit's `SuiClientProvider` URL and pass a `SuiGrpcClient` on the matching endpoint via `useMorseFiles({ suiClient })`. This is an RPC-infrastructure property, not a bug in the package logic.
|
|
196
|
+
- **Mainnet works, but encrypted files there need your own Seal key servers.** Testnet pins an open allowlist, so `network: "testnet"` needs no setup. Mainnet pins none, because every mainnet Seal operator is commercial. Without `sealKeyServers`, `handles.seal` is `null`: unencrypted upload and download still work, and the encrypted hooks reject with a message saying so. See [Seal on mainnet](#seal-on-mainnet).
|
|
151
197
|
- **Storage is a lease.** Walrus storage is capped at 53 epochs ahead and expires; this is not permanent storage.
|
|
152
198
|
|
|
153
199
|
## Example
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { formatUserMessage, buildRecipientFileSealId, SealError, uploadEncryptedRecipientFileFromBytes, uploadRecipientFileFromBytes } from '@arcadiasystems/morse-sdk';
|
|
1
2
|
import { createContext, useContext, useState, useRef, useCallback, useEffect, useMemo } from 'react';
|
|
2
|
-
import { buildRecipientFileSealId, SealError, uploadEncryptedRecipientFileFromBytes, uploadRecipientFileFromBytes } from '@arcadiasystems/morse-sdk';
|
|
3
3
|
import { SessionKey } from '@mysten/seal';
|
|
4
4
|
|
|
5
5
|
// src/constants/default-encryption-overhead-bytes.ts
|
|
@@ -36,11 +36,16 @@ function buildShareLink(args) {
|
|
|
36
36
|
":fileId",
|
|
37
37
|
encodeURIComponent(args.fileId)
|
|
38
38
|
);
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
args.
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
const params = [];
|
|
40
|
+
if (args.network !== void 0) {
|
|
41
|
+
params.push(`net=${args.network}`);
|
|
42
|
+
}
|
|
43
|
+
if (args.sealIdPrefix !== void 0 && args.sealNonce !== void 0) {
|
|
44
|
+
params.push(`p=${bytesToHex(args.sealIdPrefix)}`);
|
|
45
|
+
params.push(`n=${bytesToHex(args.sealNonce)}`);
|
|
46
|
+
}
|
|
47
|
+
const query = params.length === 0 ? "" : `?${params.join("&")}`;
|
|
48
|
+
return `${base}${path}${query}`;
|
|
44
49
|
}
|
|
45
50
|
|
|
46
51
|
// src/files/epoch-duration-ms.ts
|
|
@@ -67,6 +72,83 @@ function estimateStorageUsd(args) {
|
|
|
67
72
|
const rate = args.usdPerGbPerMonth ?? DEFAULT_USD_PER_GB_PER_MONTH;
|
|
68
73
|
return { usd: gb * months * rate, gb, months, days, sizeBytes };
|
|
69
74
|
}
|
|
75
|
+
function uploadErrorMessage(error, network) {
|
|
76
|
+
const shortfall = parseInsufficientBalance(errorText(error));
|
|
77
|
+
if (shortfall === null) {
|
|
78
|
+
return formatUserMessage(error);
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
title: `Not enough ${shortfall.symbol}`,
|
|
82
|
+
description: describe(shortfall, network),
|
|
83
|
+
cause: error
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
var INSUFFICIENT_RE = /Insufficient balance of (\S+) for owner \S+?\.\s*Required:\s*(\d+),\s*Available:\s*(\d+)/;
|
|
87
|
+
function parseInsufficientBalance(text) {
|
|
88
|
+
const match = INSUFFICIENT_RE.exec(text);
|
|
89
|
+
if (match === null) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
const [, coinType, required, available] = match;
|
|
93
|
+
return {
|
|
94
|
+
...coin(coinType),
|
|
95
|
+
required: BigInt(required),
|
|
96
|
+
available: BigInt(available)
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function coin(coinType) {
|
|
100
|
+
if (coinType.endsWith("::wal::WAL")) {
|
|
101
|
+
return { symbol: "WAL", decimals: 9 };
|
|
102
|
+
}
|
|
103
|
+
if (coinType.endsWith("::sui::SUI")) {
|
|
104
|
+
return { symbol: "SUI", decimals: 9 };
|
|
105
|
+
}
|
|
106
|
+
const symbol = coinType.split("::").pop();
|
|
107
|
+
return { symbol: symbol ?? "coins", decimals: null };
|
|
108
|
+
}
|
|
109
|
+
function describe(shortfall, network) {
|
|
110
|
+
const need = amount(shortfall.required, shortfall.decimals, shortfall.symbol);
|
|
111
|
+
const have = amount(
|
|
112
|
+
shortfall.available,
|
|
113
|
+
shortfall.decimals,
|
|
114
|
+
shortfall.symbol
|
|
115
|
+
);
|
|
116
|
+
return `This upload needs ${need} and the connected wallet has ${have}. ${topUp(shortfall.symbol, network)}`;
|
|
117
|
+
}
|
|
118
|
+
function topUp(symbol, network) {
|
|
119
|
+
if (network === "testnet") {
|
|
120
|
+
return symbol === "WAL" ? "Swap some testnet SUI for WAL at https://stake-wal.wal.app/?network=testnet, then retry." : "Get testnet SUI from https://faucet.sui.io/, then retry.";
|
|
121
|
+
}
|
|
122
|
+
if (network === "mainnet") {
|
|
123
|
+
return `There is no faucet on mainnet, so ${symbol} has to be acquired (Walrus charges WAL for storage and SUI for gas). Top up the wallet, then retry.`;
|
|
124
|
+
}
|
|
125
|
+
return `Top up the wallet with ${symbol}, then retry.`;
|
|
126
|
+
}
|
|
127
|
+
function amount(value, decimals, symbol) {
|
|
128
|
+
if (decimals === null) {
|
|
129
|
+
return `${value.toString()} ${symbol}`;
|
|
130
|
+
}
|
|
131
|
+
const scale = 10n ** BigInt(decimals);
|
|
132
|
+
const whole = value / scale;
|
|
133
|
+
const fraction = (value % scale).toString().padStart(decimals, "0");
|
|
134
|
+
const trimmed = fraction.replace(/0+$/, "");
|
|
135
|
+
const text = trimmed === "" ? whole.toString() : `${whole}.${trimmed}`;
|
|
136
|
+
return `${text} ${symbol}`;
|
|
137
|
+
}
|
|
138
|
+
function errorText(error) {
|
|
139
|
+
const parts = [];
|
|
140
|
+
let current = error;
|
|
141
|
+
for (let depth = 0; current !== null && current !== void 0 && depth < 5; depth += 1) {
|
|
142
|
+
if (current instanceof Error) {
|
|
143
|
+
parts.push(current.message);
|
|
144
|
+
current = current.cause;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
parts.push(String(current));
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
return parts.join(" | ");
|
|
151
|
+
}
|
|
70
152
|
|
|
71
153
|
// src/files/upload-phase-label.ts
|
|
72
154
|
var LABELS = {
|
|
@@ -106,6 +188,7 @@ function useMorseFilesContext() {
|
|
|
106
188
|
}
|
|
107
189
|
return handles;
|
|
108
190
|
}
|
|
191
|
+
var SEAL_UNAVAILABLE = "No Seal key servers are configured, so decryption is unavailable. Testnet pins an open set; mainnet pins none, because every operator there is commercial. Pass `sealKeyServers` to MorseFilesProvider, starting from the SDK's MAINNET_SEAL_COMMITTEE with the apiKeyName and apiKey your operator issues.";
|
|
109
192
|
var DEFAULT_TTL_MIN = 10;
|
|
110
193
|
function useFileDecrypt() {
|
|
111
194
|
const { seal, walrusRead, signer, client, config, address } = useMorseFilesContext();
|
|
@@ -123,19 +206,26 @@ function useFileDecrypt() {
|
|
|
123
206
|
setStatus("working");
|
|
124
207
|
setData(null);
|
|
125
208
|
setError(null);
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
209
|
+
if (seal === null) {
|
|
210
|
+
const unavailable = new Error(SEAL_UNAVAILABLE);
|
|
211
|
+
setError(unavailable);
|
|
212
|
+
setStatus("error");
|
|
213
|
+
throw unavailable;
|
|
214
|
+
}
|
|
215
|
+
const packageId = config.originalPackageId ?? config.packageId;
|
|
133
216
|
const getSession = async () => {
|
|
134
|
-
|
|
135
|
-
|
|
217
|
+
const cached = sessionRef.current;
|
|
218
|
+
if (cached !== null && cached.address === address && cached.packageId === packageId) {
|
|
219
|
+
return cached.key;
|
|
136
220
|
}
|
|
137
|
-
const key = await
|
|
138
|
-
|
|
221
|
+
const key = await SessionKey.create({
|
|
222
|
+
address,
|
|
223
|
+
packageId,
|
|
224
|
+
ttlMin: input.ttlMin ?? DEFAULT_TTL_MIN,
|
|
225
|
+
signer,
|
|
226
|
+
suiClient: client
|
|
227
|
+
});
|
|
228
|
+
sessionRef.current = { address, packageId, key };
|
|
139
229
|
return key;
|
|
140
230
|
};
|
|
141
231
|
try {
|
|
@@ -304,9 +394,10 @@ function useFileDownload(fileId, seal) {
|
|
|
304
394
|
download
|
|
305
395
|
};
|
|
306
396
|
}
|
|
397
|
+
var SEAL_UNAVAILABLE2 = "No Seal key servers are configured, so encrypted files are unavailable. Testnet pins an open set; mainnet pins none, because every operator there is commercial. Pass `sealKeyServers` to MorseFilesProvider, starting from the SDK's MAINNET_SEAL_COMMITTEE with the apiKeyName and apiKey your operator issues.";
|
|
307
398
|
var FALLBACK_CONTENT_TYPE = "application/octet-stream";
|
|
308
399
|
function useFileUpload() {
|
|
309
|
-
const { adapter, config, walrusWrite, seal } = useMorseFilesContext();
|
|
400
|
+
const { adapter, config, walrusWrite, seal, network } = useMorseFilesContext();
|
|
310
401
|
const [status, setStatus] = useState("idle");
|
|
311
402
|
const [phase, setPhase] = useState(null);
|
|
312
403
|
const [data, setData] = useState(null);
|
|
@@ -334,6 +425,9 @@ function useFileUpload() {
|
|
|
334
425
|
const onProgress = (event) => setPhase(event.phase);
|
|
335
426
|
let completed;
|
|
336
427
|
if (input.encrypt === true) {
|
|
428
|
+
if (seal === null) {
|
|
429
|
+
throw new Error(SEAL_UNAVAILABLE2);
|
|
430
|
+
}
|
|
337
431
|
const result = await uploadEncryptedRecipientFileFromBytes(
|
|
338
432
|
adapter,
|
|
339
433
|
config,
|
|
@@ -356,6 +450,7 @@ function useFileUpload() {
|
|
|
356
450
|
sealNonce: result.sealNonce,
|
|
357
451
|
shareLink: buildShareLink({
|
|
358
452
|
fileId: result.fileId,
|
|
453
|
+
network,
|
|
359
454
|
sealIdPrefix: result.sealIdPrefix,
|
|
360
455
|
sealNonce: result.sealNonce
|
|
361
456
|
})
|
|
@@ -376,7 +471,7 @@ function useFileUpload() {
|
|
|
376
471
|
encrypted: false,
|
|
377
472
|
sealIdPrefix: null,
|
|
378
473
|
sealNonce: null,
|
|
379
|
-
shareLink: buildShareLink({ fileId: result.fileId })
|
|
474
|
+
shareLink: buildShareLink({ fileId: result.fileId, network })
|
|
380
475
|
};
|
|
381
476
|
}
|
|
382
477
|
setData(completed);
|
|
@@ -388,7 +483,7 @@ function useFileUpload() {
|
|
|
388
483
|
throw err;
|
|
389
484
|
}
|
|
390
485
|
},
|
|
391
|
-
[adapter, config, walrusWrite, seal]
|
|
486
|
+
[adapter, config, walrusWrite, seal, network]
|
|
392
487
|
);
|
|
393
488
|
return { status, phase, data, error, upload, reset };
|
|
394
489
|
}
|
|
@@ -557,6 +652,6 @@ function formatUsd(usd) {
|
|
|
557
652
|
})}`;
|
|
558
653
|
}
|
|
559
654
|
|
|
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-
|
|
562
|
-
//# sourceMappingURL=chunk-
|
|
655
|
+
export { DEFAULT_ENCRYPTION_OVERHEAD_BYTES, MorseFilesContext, buildShareLink, bytesToHex, epochDurationMs, estimateStorageUsd, formatBytes, formatUsd, isPreviewableImage, resolveOrigin, triggerDownload, uploadErrorMessage, uploadPhaseLabel, uploadPhases, useClipboard, useDropzone, useFileDecrypt, useFileDownload, useFileUpload, useMorseFilesContext, useRecipientFile, useStorageCostEstimate };
|
|
656
|
+
//# sourceMappingURL=chunk-CAUC2ROV.js.map
|
|
657
|
+
//# sourceMappingURL=chunk-CAUC2ROV.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-error-message.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","SEAL_UNAVAILABLE"],"mappings":"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","file":"chunk-CAUC2ROV.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 * with the network as `net` and the Seal material (prefix + nonce, if any) as\n * hex `p`/`n` queries inside that fragment, so none of it reaches a server log.\n * The seal material is not a secret: decryption is still gated by the file's\n * recipient set. SSR-safe (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 params: string[] = [];\n\tif (args.network !== undefined) {\n\t\tparams.push(`net=${args.network}`);\n\t}\n\tif (args.sealIdPrefix !== undefined && args.sealNonce !== undefined) {\n\t\tparams.push(`p=${bytesToHex(args.sealIdPrefix)}`);\n\t\tparams.push(`n=${bytesToHex(args.sealNonce)}`);\n\t}\n\tconst query = params.length === 0 ? \"\" : `?${params.join(\"&\")}`;\n\treturn `${base}${path}${query}`;\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 {\n\ttype FormattedError,\n\tformatUserMessage,\n} from \"@arcadiasystems/morse-sdk\";\n\n/**\n * Render an upload failure for the UI. Defers to the SDK's `formatUserMessage`\n * except for an insufficient-balance failure, which the SDK sees only as a\n * transport error carrying `@mysten/sui`'s raw text: a coin type, an address,\n * and two unscaled integers. That reads as a network problem when it is really\n * \"top up the wallet\", so it is rewritten here with the amounts in whole coins\n * and the network's way of getting more.\n */\nexport function uploadErrorMessage(\n\terror: unknown,\n\tnetwork?: \"testnet\" | \"mainnet\",\n): FormattedError {\n\tconst shortfall = parseInsufficientBalance(errorText(error));\n\tif (shortfall === null) {\n\t\treturn formatUserMessage(error);\n\t}\n\treturn {\n\t\ttitle: `Not enough ${shortfall.symbol}`,\n\t\tdescription: describe(shortfall, network),\n\t\tcause: error,\n\t};\n}\n\ninterface Shortfall {\n\treadonly symbol: string;\n\treadonly decimals: number | null;\n\treadonly required: bigint;\n\treadonly available: bigint;\n}\n\n// @mysten/sui's CoinWithBalance intent, thrown while resolving the coins the\n// transaction spends: \"Insufficient balance of <coinType> for owner <address>.\n// Required: <n>, Available: <n>\".\nconst INSUFFICIENT_RE =\n\t/Insufficient balance of (\\S+) for owner \\S+?\\.\\s*Required:\\s*(\\d+),\\s*Available:\\s*(\\d+)/;\n\nfunction parseInsufficientBalance(text: string): Shortfall | null {\n\tconst match = INSUFFICIENT_RE.exec(text);\n\tif (match === null) {\n\t\treturn null;\n\t}\n\tconst [, coinType, required, available] = match;\n\treturn {\n\t\t...coin(coinType as string),\n\t\trequired: BigInt(required as string),\n\t\tavailable: BigInt(available as string),\n\t};\n}\n\n/**\n * Both coins Walrus charges use 9 decimals. Anything else is reported in raw\n * units rather than scaled by a guessed exponent.\n */\nfunction coin(coinType: string): { symbol: string; decimals: number | null } {\n\tif (coinType.endsWith(\"::wal::WAL\")) {\n\t\treturn { symbol: \"WAL\", decimals: 9 };\n\t}\n\tif (coinType.endsWith(\"::sui::SUI\")) {\n\t\treturn { symbol: \"SUI\", decimals: 9 };\n\t}\n\tconst symbol = coinType.split(\"::\").pop();\n\treturn { symbol: symbol ?? \"coins\", decimals: null };\n}\n\nfunction describe(shortfall: Shortfall, network?: string): string {\n\tconst need = amount(shortfall.required, shortfall.decimals, shortfall.symbol);\n\tconst have = amount(\n\t\tshortfall.available,\n\t\tshortfall.decimals,\n\t\tshortfall.symbol,\n\t);\n\treturn `This upload needs ${need} and the connected wallet has ${have}. ${topUp(shortfall.symbol, network)}`;\n}\n\nfunction topUp(symbol: string, network?: string): string {\n\tif (network === \"testnet\") {\n\t\treturn symbol === \"WAL\"\n\t\t\t? \"Swap some testnet SUI for WAL at https://stake-wal.wal.app/?network=testnet, then retry.\"\n\t\t\t: \"Get testnet SUI from https://faucet.sui.io/, then retry.\";\n\t}\n\tif (network === \"mainnet\") {\n\t\treturn `There is no faucet on mainnet, so ${symbol} has to be acquired (Walrus charges WAL for storage and SUI for gas). Top up the wallet, then retry.`;\n\t}\n\treturn `Top up the wallet with ${symbol}, then retry.`;\n}\n\nfunction amount(\n\tvalue: bigint,\n\tdecimals: number | null,\n\tsymbol: string,\n): string {\n\tif (decimals === null) {\n\t\treturn `${value.toString()} ${symbol}`;\n\t}\n\tconst scale = 10n ** BigInt(decimals);\n\tconst whole = value / scale;\n\tconst fraction = (value % scale).toString().padStart(decimals, \"0\");\n\tconst trimmed = fraction.replace(/0+$/, \"\");\n\tconst text = trimmed === \"\" ? whole.toString() : `${whole}.${trimmed}`;\n\treturn `${text} ${symbol}`;\n}\n\n/** Flatten the error and its causes, since the text can sit on either. */\nfunction errorText(error: unknown): string {\n\tconst parts: string[] = [];\n\tlet current = error;\n\tfor (\n\t\tlet depth = 0;\n\t\tcurrent !== null && current !== undefined && depth < 5;\n\t\tdepth += 1\n\t) {\n\t\tif (current instanceof Error) {\n\t\t\tparts.push(current.message);\n\t\t\tcurrent = current.cause;\n\t\t\tcontinue;\n\t\t}\n\t\tparts.push(String(current));\n\t\tbreak;\n\t}\n\treturn parts.join(\" | \");\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\n/** Message for when the provider has no key servers to decrypt with. */\nconst SEAL_UNAVAILABLE =\n\t\"No Seal key servers are configured, so decryption is unavailable. Testnet pins an open set; mainnet pins none, because every operator there is commercial. Pass `sealKeyServers` to MorseFilesProvider, starting from the SDK's MAINNET_SEAL_COMMITTEE with the apiKeyName and apiKey your operator issues.\";\n\nconst DEFAULT_TTL_MIN = 10;\n\ninterface CachedSession {\n\treadonly address: string;\n\treadonly packageId: string;\n\treadonly key: SessionKey;\n}\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 address and package, so\n * it is cached and re-signed only when one of those changes.\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\t// A SessionKey is bound to the address that signed it and to one package, so\n\t// the cache carries both. Reusing one across a wallet switch would sign the\n\t// new account's reads with the old account's session and fail at the key\n\t// servers, or silently keep the old account's access.\n\tconst sessionRef = useRef<CachedSession | 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\tif (seal === null) {\n\t\t\t\tconst unavailable = new Error(SEAL_UNAVAILABLE);\n\t\t\t\tsetError(unavailable);\n\t\t\t\tsetStatus(\"error\");\n\t\t\t\tthrow unavailable;\n\t\t\t}\n\n\t\t\tconst packageId = config.originalPackageId ?? config.packageId;\n\t\t\tconst getSession = async () => {\n\t\t\t\tconst cached = sessionRef.current;\n\t\t\t\tif (\n\t\t\t\t\tcached !== null &&\n\t\t\t\t\tcached.address === address &&\n\t\t\t\t\tcached.packageId === packageId\n\t\t\t\t) {\n\t\t\t\t\treturn cached.key;\n\t\t\t\t}\n\t\t\t\tconst key = await SessionKey.create({\n\t\t\t\t\taddress,\n\t\t\t\t\tpackageId,\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\t\tsessionRef.current = { address, packageId, 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\n/** Shared message for the encrypted paths when no key servers exist. */\nconst SEAL_UNAVAILABLE =\n\t\"No Seal key servers are configured, so encrypted files are unavailable. Testnet pins an open set; mainnet pins none, because every operator there is commercial. Pass `sealKeyServers` to MorseFilesProvider, starting from the SDK's MAINNET_SEAL_COMMITTEE with the apiKeyName and apiKey your operator issues.\";\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, network } =\n\t\tuseMorseFilesContext();\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\tif (seal === null) {\n\t\t\t\t\t\tthrow new Error(SEAL_UNAVAILABLE);\n\t\t\t\t\t}\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\tnetwork,\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, network }),\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, network],\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"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { B as BuildShareLinkArgs, U as UsdCostArgs, a as UsdCostEstimate, M as MorseFilesHandles, P as ParsedShareLink, F as FilePhase, b as UseFileDecryptResult, S as SealMaterial, c as UseFileDownloadResult, d as UseFileUploadResult, e as UseMorseFilesOptions, f as UseMorseFilesResult, g as UseRecipientFileResult, h as UseStorageCostEstimateOptions, i as UseStorageCostEstimateResult } from './types-
|
|
2
|
-
export { C as CompletedFileUpload, j as FileDecryptInput, k as FileDecryptStatus, l as FileDownloadStatus, m as FileUploadInput, n as FileUploadStatus, L as LoadStatus, o as MorseFileDownloaderProps, p as MorseFileUploaderProps, q as MorseFilesWalletCallbacks, r as SetupStatus } from './types-
|
|
1
|
+
import { B as BuildShareLinkArgs, U as UsdCostArgs, a as UsdCostEstimate, M as MorseFilesHandles, P as ParsedShareLink, F as FilePhase, b as UseFileDecryptResult, S as SealMaterial, c as UseFileDownloadResult, d as UseFileUploadResult, e as UseMorseFilesOptions, f as UseMorseFilesResult, g as UseRecipientFileResult, h as UseStorageCostEstimateOptions, i as UseStorageCostEstimateResult } from './types-C6w30zCM.js';
|
|
2
|
+
export { C as CompletedFileUpload, j as FileDecryptInput, k as FileDecryptStatus, l as FileDownloadStatus, m as FileUploadInput, n as FileUploadStatus, L as LoadStatus, o as MorseFileDownloaderProps, p as MorseFileUploaderProps, q as MorseFilesWalletCallbacks, r as SetupStatus } from './types-C6w30zCM.js';
|
|
3
3
|
import { ReactNode, ReactElement, RefObject, ChangeEvent, MouseEvent, KeyboardEvent, DragEvent } from 'react';
|
|
4
|
-
import {
|
|
5
|
-
import '@
|
|
4
|
+
import { KeyServerConfig } from '@mysten/seal';
|
|
5
|
+
import { FormattedError, RecipientFileId } from '@arcadiasystems/morse-sdk';
|
|
6
6
|
import '@mysten/sui/cryptography';
|
|
7
7
|
import '@mysten/sui/grpc';
|
|
8
8
|
import '@mysten/sui/transactions';
|
|
@@ -15,11 +15,11 @@ import '@mysten/sui/transactions';
|
|
|
15
15
|
declare const DEFAULT_ENCRYPTION_OVERHEAD_BYTES = 150;
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* Build a share link for a RecipientFile. The fileId goes in the URL fragment
|
|
19
|
-
* and the Seal material (prefix + nonce, if any) as
|
|
20
|
-
*
|
|
21
|
-
* secret: decryption is still gated by the file's
|
|
22
|
-
* (falls back to an empty base outside a browser).
|
|
18
|
+
* Build a share link for a RecipientFile. The fileId goes in the URL fragment,
|
|
19
|
+
* with the network as `net` and the Seal material (prefix + nonce, if any) as
|
|
20
|
+
* hex `p`/`n` queries inside that fragment, so none of it reaches a server log.
|
|
21
|
+
* The seal material is not a secret: decryption is still gated by the file's
|
|
22
|
+
* recipient set. SSR-safe (falls back to an empty base outside a browser).
|
|
23
23
|
*/
|
|
24
24
|
declare function buildShareLink(args: BuildShareLinkArgs): string;
|
|
25
25
|
|
|
@@ -48,13 +48,43 @@ interface MorseFilesProviderProps {
|
|
|
48
48
|
declare function MorseFilesProvider({ value, children, }: MorseFilesProviderProps): ReactElement;
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
|
-
* Parse a share link produced by {@link buildShareLink}. Reads the fileId
|
|
52
|
-
* optional Seal material (prefix + nonce) from the URL
|
|
53
|
-
* when the fragment does not match the expected shape;
|
|
54
|
-
* are null for a public file (no seal material in
|
|
51
|
+
* Parse a share link produced by {@link buildShareLink}. Reads the fileId, the
|
|
52
|
+
* network, and the optional Seal material (prefix + nonce) from the URL
|
|
53
|
+
* fragment. Returns null when the fragment does not match the expected shape;
|
|
54
|
+
* `sealIdPrefix`/`sealNonce` are null for a public file (no seal material in
|
|
55
|
+
* the link), and `network` is null for a link that omits it.
|
|
56
|
+
*
|
|
57
|
+
* A damaged link is null, never a public file. The seal params are a pair, so a
|
|
58
|
+
* link carrying only one of them, or either one in unparseable hex, is rejected
|
|
59
|
+
* rather than downgraded: reporting it as public would make the caller skip
|
|
60
|
+
* decrypt and hand raw ciphertext to the user as if it were the file.
|
|
55
61
|
*/
|
|
56
62
|
declare function parseShareLink(href: string): ParsedShareLink | null;
|
|
57
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Pick the Seal key servers to build the default adapter from, or `undefined`
|
|
66
|
+
* when there are none and the adapter must be skipped.
|
|
67
|
+
*
|
|
68
|
+
* Testnet pins an open allowlist, so `morseConfig` supplies servers there and
|
|
69
|
+
* nothing is needed from the consumer. Mainnet pins none, because every
|
|
70
|
+
* operator is commercial: without `sealKeyServers` the encrypted paths cannot
|
|
71
|
+
* work, and the honest answer is no adapter rather than a broken one.
|
|
72
|
+
*
|
|
73
|
+
* Consumer-supplied servers win, so a consumer can point testnet at their own
|
|
74
|
+
* operator too.
|
|
75
|
+
*/
|
|
76
|
+
declare function resolveSealServers(fromOptions: readonly KeyServerConfig[] | undefined, fromConfig: readonly KeyServerConfig[]): readonly KeyServerConfig[] | undefined;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Render an upload failure for the UI. Defers to the SDK's `formatUserMessage`
|
|
80
|
+
* except for an insufficient-balance failure, which the SDK sees only as a
|
|
81
|
+
* transport error carrying `@mysten/sui`'s raw text: a coin type, an address,
|
|
82
|
+
* and two unscaled integers. That reads as a network problem when it is really
|
|
83
|
+
* "top up the wallet", so it is rewritten here with the amounts in whole coins
|
|
84
|
+
* and the network's way of getting more.
|
|
85
|
+
*/
|
|
86
|
+
declare function uploadErrorMessage(error: unknown, network?: "testnet" | "mainnet"): FormattedError;
|
|
87
|
+
|
|
58
88
|
/** Human-readable label for an upload phase, for the progress stepper. */
|
|
59
89
|
declare function uploadPhaseLabel(phase: FilePhase): string;
|
|
60
90
|
|
|
@@ -68,8 +98,8 @@ declare function uploadPhases(encrypted: boolean): readonly FilePhase[];
|
|
|
68
98
|
* Decrypt + return the plaintext of an encrypted RecipientFile. Reads the
|
|
69
99
|
* ciphertext from Walrus, creates (and reuses) a Seal SessionKey, and decrypts
|
|
70
100
|
* under the file's recipient policy. Re-signs once on a session-expired error.
|
|
71
|
-
* The SessionKey is reusable across files for the same package, so
|
|
72
|
-
*
|
|
101
|
+
* The SessionKey is reusable across files for the same address and package, so
|
|
102
|
+
* it is cached and re-signed only when one of those changes.
|
|
73
103
|
*/
|
|
74
104
|
declare function useFileDecrypt(): UseFileDecryptResult;
|
|
75
105
|
|
|
@@ -218,7 +248,15 @@ declare function formatBytes(bytes: number, fractionDigits?: number): string;
|
|
|
218
248
|
*/
|
|
219
249
|
declare function formatUsd(usd: number): string;
|
|
220
250
|
|
|
221
|
-
/**
|
|
251
|
+
/**
|
|
252
|
+
* Decode a hex string to bytes, rejecting anything that is not valid hex.
|
|
253
|
+
*
|
|
254
|
+
* Throws rather than coercing. The previous version relied on TypedArray's
|
|
255
|
+
* NaN -> 0 coercion, so "zzzz" decoded to [0, 0] and an odd-length "abc" to
|
|
256
|
+
* [171] with the trailing nibble dropped. Both produced a plausible-looking
|
|
257
|
+
* byte array from damaged input, which then failed much later inside Seal's
|
|
258
|
+
* decrypt with an error that pointed nowhere near the real cause.
|
|
259
|
+
*/
|
|
222
260
|
declare function hexToBytes(hex: string): Uint8Array;
|
|
223
261
|
|
|
224
262
|
/** True when a content type is a browser-previewable image. */
|
|
@@ -238,4 +276,4 @@ declare function resolveOrigin(): string | null;
|
|
|
238
276
|
*/
|
|
239
277
|
declare function triggerDownload(objectUrl: string, filename: string): void;
|
|
240
278
|
|
|
241
|
-
export { BuildShareLinkArgs, DEFAULT_ENCRYPTION_OVERHEAD_BYTES, type DropzoneInputProps, type DropzoneRootProps, FilePhase, MorseFilesHandles, MorseFilesProvider, type MorseFilesProviderProps, ParsedShareLink, SealMaterial, UsdCostArgs, UsdCostEstimate, type UseClipboardResult, type UseDropzoneOptions, type UseDropzoneResult, UseFileDecryptResult, UseFileDownloadResult, UseFileUploadResult, UseMorseFilesOptions, UseMorseFilesResult, UseRecipientFileResult, UseStorageCostEstimateOptions, UseStorageCostEstimateResult, buildShareLink, bytesToHex, epochDurationMs, estimateStorageUsd, formatBytes, formatUsd, hexToBytes, isPreviewableImage, parseShareLink, resolveOrigin, triggerDownload, uploadPhaseLabel, uploadPhases, useClipboard, useDropzone, useFileDecrypt, useFileDownload, useFileUpload, useMorseFiles, useMorseFilesContext, useRecipientFile, useStorageCostEstimate };
|
|
279
|
+
export { BuildShareLinkArgs, DEFAULT_ENCRYPTION_OVERHEAD_BYTES, type DropzoneInputProps, type DropzoneRootProps, FilePhase, MorseFilesHandles, MorseFilesProvider, type MorseFilesProviderProps, ParsedShareLink, SealMaterial, UsdCostArgs, UsdCostEstimate, type UseClipboardResult, type UseDropzoneOptions, type UseDropzoneResult, UseFileDecryptResult, UseFileDownloadResult, UseFileUploadResult, UseMorseFilesOptions, UseMorseFilesResult, UseRecipientFileResult, UseStorageCostEstimateOptions, UseStorageCostEstimateResult, buildShareLink, bytesToHex, epochDurationMs, estimateStorageUsd, formatBytes, formatUsd, hexToBytes, isPreviewableImage, parseShareLink, resolveOrigin, resolveSealServers, triggerDownload, uploadErrorMessage, uploadPhaseLabel, uploadPhases, useClipboard, useDropzone, useFileDecrypt, useFileDownload, useFileUpload, useMorseFiles, useMorseFilesContext, useRecipientFile, useStorageCostEstimate };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { MorseFilesContext } from './chunk-
|
|
2
|
-
export { DEFAULT_ENCRYPTION_OVERHEAD_BYTES, buildShareLink, bytesToHex, epochDurationMs, estimateStorageUsd, formatBytes, formatUsd, isPreviewableImage, resolveOrigin, triggerDownload, uploadPhaseLabel, uploadPhases, useClipboard, useDropzone, useFileDecrypt, useFileDownload, useFileUpload, useMorseFilesContext, useRecipientFile, useStorageCostEstimate } from './chunk-
|
|
1
|
+
import { MorseFilesContext } from './chunk-CAUC2ROV.js';
|
|
2
|
+
export { DEFAULT_ENCRYPTION_OVERHEAD_BYTES, buildShareLink, bytesToHex, epochDurationMs, estimateStorageUsd, formatBytes, formatUsd, isPreviewableImage, resolveOrigin, triggerDownload, uploadErrorMessage, uploadPhaseLabel, uploadPhases, useClipboard, useDropzone, useFileDecrypt, useFileDownload, useFileUpload, useMorseFilesContext, useRecipientFile, useStorageCostEstimate } from './chunk-CAUC2ROV.js';
|
|
3
3
|
import { WalletStandardAdapter } from './chunk-A2AMSP5S.js';
|
|
4
4
|
import { jsx } from 'react/jsx-runtime';
|
|
5
5
|
import { morseConfig, WalletStandardSigner, DefaultWalrusWriteAdapter, HttpAggregatorReadAdapter, DefaultSealAdapter, RpcRecipientFilesReader, BrowserStoragePubkeyCache } from '@arcadiasystems/morse-sdk';
|
|
@@ -16,11 +16,19 @@ function MorseFilesProvider({
|
|
|
16
16
|
// src/utils/hex-to-bytes.ts
|
|
17
17
|
function hexToBytes(hex) {
|
|
18
18
|
const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
if (clean.length % 2 !== 0) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
`Invalid hex: expected an even number of digits, got ${clean.length}.`
|
|
22
|
+
);
|
|
22
23
|
}
|
|
23
|
-
|
|
24
|
+
if (!/^[0-9a-fA-F]*$/.test(clean)) {
|
|
25
|
+
throw new Error("Invalid hex: expected only the digits 0-9 and a-f.");
|
|
26
|
+
}
|
|
27
|
+
const bytes = new Uint8Array(clean.length / 2);
|
|
28
|
+
for (let i = 0; i < bytes.length; i += 1) {
|
|
29
|
+
bytes[i] = Number.parseInt(clean.slice(i * 2, i * 2 + 2), 16);
|
|
30
|
+
}
|
|
31
|
+
return bytes;
|
|
24
32
|
}
|
|
25
33
|
|
|
26
34
|
// src/files/parse-share-link.ts
|
|
@@ -33,15 +41,41 @@ function parseShareLink(href) {
|
|
|
33
41
|
if (rawFileId === void 0) {
|
|
34
42
|
return null;
|
|
35
43
|
}
|
|
44
|
+
const fileId = decodeURIComponent(rawFileId);
|
|
36
45
|
const params = new URLSearchParams(match?.[2] ?? "");
|
|
46
|
+
const network = parseNetwork(params.get("net"));
|
|
37
47
|
const prefix = params.get("p");
|
|
38
48
|
const nonce = params.get("n");
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
49
|
+
if (prefix === null && nonce === null) {
|
|
50
|
+
return { fileId, network, sealIdPrefix: null, sealNonce: null };
|
|
51
|
+
}
|
|
52
|
+
if (prefix === null || nonce === null) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
return {
|
|
57
|
+
fileId,
|
|
58
|
+
network,
|
|
59
|
+
sealIdPrefix: hexToBytes(prefix),
|
|
60
|
+
sealNonce: hexToBytes(nonce)
|
|
61
|
+
};
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function parseNetwork(value) {
|
|
67
|
+
if (value === "testnet" || value === "mainnet") {
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/files/resolve-seal-servers.ts
|
|
74
|
+
function resolveSealServers(fromOptions, fromConfig) {
|
|
75
|
+
if (fromOptions !== void 0 && fromOptions.length > 0) {
|
|
76
|
+
return fromOptions;
|
|
77
|
+
}
|
|
78
|
+
return fromConfig.length > 0 ? fromConfig : void 0;
|
|
45
79
|
}
|
|
46
80
|
function pickPubkeyCache() {
|
|
47
81
|
if (typeof window === "undefined") {
|
|
@@ -100,7 +134,15 @@ function useMorseFiles(options) {
|
|
|
100
134
|
signer
|
|
101
135
|
);
|
|
102
136
|
const walrusRead = current.walrusRead ?? HttpAggregatorReadAdapter.fromMorseConfig(config, client);
|
|
103
|
-
const
|
|
137
|
+
const sealServers = resolveSealServers(
|
|
138
|
+
current.sealKeyServers,
|
|
139
|
+
config.sealKeyServers
|
|
140
|
+
);
|
|
141
|
+
const seal = current.seal ?? (sealServers === void 0 ? null : DefaultSealAdapter.fromMorseConfig(
|
|
142
|
+
config,
|
|
143
|
+
{ serverConfigs: sealServers },
|
|
144
|
+
client
|
|
145
|
+
));
|
|
104
146
|
const reader = RpcRecipientFilesReader.fromConfig(client, {
|
|
105
147
|
packageId: config.packageId
|
|
106
148
|
});
|
|
@@ -133,6 +175,6 @@ function useMorseFiles(options) {
|
|
|
133
175
|
return result;
|
|
134
176
|
}
|
|
135
177
|
|
|
136
|
-
export { MorseFilesProvider, hexToBytes, parseShareLink, useMorseFiles };
|
|
178
|
+
export { MorseFilesProvider, hexToBytes, parseShareLink, resolveSealServers, useMorseFiles };
|
|
137
179
|
//# sourceMappingURL=index.js.map
|
|
138
180
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/files/morse-files-provider.tsx","../src/utils/hex-to-bytes.ts","../src/files/parse-share-link.ts","../src/files/pick-pubkey-cache.ts","../src/files/use-morse-files.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { ReactElement, ReactNode } from \"react\";\nimport { MorseFilesContext } from \"./context.js\";\nimport type { MorseFilesHandles } from \"./types.js\";\n\n/** Props for {@link MorseFilesProvider}. */\nexport interface MorseFilesProviderProps {\n\t/** The handles from {@link useMorseFiles} (memoized by that hook). */\n\treadonly value: MorseFilesHandles;\n\treadonly children: ReactNode;\n}\n\n/** Provides the injected files-flow handles to the hooks and components. */\nexport function MorseFilesProvider({\n\tvalue,\n\tchildren,\n}: MorseFilesProviderProps): ReactElement {\n\treturn (\n\t\t<MorseFilesContext.Provider value={value}>\n\t\t\t{children}\n\t\t</MorseFilesContext.Provider>\n\t);\n}\n","/** Decode a hex string (with or without a `0x` prefix) into bytes. */\nexport function hexToBytes(hex: string): Uint8Array {\n\tconst clean = hex.startsWith(\"0x\") ? hex.slice(2) : hex;\n\tconst out = new Uint8Array(Math.floor(clean.length / 2));\n\tfor (let i = 0; i < out.length; i++) {\n\t\tout[i] = Number.parseInt(clean.slice(i * 2, i * 2 + 2), 16);\n\t}\n\treturn out;\n}\n","import { hexToBytes } from \"../utils/hex-to-bytes.js\";\nimport type { ParsedShareLink } from \"./types.js\";\n\n// Matches the fragment shape \"f/<fileId>\" optionally followed by a query.\nconst FRAGMENT_RE = /^\\/?f\\/([^?]+)(?:\\?(.*))?$/;\n\n/**\n * Parse a share link produced by {@link buildShareLink}. Reads the fileId and\n * optional Seal material (prefix + nonce) from the URL fragment. Returns null\n * when the fragment does not match the expected shape; `sealIdPrefix`/`sealNonce`\n * are null for a public file (no seal material in the link).\n */\nexport function parseShareLink(href: string): ParsedShareLink | null {\n\tconst hashIndex = href.indexOf(\"#\");\n\tconst fragment = hashIndex >= 0 ? href.slice(hashIndex + 1) : href;\n\tconst match = FRAGMENT_RE.exec(fragment);\n\tconst rawFileId = match?.[1];\n\tif (rawFileId === undefined) {\n\t\treturn null;\n\t}\n\tconst params = new URLSearchParams(match?.[2] ?? \"\");\n\tconst prefix = params.get(\"p\");\n\tconst nonce = params.get(\"n\");\n\tconst hasSeal = prefix !== null && nonce !== null;\n\treturn {\n\t\tfileId: decodeURIComponent(rawFileId),\n\t\tsealIdPrefix: hasSeal ? hexToBytes(prefix) : null,\n\t\tsealNonce: hasSeal ? hexToBytes(nonce) : null,\n\t};\n}\n","import {\n\tBrowserStoragePubkeyCache,\n\ttype PubkeyCache,\n} from \"@arcadiasystems/morse-sdk\";\n\n/**\n * A localStorage-backed pubkey cache in browsers, undefined elsewhere. Lets\n * fromAccountAsync skip the Phantom probe popup on subsequent reloads.\n */\nexport function pickPubkeyCache(): PubkeyCache | undefined {\n\tif (typeof window === \"undefined\") {\n\t\treturn undefined;\n\t}\n\ttry {\n\t\treturn new BrowserStoragePubkeyCache();\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n","import {\n\tDefaultSealAdapter,\n\tDefaultWalrusWriteAdapter,\n\tHttpAggregatorReadAdapter,\n\tmorseConfig,\n\tRpcRecipientFilesReader,\n\tWalletStandardSigner,\n\ttype WalrusAdapterConfig,\n} from \"@arcadiasystems/morse-sdk\";\nimport type { SealCompatibleClient } from \"@mysten/seal\";\nimport { SuiGrpcClient } from \"@mysten/sui/grpc\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { WalletStandardAdapter } from \"../wallet-standard/wallet-standard-adapter.js\";\nimport { pickPubkeyCache } from \"./pick-pubkey-cache.js\";\nimport type { UseMorseFilesOptions, UseMorseFilesResult } from \"./types.js\";\n\n/**\n * Setup hook for the files flow. Builds the SDK handles from the connected\n * wallet: a `WalletStandardAdapter` for on-chain ops, a `WalletStandardSigner`\n * (via fromAccountAsync + a pubkey cache) for Walrus writes and the Seal\n * SessionKey, the Walrus write/read adapters, the Seal adapter, and the files\n * reader. Async because fromAccountAsync may probe the wallet once.\n *\n * Wrap your tree in `<MorseFilesProvider value={result.handles}>` once `status`\n * is `\"ready\"`.\n */\nexport function useMorseFiles(\n\toptions: UseMorseFilesOptions,\n): UseMorseFilesResult {\n\tconst { network } = options;\n\tconst address = options.account.address;\n\tconst [result, setResult] = useState<UseMorseFilesResult>({\n\t\tstatus: \"idle\",\n\t\thandles: null,\n\t\terror: null,\n\t});\n\n\t// Rebuild only when the network or wallet address changes. Everything else\n\t// (the account object, callbacks, and optional adapters) is read from the\n\t// latest options via a ref, so passing fresh object/callback literals on\n\t// every render does not retrigger setup and loop.\n\tconst optionsRef = useRef(options);\n\toptionsRef.current = options;\n\n\tuseEffect(() => {\n\t\tlet cancelled = false;\n\t\tsetResult({ status: \"preparing\", handles: null, error: null });\n\t\t(async () => {\n\t\t\ttry {\n\t\t\t\tconst current = optionsRef.current;\n\t\t\t\tconst { account, callbacks } = current;\n\t\t\t\tconst config = morseConfig({ network });\n\t\t\t\t// Use the provided gRPC client (e.g. a dedicated RPC) or build one on\n\t\t\t\t// the network's public RPC.\n\t\t\t\tconst client =\n\t\t\t\t\tcurrent.suiClient ??\n\t\t\t\t\tnew SuiGrpcClient({ network, baseUrl: config.rpcUrl });\n\t\t\t\tconst adapter = new WalletStandardAdapter(\n\t\t\t\t\taddress,\n\t\t\t\t\tcallbacks.signAndExecuteTransaction,\n\t\t\t\t\tclient,\n\t\t\t\t);\n\t\t\t\tconst pubkeyCache = current.pubkeyCache ?? pickPubkeyCache();\n\t\t\t\tconst signer = await WalletStandardSigner.fromAccountAsync(\n\t\t\t\t\taccount,\n\t\t\t\t\t{\n\t\t\t\t\t\tsignTransaction: callbacks.signTransaction,\n\t\t\t\t\t\tsignPersonalMessage: callbacks.signPersonalMessage,\n\t\t\t\t\t},\n\t\t\t\t\tpubkeyCache === undefined ? undefined : { pubkeyCache },\n\t\t\t\t);\n\t\t\t\tif (cancelled) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst walrusWrite =\n\t\t\t\t\tcurrent.walrusWrite ??\n\t\t\t\t\tDefaultWalrusWriteAdapter.fromConfig(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnetwork,\n\t\t\t\t\t\t\tsuiClient: client,\n\t\t\t\t\t\t\t...(current.walrusWriteConfig ?? {}),\n\t\t\t\t\t\t} as WalrusAdapterConfig,\n\t\t\t\t\t\tsigner,\n\t\t\t\t\t);\n\t\t\t\tconst walrusRead =\n\t\t\t\t\tcurrent.walrusRead ??\n\t\t\t\t\tHttpAggregatorReadAdapter.fromMorseConfig(config, client);\n\t\t\t\tconst seal = DefaultSealAdapter.fromMorseConfig(config, {}, client);\n\t\t\t\tconst reader = RpcRecipientFilesReader.fromConfig(client, {\n\t\t\t\t\tpackageId: config.packageId,\n\t\t\t\t});\n\t\t\t\tsetResult({\n\t\t\t\t\tstatus: \"ready\",\n\t\t\t\t\thandles: {\n\t\t\t\t\t\tnetwork,\n\t\t\t\t\t\tconfig,\n\t\t\t\t\t\tclient: client as unknown as SealCompatibleClient,\n\t\t\t\t\t\taddress: adapter.address,\n\t\t\t\t\t\tadapter,\n\t\t\t\t\t\tsigner,\n\t\t\t\t\t\twalrusWrite,\n\t\t\t\t\t\twalrusRead,\n\t\t\t\t\t\tseal,\n\t\t\t\t\t\treader,\n\t\t\t\t\t},\n\t\t\t\t\terror: null,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tif (!cancelled) {\n\t\t\t\t\tsetResult({ status: \"error\", handles: null, error });\n\t\t\t\t}\n\t\t\t}\n\t\t})();\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t};\n\t}, [network, address]);\n\n\treturn result;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/files/morse-files-provider.tsx","../src/utils/hex-to-bytes.ts","../src/files/parse-share-link.ts","../src/files/resolve-seal-servers.ts","../src/files/pick-pubkey-cache.ts","../src/files/use-morse-files.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { ReactElement, ReactNode } from \"react\";\nimport { MorseFilesContext } from \"./context.js\";\nimport type { MorseFilesHandles } from \"./types.js\";\n\n/** Props for {@link MorseFilesProvider}. */\nexport interface MorseFilesProviderProps {\n\t/** The handles from {@link useMorseFiles} (memoized by that hook). */\n\treadonly value: MorseFilesHandles;\n\treadonly children: ReactNode;\n}\n\n/** Provides the injected files-flow handles to the hooks and components. */\nexport function MorseFilesProvider({\n\tvalue,\n\tchildren,\n}: MorseFilesProviderProps): ReactElement {\n\treturn (\n\t\t<MorseFilesContext.Provider value={value}>\n\t\t\t{children}\n\t\t</MorseFilesContext.Provider>\n\t);\n}\n","/**\n * Decode a hex string to bytes, rejecting anything that is not valid hex.\n *\n * Throws rather than coercing. The previous version relied on TypedArray's\n * NaN -> 0 coercion, so \"zzzz\" decoded to [0, 0] and an odd-length \"abc\" to\n * [171] with the trailing nibble dropped. Both produced a plausible-looking\n * byte array from damaged input, which then failed much later inside Seal's\n * decrypt with an error that pointed nowhere near the real cause.\n */\nexport function hexToBytes(hex: string): Uint8Array {\n\tconst clean = hex.startsWith(\"0x\") ? hex.slice(2) : hex;\n\tif (clean.length % 2 !== 0) {\n\t\tthrow new Error(\n\t\t\t`Invalid hex: expected an even number of digits, got ${clean.length}.`,\n\t\t);\n\t}\n\tif (!/^[0-9a-fA-F]*$/.test(clean)) {\n\t\tthrow new Error(\"Invalid hex: expected only the digits 0-9 and a-f.\");\n\t}\n\tconst bytes = new Uint8Array(clean.length / 2);\n\tfor (let i = 0; i < bytes.length; i += 1) {\n\t\tbytes[i] = Number.parseInt(clean.slice(i * 2, i * 2 + 2), 16);\n\t}\n\treturn bytes;\n}\n","import { hexToBytes } from \"../utils/hex-to-bytes.js\";\nimport type { ParsedShareLink } from \"./types.js\";\n\n// Matches the fragment shape \"f/<fileId>\" optionally followed by a query.\nconst FRAGMENT_RE = /^\\/?f\\/([^?]+)(?:\\?(.*))?$/;\n\n/**\n * Parse a share link produced by {@link buildShareLink}. Reads the fileId, the\n * network, and the optional Seal material (prefix + nonce) from the URL\n * fragment. Returns null when the fragment does not match the expected shape;\n * `sealIdPrefix`/`sealNonce` are null for a public file (no seal material in\n * the link), and `network` is null for a link that omits it.\n *\n * A damaged link is null, never a public file. The seal params are a pair, so a\n * link carrying only one of them, or either one in unparseable hex, is rejected\n * rather than downgraded: reporting it as public would make the caller skip\n * decrypt and hand raw ciphertext to the user as if it were the file.\n */\nexport function parseShareLink(href: string): ParsedShareLink | null {\n\tconst hashIndex = href.indexOf(\"#\");\n\tconst fragment = hashIndex >= 0 ? href.slice(hashIndex + 1) : href;\n\tconst match = FRAGMENT_RE.exec(fragment);\n\tconst rawFileId = match?.[1];\n\tif (rawFileId === undefined) {\n\t\treturn null;\n\t}\n\tconst fileId = decodeURIComponent(rawFileId);\n\tconst params = new URLSearchParams(match?.[2] ?? \"\");\n\tconst network = parseNetwork(params.get(\"net\"));\n\tconst prefix = params.get(\"p\");\n\tconst nonce = params.get(\"n\");\n\tif (prefix === null && nonce === null) {\n\t\treturn { fileId, network, sealIdPrefix: null, sealNonce: null };\n\t}\n\tif (prefix === null || nonce === null) {\n\t\treturn null;\n\t}\n\ttry {\n\t\treturn {\n\t\t\tfileId,\n\t\t\tnetwork,\n\t\t\tsealIdPrefix: hexToBytes(prefix),\n\t\t\tsealNonce: hexToBytes(nonce),\n\t\t};\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n// An unknown value is null, not a rejected link: a future network name should\n// leave the caller on its own default rather than break the whole link.\nfunction parseNetwork(value: string | null): \"testnet\" | \"mainnet\" | null {\n\tif (value === \"testnet\" || value === \"mainnet\") {\n\t\treturn value;\n\t}\n\treturn null;\n}\n","import type { KeyServerConfig } from \"@mysten/seal\";\n\n/**\n * Pick the Seal key servers to build the default adapter from, or `undefined`\n * when there are none and the adapter must be skipped.\n *\n * Testnet pins an open allowlist, so `morseConfig` supplies servers there and\n * nothing is needed from the consumer. Mainnet pins none, because every\n * operator is commercial: without `sealKeyServers` the encrypted paths cannot\n * work, and the honest answer is no adapter rather than a broken one.\n *\n * Consumer-supplied servers win, so a consumer can point testnet at their own\n * operator too.\n */\nexport function resolveSealServers(\n\tfromOptions: readonly KeyServerConfig[] | undefined,\n\tfromConfig: readonly KeyServerConfig[],\n): readonly KeyServerConfig[] | undefined {\n\tif (fromOptions !== undefined && fromOptions.length > 0) {\n\t\treturn fromOptions;\n\t}\n\treturn fromConfig.length > 0 ? fromConfig : undefined;\n}\n","import {\n\tBrowserStoragePubkeyCache,\n\ttype PubkeyCache,\n} from \"@arcadiasystems/morse-sdk\";\n\n/**\n * A localStorage-backed pubkey cache in browsers, undefined elsewhere. Lets\n * fromAccountAsync skip the Phantom probe popup on subsequent reloads.\n */\nexport function pickPubkeyCache(): PubkeyCache | undefined {\n\tif (typeof window === \"undefined\") {\n\t\treturn undefined;\n\t}\n\ttry {\n\t\treturn new BrowserStoragePubkeyCache();\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n","import {\n\tDefaultSealAdapter,\n\tDefaultWalrusWriteAdapter,\n\tHttpAggregatorReadAdapter,\n\tmorseConfig,\n\tRpcRecipientFilesReader,\n\tWalletStandardSigner,\n\ttype WalrusAdapterConfig,\n} from \"@arcadiasystems/morse-sdk\";\nimport { SuiGrpcClient } from \"@mysten/sui/grpc\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { WalletStandardAdapter } from \"../wallet-standard/wallet-standard-adapter.js\";\nimport { pickPubkeyCache } from \"./pick-pubkey-cache.js\";\nimport { resolveSealServers } from \"./resolve-seal-servers.js\";\nimport type { UseMorseFilesOptions, UseMorseFilesResult } from \"./types.js\";\n\n/**\n * Setup hook for the files flow. Builds the SDK handles from the connected\n * wallet: a `WalletStandardAdapter` for on-chain ops, a `WalletStandardSigner`\n * (via fromAccountAsync + a pubkey cache) for Walrus writes and the Seal\n * SessionKey, the Walrus write/read adapters, the Seal adapter, and the files\n * reader. Async because fromAccountAsync may probe the wallet once.\n *\n * Wrap your tree in `<MorseFilesProvider value={result.handles}>` once `status`\n * is `\"ready\"`.\n */\nexport function useMorseFiles(\n\toptions: UseMorseFilesOptions,\n): UseMorseFilesResult {\n\tconst { network } = options;\n\tconst address = options.account.address;\n\tconst [result, setResult] = useState<UseMorseFilesResult>({\n\t\tstatus: \"idle\",\n\t\thandles: null,\n\t\terror: null,\n\t});\n\n\t// Rebuild only when the network or wallet address changes. Everything else\n\t// (the account object, callbacks, and optional adapters) is read from the\n\t// latest options via a ref, so passing fresh object/callback literals on\n\t// every render does not retrigger setup and loop.\n\tconst optionsRef = useRef(options);\n\toptionsRef.current = options;\n\n\tuseEffect(() => {\n\t\tlet cancelled = false;\n\t\tsetResult({ status: \"preparing\", handles: null, error: null });\n\t\t(async () => {\n\t\t\ttry {\n\t\t\t\tconst current = optionsRef.current;\n\t\t\t\tconst { account, callbacks } = current;\n\t\t\t\tconst config = morseConfig({ network });\n\t\t\t\t// Use the provided gRPC client (e.g. a dedicated RPC) or build one on\n\t\t\t\t// the network's public RPC.\n\t\t\t\tconst client =\n\t\t\t\t\tcurrent.suiClient ??\n\t\t\t\t\tnew SuiGrpcClient({ network, baseUrl: config.rpcUrl });\n\t\t\t\tconst adapter = new WalletStandardAdapter(\n\t\t\t\t\taddress,\n\t\t\t\t\tcallbacks.signAndExecuteTransaction,\n\t\t\t\t\tclient,\n\t\t\t\t);\n\t\t\t\tconst pubkeyCache = current.pubkeyCache ?? pickPubkeyCache();\n\t\t\t\tconst signer = await WalletStandardSigner.fromAccountAsync(\n\t\t\t\t\taccount,\n\t\t\t\t\t{\n\t\t\t\t\t\tsignTransaction: callbacks.signTransaction,\n\t\t\t\t\t\tsignPersonalMessage: callbacks.signPersonalMessage,\n\t\t\t\t\t},\n\t\t\t\t\tpubkeyCache === undefined ? undefined : { pubkeyCache },\n\t\t\t\t);\n\t\t\t\tif (cancelled) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst walrusWrite =\n\t\t\t\t\tcurrent.walrusWrite ??\n\t\t\t\t\tDefaultWalrusWriteAdapter.fromConfig(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnetwork,\n\t\t\t\t\t\t\tsuiClient: client,\n\t\t\t\t\t\t\t...(current.walrusWriteConfig ?? {}),\n\t\t\t\t\t\t} as WalrusAdapterConfig,\n\t\t\t\t\t\tsigner,\n\t\t\t\t\t);\n\t\t\t\tconst walrusRead =\n\t\t\t\t\tcurrent.walrusRead ??\n\t\t\t\t\tHttpAggregatorReadAdapter.fromMorseConfig(config, client);\n\t\t\t\t// Built only when key servers exist. Testnet pins an open set;\n\t\t\t\t// mainnet pins none, because every operator there is commercial.\n\t\t\t\t// Failing setup outright would take the unencrypted flows down\n\t\t\t\t// too, so this is null and the encrypted hooks reject instead.\n\t\t\t\tconst sealServers = resolveSealServers(\n\t\t\t\t\tcurrent.sealKeyServers,\n\t\t\t\t\tconfig.sealKeyServers,\n\t\t\t\t);\n\t\t\t\tconst seal =\n\t\t\t\t\tcurrent.seal ??\n\t\t\t\t\t(sealServers === undefined\n\t\t\t\t\t\t? null\n\t\t\t\t\t\t: DefaultSealAdapter.fromMorseConfig(\n\t\t\t\t\t\t\t\tconfig,\n\t\t\t\t\t\t\t\t{ serverConfigs: sealServers },\n\t\t\t\t\t\t\t\tclient,\n\t\t\t\t\t\t\t));\n\t\t\t\tconst reader = RpcRecipientFilesReader.fromConfig(client, {\n\t\t\t\t\tpackageId: config.packageId,\n\t\t\t\t});\n\t\t\t\tsetResult({\n\t\t\t\t\tstatus: \"ready\",\n\t\t\t\t\thandles: {\n\t\t\t\t\t\tnetwork,\n\t\t\t\t\t\tconfig,\n\t\t\t\t\t\tclient,\n\t\t\t\t\t\taddress: adapter.address,\n\t\t\t\t\t\tadapter,\n\t\t\t\t\t\tsigner,\n\t\t\t\t\t\twalrusWrite,\n\t\t\t\t\t\twalrusRead,\n\t\t\t\t\t\tseal,\n\t\t\t\t\t\treader,\n\t\t\t\t\t},\n\t\t\t\t\terror: null,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tif (!cancelled) {\n\t\t\t\t\tsetResult({ status: \"error\", handles: null, error });\n\t\t\t\t}\n\t\t\t}\n\t\t})();\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t};\n\t}, [network, address]);\n\n\treturn result;\n}\n"]}
|
package/dist/styles.css
CHANGED
|
@@ -89,6 +89,24 @@
|
|
|
89
89
|
font-size: 0.8125rem;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
/*
|
|
93
|
+
* The native file input the dropzone wraps. Hidden visually, not removed: the
|
|
94
|
+
* root's onClick calls input.click(), and the input stays in the accessibility
|
|
95
|
+
* tree for assistive tech. Do not fold this into .mu-input, which styles the
|
|
96
|
+
* visible text fields.
|
|
97
|
+
*/
|
|
98
|
+
.mu-file-input {
|
|
99
|
+
position: absolute;
|
|
100
|
+
width: 1px;
|
|
101
|
+
height: 1px;
|
|
102
|
+
padding: 0;
|
|
103
|
+
border: 0;
|
|
104
|
+
margin: -1px;
|
|
105
|
+
overflow: hidden;
|
|
106
|
+
clip-path: inset(50%);
|
|
107
|
+
white-space: nowrap;
|
|
108
|
+
}
|
|
109
|
+
|
|
92
110
|
/* ----- Inputs / fields ----- */
|
|
93
111
|
.mu-input,
|
|
94
112
|
.mu-epochs-input {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RecipientFileId, RecipientFile, FileUploadProgressEvent, SuiAddress, NetworkConfig, WalletAdapter, WalrusWriteAdapter, WalrusFlowCapable, WalrusReadAdapter, SealAdapter, RecipientFilesReader, WalletSignTransaction, WalletSignPersonalMessage, WalletStandardAccount, PubkeyCache } from '@arcadiasystems/morse-sdk';
|
|
2
|
-
import { SealCompatibleClient } from '@mysten/seal';
|
|
2
|
+
import { SealCompatibleClient, KeyServerConfig } from '@mysten/seal';
|
|
3
3
|
import { Signer } from '@mysten/sui/cryptography';
|
|
4
4
|
import { SuiGrpcClient } from '@mysten/sui/grpc';
|
|
5
5
|
import { Transaction } from '@mysten/sui/transactions';
|
|
@@ -45,6 +45,17 @@ interface UseMorseFilesOptions {
|
|
|
45
45
|
};
|
|
46
46
|
/** Prebuilt Walrus read adapter; defaults to the aggregator. */
|
|
47
47
|
readonly walrusRead?: WalrusReadAdapter;
|
|
48
|
+
/** Prebuilt Seal adapter; overrides the default and `sealKeyServers`. */
|
|
49
|
+
readonly seal?: SealAdapter;
|
|
50
|
+
/**
|
|
51
|
+
* Seal key servers for the default adapter. Testnet pins an open set, so
|
|
52
|
+
* this is only needed on mainnet, where every operator is commercial and
|
|
53
|
+
* `morseConfig` therefore pins none. Start from the SDK's
|
|
54
|
+
* `MAINNET_SEAL_COMMITTEE` and add the `apiKeyName` / `apiKey` your
|
|
55
|
+
* operator issues. Without it, `handles.seal` is `null` and only the
|
|
56
|
+
* unencrypted paths work.
|
|
57
|
+
*/
|
|
58
|
+
readonly sealKeyServers?: readonly KeyServerConfig[];
|
|
48
59
|
/** Pubkey cache for fromAccountAsync; defaults to BrowserStoragePubkeyCache. */
|
|
49
60
|
readonly pubkeyCache?: PubkeyCache;
|
|
50
61
|
}
|
|
@@ -68,7 +79,16 @@ interface MorseFilesHandles {
|
|
|
68
79
|
/** Flow-capable write adapter (required for the single-PTB file ops). */
|
|
69
80
|
readonly walrusWrite: WalrusWriteAdapter & WalrusFlowCapable;
|
|
70
81
|
readonly walrusRead: WalrusReadAdapter;
|
|
71
|
-
|
|
82
|
+
/**
|
|
83
|
+
* `null` when no Seal key servers are available, which on mainnet means no
|
|
84
|
+
* credential was supplied. Unencrypted upload and download still work; the
|
|
85
|
+
* encrypted paths reject with a message naming `sealKeyServers`.
|
|
86
|
+
*
|
|
87
|
+
* Nullable rather than absent so the setup hook still succeeds: building it
|
|
88
|
+
* eagerly would fail the whole hook on mainnet and take the public flows
|
|
89
|
+
* down with it.
|
|
90
|
+
*/
|
|
91
|
+
readonly seal: SealAdapter | null;
|
|
72
92
|
readonly reader: RecipientFilesReader;
|
|
73
93
|
}
|
|
74
94
|
/** Status of the async {@link useMorseFiles} setup. */
|
|
@@ -184,7 +204,7 @@ interface UseFileDownloadResult {
|
|
|
184
204
|
interface UsdCostArgs {
|
|
185
205
|
readonly sizeBytes: number;
|
|
186
206
|
readonly epochs: number;
|
|
187
|
-
/** Epoch length in ms
|
|
207
|
+
/** Epoch length in ms; see {@link epochDurationMs} for the nominal values. */
|
|
188
208
|
readonly epochDurationMs: number;
|
|
189
209
|
/** Bytes the Seal envelope adds when encrypting; folded into the priced size. */
|
|
190
210
|
readonly encryptionOverheadBytes?: number;
|
|
@@ -239,6 +259,11 @@ interface BuildShareLinkArgs {
|
|
|
239
259
|
readonly baseUrl?: string;
|
|
240
260
|
/** Fragment path pattern containing `:fileId`; defaults to `/#/f/:fileId`. */
|
|
241
261
|
readonly pattern?: string;
|
|
262
|
+
/**
|
|
263
|
+
* Network the file lives on, carried in the link as `net`. A file id means
|
|
264
|
+
* nothing without it: the same id on the other network is simply not found.
|
|
265
|
+
*/
|
|
266
|
+
readonly network?: "testnet" | "mainnet";
|
|
242
267
|
}
|
|
243
268
|
/** Result of {@link parseShareLink}. */
|
|
244
269
|
interface ParsedShareLink {
|
|
@@ -246,6 +271,12 @@ interface ParsedShareLink {
|
|
|
246
271
|
/** Seal material if present in the link, else null (public file). */
|
|
247
272
|
readonly sealIdPrefix: Uint8Array | null;
|
|
248
273
|
readonly sealNonce: Uint8Array | null;
|
|
274
|
+
/**
|
|
275
|
+
* Network from the link's `net`, or null when the link omits it or names
|
|
276
|
+
* one this version does not know. Read it before looking the file up; the
|
|
277
|
+
* id resolves on one network only.
|
|
278
|
+
*/
|
|
279
|
+
readonly network: "testnet" | "mainnet" | null;
|
|
249
280
|
}
|
|
250
281
|
|
|
251
282
|
export type { BuildShareLinkArgs as B, CompletedFileUpload as C, FilePhase as F, LoadStatus as L, MorseFilesHandles as M, ParsedShareLink as P, SealMaterial as S, UsdCostArgs as U, UsdCostEstimate as a, UseFileDecryptResult as b, UseFileDownloadResult as c, UseFileUploadResult as d, UseMorseFilesOptions as e, UseMorseFilesResult as f, UseRecipientFileResult as g, UseStorageCostEstimateOptions as h, UseStorageCostEstimateResult as i, FileDecryptInput as j, FileDecryptStatus as k, FileDownloadStatus as l, FileUploadInput as m, FileUploadStatus as n, MorseFileDownloaderProps as o, MorseFileUploaderProps as p, MorseFilesWalletCallbacks as q, SetupStatus as r };
|
package/dist/ui.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { o as MorseFileDownloaderProps, p as MorseFileUploaderProps } from './types-
|
|
1
|
+
import { o as MorseFileDownloaderProps, p as MorseFileUploaderProps } from './types-C6w30zCM.js';
|
|
2
2
|
import { ReactElement } from 'react';
|
|
3
3
|
import '@arcadiasystems/morse-sdk';
|
|
4
4
|
import '@mysten/seal';
|
package/dist/ui.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useFileDownload, formatBytes, useFileUpload, useClipboard, useStorageCostEstimate, useDropzone, uploadPhases, formatUsd, uploadPhaseLabel } from './chunk-
|
|
1
|
+
import { useFileDownload, formatBytes, useFileUpload, useClipboard, useMorseFilesContext, useStorageCostEstimate, useDropzone, uploadPhases, uploadErrorMessage, formatUsd, uploadPhaseLabel } from './chunk-CAUC2ROV.js';
|
|
2
2
|
import { formatUserMessage } from '@arcadiasystems/morse-sdk';
|
|
3
3
|
import { useMemo, useState } from 'react';
|
|
4
4
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
@@ -81,6 +81,7 @@ function MorseFileUploader(props) {
|
|
|
81
81
|
const [epochs, setEpochs] = useState(defaultEpochs ?? DEFAULT_EPOCHS);
|
|
82
82
|
const upload = useFileUpload();
|
|
83
83
|
const clipboard = useClipboard();
|
|
84
|
+
const { network } = useMorseFilesContext();
|
|
84
85
|
const { estimate } = useStorageCostEstimate({
|
|
85
86
|
sizeBytes: file?.size ?? 0,
|
|
86
87
|
epochs,
|
|
@@ -109,10 +110,10 @@ function MorseFileUploader(props) {
|
|
|
109
110
|
};
|
|
110
111
|
const phases = uploadPhases(encrypt);
|
|
111
112
|
const activeIndex = upload.phase === null ? -1 : phases.indexOf(upload.phase);
|
|
112
|
-
const errorMessage = upload.error === null ? null :
|
|
113
|
+
const errorMessage = upload.error === null ? null : uploadErrorMessage(upload.error, network);
|
|
113
114
|
return /* @__PURE__ */ jsxs("div", { className: `mu-uploader ${className ?? ""}`, "data-mu": "uploader", children: [
|
|
114
115
|
/* @__PURE__ */ jsxs("div", { className: "mu-dropzone", ...getRootProps(), children: [
|
|
115
|
-
/* @__PURE__ */ jsx("input", { className: "mu-input", ...getInputProps() }),
|
|
116
|
+
/* @__PURE__ */ jsx("input", { className: "mu-file-input", ...getInputProps() }),
|
|
116
117
|
/* @__PURE__ */ jsx("span", { className: "mu-dropzone-icon", "aria-hidden": "true" }),
|
|
117
118
|
file === null ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
118
119
|
/* @__PURE__ */ jsx("span", { className: "mu-dropzone-hint", children: "Drag a file here, or click to browse" }),
|
package/dist/ui.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ui/morse-file-downloader.tsx","../src/utils/clamp.ts","../src/ui/morse-file-uploader.tsx"],"names":["formatUserMessage","jsxs","jsx"],"mappings":"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","file":"ui.js","sourcesContent":["import { formatUserMessage } from \"@arcadiasystems/morse-sdk\";\nimport { type ReactElement, useMemo } from \"react\";\nimport type { MorseFileDownloaderProps, SealMaterial } from \"../files/types.js\";\nimport { useFileDownload } from \"../files/use-file-download.js\";\nimport { formatBytes } from \"../utils/format-bytes.js\";\n\n/**\n * Drop-in single-file viewer/downloader for a share link. Loads the on-chain\n * metadata, then downloads (public read) or decrypts (recipient) the bytes with\n * the correct filename and MIME type. A RecipientFile has no on-chain encrypted\n * flag, so encryption is inferred from Seal material in the share link. Logic\n * lives in {@link useFileDownload}.\n */\nexport function MorseFileDownloader(\n\tprops: MorseFileDownloaderProps,\n): ReactElement {\n\tconst { fileId, sealIdPrefix, sealNonce, className } = props;\n\tconst seal = useMemo<SealMaterial | null>(() => {\n\t\tif (sealIdPrefix === undefined || sealIdPrefix === null) {\n\t\t\treturn null;\n\t\t}\n\t\tif (sealNonce === undefined || sealNonce === null) {\n\t\t\treturn null;\n\t\t}\n\t\treturn { sealIdPrefix, sealNonce };\n\t}, [sealIdPrefix, sealNonce]);\n\n\tconst dl = useFileDownload(fileId, seal);\n\tconst { file } = dl;\n\n\tif (dl.metaStatus === \"loading\" || dl.metaStatus === \"idle\") {\n\t\treturn (\n\t\t\t<section className={`mu-view ${className ?? \"\"}`} data-mu=\"view\">\n\t\t\t\t<span className=\"mu-muted\">Loading file...</span>\n\t\t\t</section>\n\t\t);\n\t}\n\n\tif (dl.metaStatus === \"error\" || file === null) {\n\t\treturn (\n\t\t\t<section className={`mu-view ${className ?? \"\"}`} data-mu=\"view\">\n\t\t\t\t<p className=\"mu-error\" role=\"alert\">\n\t\t\t\t\tCould not load this file. The id may be wrong or the record may have\n\t\t\t\t\tbeen deleted.\n\t\t\t\t</p>\n\t\t\t</section>\n\t\t);\n\t}\n\n\tconst bytesError = dl.error === null ? null : formatUserMessage(dl.error);\n\n\treturn (\n\t\t<section className={`mu-view ${className ?? \"\"}`} data-mu=\"view\">\n\t\t\t<header className=\"mu-view-head\">\n\t\t\t\t<strong className=\"mu-view-name\">{file.name}</strong>\n\t\t\t\t<span className=\"mu-muted\">\n\t\t\t\t\t{file.contentType} · {formatBytes(file.size)}\n\t\t\t\t\t{dl.encrypted ? \" · encrypted\" : \"\"}\n\t\t\t\t</span>\n\t\t\t</header>\n\n\t\t\t<div className=\"mu-view-body\">\n\t\t\t\t{dl.status === \"ready\" && dl.isImage && dl.objectUrl !== null ? (\n\t\t\t\t\t<img className=\"mu-view-img\" src={dl.objectUrl} alt={file.name} />\n\t\t\t\t) : null}\n\n\t\t\t\t{dl.status === \"ready\" ? (\n\t\t\t\t\t<button type=\"button\" className=\"mu-upload\" onClick={() => dl.save()}>\n\t\t\t\t\t\tSave file\n\t\t\t\t\t</button>\n\t\t\t\t) : (\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclassName=\"mu-upload\"\n\t\t\t\t\t\tdisabled={dl.status === \"loading\"}\n\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\tvoid (dl.isImage ? dl.load() : dl.download());\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{dl.status === \"loading\"\n\t\t\t\t\t\t\t? dl.encrypted\n\t\t\t\t\t\t\t\t? \"Decrypting...\"\n\t\t\t\t\t\t\t\t: \"Downloading...\"\n\t\t\t\t\t\t\t: dl.encrypted\n\t\t\t\t\t\t\t\t? \"Decrypt and open\"\n\t\t\t\t\t\t\t\t: \"Download\"}\n\t\t\t\t\t</button>\n\t\t\t\t)}\n\n\t\t\t\t{bytesError !== null ? (\n\t\t\t\t\t<div className=\"mu-error\" role=\"alert\">\n\t\t\t\t\t\t<strong>{bytesError.title}</strong>\n\t\t\t\t\t\t<span>{bytesError.description}</span>\n\t\t\t\t\t</div>\n\t\t\t\t) : null}\n\t\t\t</div>\n\t\t</section>\n\t);\n}\n","/** Clamp a number to the inclusive [min, max] range. */\nexport function clamp(value: number, min: number, max: number): number {\n\tif (value < min) {\n\t\treturn min;\n\t}\n\tif (value > max) {\n\t\treturn max;\n\t}\n\treturn value;\n}\n","import { formatUserMessage } from \"@arcadiasystems/morse-sdk\";\nimport { type ReactElement, useState } from \"react\";\nimport type { MorseFileUploaderProps } from \"../files/types.js\";\nimport { uploadPhaseLabel } from \"../files/upload-phase-label.js\";\nimport { uploadPhases } from \"../files/upload-phases.js\";\nimport { useFileUpload } from \"../files/use-file-upload.js\";\nimport { useStorageCostEstimate } from \"../files/use-storage-cost-estimate.js\";\nimport { useClipboard } from \"../primitives/use-clipboard.js\";\nimport { useDropzone } from \"../primitives/use-dropzone.js\";\nimport { clamp } from \"../utils/clamp.js\";\nimport { formatBytes } from \"../utils/format-bytes.js\";\nimport { formatUsd } from \"../utils/format-usd.js\";\n\nconst DEFAULT_EPOCHS = 5;\n// Walrus caps a lease at 53 epochs ahead on both networks.\nconst WALRUS_MAX_EPOCHS = 53;\n\n/**\n * Drop-in file uploader for the 0.4.0 RecipientFile flow. Pick a file, choose a\n * storage duration, and upload. Encryption and the recipient set are controlled\n * by props (`encrypt`, `recipients`); the connected wallet is auto-added as a\n * recipient on chain. Emits the completed upload (with seal material and share\n * link) via `onUploaded`. All logic lives in hooks; this component only renders.\n */\nexport function MorseFileUploader(props: MorseFileUploaderProps): ReactElement {\n\tconst {\n\t\tclassName,\n\t\trecipients,\n\t\tencrypt = false,\n\t\tdefaultEpochs,\n\t\tminEpochs = 1,\n\t\tmaxEpochs = WALRUS_MAX_EPOCHS,\n\t\tonUploaded,\n\t} = props;\n\n\tconst [file, setFile] = useState<File | null>(null);\n\tconst [epochs, setEpochs] = useState(defaultEpochs ?? DEFAULT_EPOCHS);\n\tconst upload = useFileUpload();\n\tconst clipboard = useClipboard();\n\n\tconst { estimate } = useStorageCostEstimate({\n\t\tsizeBytes: file?.size ?? 0,\n\t\tepochs,\n\t\tencrypt,\n\t});\n\n\tconst { getRootProps, getInputProps } = useDropzone({\n\t\tmultiple: false,\n\t\tariaLabel: \"Choose a file to upload\",\n\t\tonFiles: (files) => setFile(files[0] ?? null),\n\t});\n\n\tconst handleUpload = async () => {\n\t\tif (file === null) {\n\t\t\treturn;\n\t\t}\n\t\tconst result = await upload.upload({\n\t\t\tfile,\n\t\t\tepochs,\n\t\t\tencrypt,\n\t\t\t...(recipients === undefined ? {} : { recipients }),\n\t\t});\n\t\tonUploaded?.(result);\n\t};\n\n\tconst handleReset = () => {\n\t\tupload.reset();\n\t\tsetFile(null);\n\t};\n\n\tconst phases = uploadPhases(encrypt);\n\tconst activeIndex = upload.phase === null ? -1 : phases.indexOf(upload.phase);\n\tconst errorMessage =\n\t\tupload.error === null ? null : formatUserMessage(upload.error);\n\n\treturn (\n\t\t<div className={`mu-uploader ${className ?? \"\"}`} data-mu=\"uploader\">\n\t\t\t<div className=\"mu-dropzone\" {...getRootProps()}>\n\t\t\t\t<input className=\"mu-input\" {...getInputProps()} />\n\t\t\t\t<span className=\"mu-dropzone-icon\" aria-hidden=\"true\" />\n\t\t\t\t{file === null ? (\n\t\t\t\t\t<>\n\t\t\t\t\t\t<span className=\"mu-dropzone-hint\">\n\t\t\t\t\t\t\tDrag a file here, or click to browse\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span className=\"mu-dropzone-sub\">Any file type</span>\n\t\t\t\t\t</>\n\t\t\t\t) : (\n\t\t\t\t\t<>\n\t\t\t\t\t\t<span className=\"mu-dropzone-hint\">{file.name}</span>\n\t\t\t\t\t\t<span className=\"mu-dropzone-sub\">\n\t\t\t\t\t\t\t{formatBytes(file.size)} · click to replace\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</>\n\t\t\t\t)}\n\t\t\t</div>\n\n\t\t\t<label className=\"mu-field\">\n\t\t\t\t<span className=\"mu-field-label\">Storage duration (epochs)</span>\n\t\t\t\t<input\n\t\t\t\t\tclassName=\"mu-epochs-input\"\n\t\t\t\t\ttype=\"number\"\n\t\t\t\t\tmin={minEpochs}\n\t\t\t\t\tmax={maxEpochs}\n\t\t\t\t\tvalue={epochs}\n\t\t\t\t\tonChange={(event) =>\n\t\t\t\t\t\tsetEpochs(\n\t\t\t\t\t\t\tclamp(\n\t\t\t\t\t\t\t\tNumber.parseInt(event.target.value, 10) || minEpochs,\n\t\t\t\t\t\t\t\tminEpochs,\n\t\t\t\t\t\t\t\tmaxEpochs,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t/>\n\t\t\t\t<span\n\t\t\t\t\tclassName=\"mu-field-help\"\n\t\t\t\t\ttitle={`Walrus storage is a lease, not permanent. You can buy at most ${maxEpochs} epochs ahead. An epoch is about 1 day on testnet and about 2 weeks on mainnet.`}\n\t\t\t\t>\n\t\t\t\t\t{`≈ ${Math.round(estimate.days)} day${\n\t\t\t\t\t\tMath.round(estimate.days) === 1 ? \"\" : \"s\"\n\t\t\t\t\t} of storage · max ${maxEpochs} epochs`}\n\t\t\t\t</span>\n\t\t\t</label>\n\n\t\t\t<div className=\"mu-cost\" data-mu=\"cost\">\n\t\t\t\t<span className=\"mu-cost-label\">\n\t\t\t\t\t{encrypt\n\t\t\t\t\t\t? \"Estimated storage cost (encrypted)\"\n\t\t\t\t\t\t: \"Estimated storage cost\"}\n\t\t\t\t</span>\n\t\t\t\t<span className=\"mu-cost-value\">{formatUsd(estimate.usd)}</span>\n\t\t\t</div>\n\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclassName=\"mu-upload\"\n\t\t\t\tdisabled={file === null || upload.status === \"uploading\"}\n\t\t\t\tonClick={() => {\n\t\t\t\t\tvoid handleUpload();\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{upload.status === \"uploading\"\n\t\t\t\t\t? \"Uploading...\"\n\t\t\t\t\t: encrypt\n\t\t\t\t\t\t? \"Encrypt and upload\"\n\t\t\t\t\t\t: \"Upload\"}\n\t\t\t</button>\n\n\t\t\t{upload.status === \"uploading\" ? (\n\t\t\t\t<ol className=\"mu-stepper\" data-mu=\"stepper\">\n\t\t\t\t\t{phases.map((phase, index) => (\n\t\t\t\t\t\t<li\n\t\t\t\t\t\t\tkey={phase}\n\t\t\t\t\t\t\tclassName=\"mu-step\"\n\t\t\t\t\t\t\tdata-state={\n\t\t\t\t\t\t\t\tindex < activeIndex\n\t\t\t\t\t\t\t\t\t? \"done\"\n\t\t\t\t\t\t\t\t\t: index === activeIndex\n\t\t\t\t\t\t\t\t\t\t? \"active\"\n\t\t\t\t\t\t\t\t\t\t: \"pending\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<span className=\"mu-step-dot\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t{index + 1}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span className=\"mu-step-label\">{uploadPhaseLabel(phase)}</span>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t))}\n\t\t\t\t</ol>\n\t\t\t) : null}\n\n\t\t\t{errorMessage !== null ? (\n\t\t\t\t<div className=\"mu-error\" role=\"alert\">\n\t\t\t\t\t<strong>{errorMessage.title}</strong>\n\t\t\t\t\t<span>{errorMessage.description}</span>\n\t\t\t\t</div>\n\t\t\t) : null}\n\n\t\t\t{upload.data !== null ? (\n\t\t\t\t<div className=\"mu-result\" data-mu=\"result\">\n\t\t\t\t\t<span className=\"mu-result-head\">\n\t\t\t\t\t\t<span className=\"mu-result-badge\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t✓\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<strong>\n\t\t\t\t\t\t\t{upload.data.encrypted ? \"Encrypted and uploaded\" : \"Uploaded\"}\n\t\t\t\t\t\t</strong>\n\t\t\t\t\t</span>\n\t\t\t\t\t<div className=\"mu-result-actions\">\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\tclassName=\"mu-upload mu-result-link\"\n\t\t\t\t\t\t\thref={upload.data.shareLink}\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noreferrer\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tOpen share link\n\t\t\t\t\t\t</a>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tclassName=\"mu-ghost mu-result-copy\"\n\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\tvoid clipboard.copy(upload.data?.shareLink ?? \"\");\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{clipboard.copied ? \"Copied!\" : \"Copy link\"}\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<button type=\"button\" className=\"mu-ghost\" onClick={handleReset}>\n\t\t\t\t\t\t\tUpload another\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</div>\n\t\t\t\t\t<code className=\"mu-result-id\">{upload.data.fileId}</code>\n\t\t\t\t\t{upload.data.encrypted ? (\n\t\t\t\t\t\t<span className=\"mu-result-note\">\n\t\t\t\t\t\t\tThe share link carries the decryption key; only recipients can\n\t\t\t\t\t\t\topen it.\n\t\t\t\t\t\t</span>\n\t\t\t\t\t) : null}\n\t\t\t\t</div>\n\t\t\t) : null}\n\t\t</div>\n\t);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/ui/morse-file-downloader.tsx","../src/utils/clamp.ts","../src/ui/morse-file-uploader.tsx"],"names":["jsxs","jsx"],"mappings":"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","file":"ui.js","sourcesContent":["import { formatUserMessage } from \"@arcadiasystems/morse-sdk\";\nimport { type ReactElement, useMemo } from \"react\";\nimport type { MorseFileDownloaderProps, SealMaterial } from \"../files/types.js\";\nimport { useFileDownload } from \"../files/use-file-download.js\";\nimport { formatBytes } from \"../utils/format-bytes.js\";\n\n/**\n * Drop-in single-file viewer/downloader for a share link. Loads the on-chain\n * metadata, then downloads (public read) or decrypts (recipient) the bytes with\n * the correct filename and MIME type. A RecipientFile has no on-chain encrypted\n * flag, so encryption is inferred from Seal material in the share link. Logic\n * lives in {@link useFileDownload}.\n */\nexport function MorseFileDownloader(\n\tprops: MorseFileDownloaderProps,\n): ReactElement {\n\tconst { fileId, sealIdPrefix, sealNonce, className } = props;\n\tconst seal = useMemo<SealMaterial | null>(() => {\n\t\tif (sealIdPrefix === undefined || sealIdPrefix === null) {\n\t\t\treturn null;\n\t\t}\n\t\tif (sealNonce === undefined || sealNonce === null) {\n\t\t\treturn null;\n\t\t}\n\t\treturn { sealIdPrefix, sealNonce };\n\t}, [sealIdPrefix, sealNonce]);\n\n\tconst dl = useFileDownload(fileId, seal);\n\tconst { file } = dl;\n\n\tif (dl.metaStatus === \"loading\" || dl.metaStatus === \"idle\") {\n\t\treturn (\n\t\t\t<section className={`mu-view ${className ?? \"\"}`} data-mu=\"view\">\n\t\t\t\t<span className=\"mu-muted\">Loading file...</span>\n\t\t\t</section>\n\t\t);\n\t}\n\n\tif (dl.metaStatus === \"error\" || file === null) {\n\t\treturn (\n\t\t\t<section className={`mu-view ${className ?? \"\"}`} data-mu=\"view\">\n\t\t\t\t<p className=\"mu-error\" role=\"alert\">\n\t\t\t\t\tCould not load this file. The id may be wrong or the record may have\n\t\t\t\t\tbeen deleted.\n\t\t\t\t</p>\n\t\t\t</section>\n\t\t);\n\t}\n\n\tconst bytesError = dl.error === null ? null : formatUserMessage(dl.error);\n\n\treturn (\n\t\t<section className={`mu-view ${className ?? \"\"}`} data-mu=\"view\">\n\t\t\t<header className=\"mu-view-head\">\n\t\t\t\t<strong className=\"mu-view-name\">{file.name}</strong>\n\t\t\t\t<span className=\"mu-muted\">\n\t\t\t\t\t{file.contentType} · {formatBytes(file.size)}\n\t\t\t\t\t{dl.encrypted ? \" · encrypted\" : \"\"}\n\t\t\t\t</span>\n\t\t\t</header>\n\n\t\t\t<div className=\"mu-view-body\">\n\t\t\t\t{dl.status === \"ready\" && dl.isImage && dl.objectUrl !== null ? (\n\t\t\t\t\t<img className=\"mu-view-img\" src={dl.objectUrl} alt={file.name} />\n\t\t\t\t) : null}\n\n\t\t\t\t{dl.status === \"ready\" ? (\n\t\t\t\t\t<button type=\"button\" className=\"mu-upload\" onClick={() => dl.save()}>\n\t\t\t\t\t\tSave file\n\t\t\t\t\t</button>\n\t\t\t\t) : (\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclassName=\"mu-upload\"\n\t\t\t\t\t\tdisabled={dl.status === \"loading\"}\n\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\tvoid (dl.isImage ? dl.load() : dl.download());\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{dl.status === \"loading\"\n\t\t\t\t\t\t\t? dl.encrypted\n\t\t\t\t\t\t\t\t? \"Decrypting...\"\n\t\t\t\t\t\t\t\t: \"Downloading...\"\n\t\t\t\t\t\t\t: dl.encrypted\n\t\t\t\t\t\t\t\t? \"Decrypt and open\"\n\t\t\t\t\t\t\t\t: \"Download\"}\n\t\t\t\t\t</button>\n\t\t\t\t)}\n\n\t\t\t\t{bytesError !== null ? (\n\t\t\t\t\t<div className=\"mu-error\" role=\"alert\">\n\t\t\t\t\t\t<strong>{bytesError.title}</strong>\n\t\t\t\t\t\t<span>{bytesError.description}</span>\n\t\t\t\t\t</div>\n\t\t\t\t) : null}\n\t\t\t</div>\n\t\t</section>\n\t);\n}\n","/** Clamp a number to the inclusive [min, max] range. */\nexport function clamp(value: number, min: number, max: number): number {\n\tif (value < min) {\n\t\treturn min;\n\t}\n\tif (value > max) {\n\t\treturn max;\n\t}\n\treturn value;\n}\n","import { type ReactElement, useState } from \"react\";\nimport type { MorseFileUploaderProps } from \"../files/types.js\";\nimport { uploadErrorMessage } from \"../files/upload-error-message.js\";\nimport { uploadPhaseLabel } from \"../files/upload-phase-label.js\";\nimport { uploadPhases } from \"../files/upload-phases.js\";\nimport { useFileUpload } from \"../files/use-file-upload.js\";\nimport { useMorseFilesContext } from \"../files/use-morse-files-context.js\";\nimport { useStorageCostEstimate } from \"../files/use-storage-cost-estimate.js\";\nimport { useClipboard } from \"../primitives/use-clipboard.js\";\nimport { useDropzone } from \"../primitives/use-dropzone.js\";\nimport { clamp } from \"../utils/clamp.js\";\nimport { formatBytes } from \"../utils/format-bytes.js\";\nimport { formatUsd } from \"../utils/format-usd.js\";\n\nconst DEFAULT_EPOCHS = 5;\n// Walrus caps a lease at 53 epochs ahead on both networks.\nconst WALRUS_MAX_EPOCHS = 53;\n\n/**\n * Drop-in file uploader for the 0.4.0 RecipientFile flow. Pick a file, choose a\n * storage duration, and upload. Encryption and the recipient set are controlled\n * by props (`encrypt`, `recipients`); the connected wallet is auto-added as a\n * recipient on chain. Emits the completed upload (with seal material and share\n * link) via `onUploaded`. All logic lives in hooks; this component only renders.\n */\nexport function MorseFileUploader(props: MorseFileUploaderProps): ReactElement {\n\tconst {\n\t\tclassName,\n\t\trecipients,\n\t\tencrypt = false,\n\t\tdefaultEpochs,\n\t\tminEpochs = 1,\n\t\tmaxEpochs = WALRUS_MAX_EPOCHS,\n\t\tonUploaded,\n\t} = props;\n\n\tconst [file, setFile] = useState<File | null>(null);\n\tconst [epochs, setEpochs] = useState(defaultEpochs ?? DEFAULT_EPOCHS);\n\tconst upload = useFileUpload();\n\tconst clipboard = useClipboard();\n\tconst { network } = useMorseFilesContext();\n\n\tconst { estimate } = useStorageCostEstimate({\n\t\tsizeBytes: file?.size ?? 0,\n\t\tepochs,\n\t\tencrypt,\n\t});\n\n\tconst { getRootProps, getInputProps } = useDropzone({\n\t\tmultiple: false,\n\t\tariaLabel: \"Choose a file to upload\",\n\t\tonFiles: (files) => setFile(files[0] ?? null),\n\t});\n\n\tconst handleUpload = async () => {\n\t\tif (file === null) {\n\t\t\treturn;\n\t\t}\n\t\tconst result = await upload.upload({\n\t\t\tfile,\n\t\t\tepochs,\n\t\t\tencrypt,\n\t\t\t...(recipients === undefined ? {} : { recipients }),\n\t\t});\n\t\tonUploaded?.(result);\n\t};\n\n\tconst handleReset = () => {\n\t\tupload.reset();\n\t\tsetFile(null);\n\t};\n\n\tconst phases = uploadPhases(encrypt);\n\tconst activeIndex = upload.phase === null ? -1 : phases.indexOf(upload.phase);\n\tconst errorMessage =\n\t\tupload.error === null ? null : uploadErrorMessage(upload.error, network);\n\n\treturn (\n\t\t<div className={`mu-uploader ${className ?? \"\"}`} data-mu=\"uploader\">\n\t\t\t<div className=\"mu-dropzone\" {...getRootProps()}>\n\t\t\t\t<input className=\"mu-file-input\" {...getInputProps()} />\n\t\t\t\t<span className=\"mu-dropzone-icon\" aria-hidden=\"true\" />\n\t\t\t\t{file === null ? (\n\t\t\t\t\t<>\n\t\t\t\t\t\t<span className=\"mu-dropzone-hint\">\n\t\t\t\t\t\t\tDrag a file here, or click to browse\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span className=\"mu-dropzone-sub\">Any file type</span>\n\t\t\t\t\t</>\n\t\t\t\t) : (\n\t\t\t\t\t<>\n\t\t\t\t\t\t<span className=\"mu-dropzone-hint\">{file.name}</span>\n\t\t\t\t\t\t<span className=\"mu-dropzone-sub\">\n\t\t\t\t\t\t\t{formatBytes(file.size)} · click to replace\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</>\n\t\t\t\t)}\n\t\t\t</div>\n\n\t\t\t<label className=\"mu-field\">\n\t\t\t\t<span className=\"mu-field-label\">Storage duration (epochs)</span>\n\t\t\t\t<input\n\t\t\t\t\tclassName=\"mu-epochs-input\"\n\t\t\t\t\ttype=\"number\"\n\t\t\t\t\tmin={minEpochs}\n\t\t\t\t\tmax={maxEpochs}\n\t\t\t\t\tvalue={epochs}\n\t\t\t\t\tonChange={(event) =>\n\t\t\t\t\t\tsetEpochs(\n\t\t\t\t\t\t\tclamp(\n\t\t\t\t\t\t\t\tNumber.parseInt(event.target.value, 10) || minEpochs,\n\t\t\t\t\t\t\t\tminEpochs,\n\t\t\t\t\t\t\t\tmaxEpochs,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t/>\n\t\t\t\t<span\n\t\t\t\t\tclassName=\"mu-field-help\"\n\t\t\t\t\ttitle={`Walrus storage is a lease, not permanent. You can buy at most ${maxEpochs} epochs ahead. An epoch is about 1 day on testnet and about 2 weeks on mainnet.`}\n\t\t\t\t>\n\t\t\t\t\t{`≈ ${Math.round(estimate.days)} day${\n\t\t\t\t\t\tMath.round(estimate.days) === 1 ? \"\" : \"s\"\n\t\t\t\t\t} of storage · max ${maxEpochs} epochs`}\n\t\t\t\t</span>\n\t\t\t</label>\n\n\t\t\t<div className=\"mu-cost\" data-mu=\"cost\">\n\t\t\t\t<span className=\"mu-cost-label\">\n\t\t\t\t\t{encrypt\n\t\t\t\t\t\t? \"Estimated storage cost (encrypted)\"\n\t\t\t\t\t\t: \"Estimated storage cost\"}\n\t\t\t\t</span>\n\t\t\t\t<span className=\"mu-cost-value\">{formatUsd(estimate.usd)}</span>\n\t\t\t</div>\n\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclassName=\"mu-upload\"\n\t\t\t\tdisabled={file === null || upload.status === \"uploading\"}\n\t\t\t\tonClick={() => {\n\t\t\t\t\tvoid handleUpload();\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{upload.status === \"uploading\"\n\t\t\t\t\t? \"Uploading...\"\n\t\t\t\t\t: encrypt\n\t\t\t\t\t\t? \"Encrypt and upload\"\n\t\t\t\t\t\t: \"Upload\"}\n\t\t\t</button>\n\n\t\t\t{upload.status === \"uploading\" ? (\n\t\t\t\t<ol className=\"mu-stepper\" data-mu=\"stepper\">\n\t\t\t\t\t{phases.map((phase, index) => (\n\t\t\t\t\t\t<li\n\t\t\t\t\t\t\tkey={phase}\n\t\t\t\t\t\t\tclassName=\"mu-step\"\n\t\t\t\t\t\t\tdata-state={\n\t\t\t\t\t\t\t\tindex < activeIndex\n\t\t\t\t\t\t\t\t\t? \"done\"\n\t\t\t\t\t\t\t\t\t: index === activeIndex\n\t\t\t\t\t\t\t\t\t\t? \"active\"\n\t\t\t\t\t\t\t\t\t\t: \"pending\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<span className=\"mu-step-dot\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t{index + 1}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span className=\"mu-step-label\">{uploadPhaseLabel(phase)}</span>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t))}\n\t\t\t\t</ol>\n\t\t\t) : null}\n\n\t\t\t{errorMessage !== null ? (\n\t\t\t\t<div className=\"mu-error\" role=\"alert\">\n\t\t\t\t\t<strong>{errorMessage.title}</strong>\n\t\t\t\t\t<span>{errorMessage.description}</span>\n\t\t\t\t</div>\n\t\t\t) : null}\n\n\t\t\t{upload.data !== null ? (\n\t\t\t\t<div className=\"mu-result\" data-mu=\"result\">\n\t\t\t\t\t<span className=\"mu-result-head\">\n\t\t\t\t\t\t<span className=\"mu-result-badge\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t✓\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<strong>\n\t\t\t\t\t\t\t{upload.data.encrypted ? \"Encrypted and uploaded\" : \"Uploaded\"}\n\t\t\t\t\t\t</strong>\n\t\t\t\t\t</span>\n\t\t\t\t\t<div className=\"mu-result-actions\">\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\tclassName=\"mu-upload mu-result-link\"\n\t\t\t\t\t\t\thref={upload.data.shareLink}\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noreferrer\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tOpen share link\n\t\t\t\t\t\t</a>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tclassName=\"mu-ghost mu-result-copy\"\n\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\tvoid clipboard.copy(upload.data?.shareLink ?? \"\");\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{clipboard.copied ? \"Copied!\" : \"Copy link\"}\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<button type=\"button\" className=\"mu-ghost\" onClick={handleReset}>\n\t\t\t\t\t\t\tUpload another\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</div>\n\t\t\t\t\t<code className=\"mu-result-id\">{upload.data.fileId}</code>\n\t\t\t\t\t{upload.data.encrypted ? (\n\t\t\t\t\t\t<span className=\"mu-result-note\">\n\t\t\t\t\t\t\tThe share link carries the decryption key; only recipients can\n\t\t\t\t\t\t\topen it.\n\t\t\t\t\t\t</span>\n\t\t\t\t\t) : null}\n\t\t\t\t</div>\n\t\t\t) : null}\n\t\t</div>\n\t);\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arcadiasystems/morse-uploader",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Headless-first React components and hooks for encrypted file sharing on Sui and Walrus, built on morse-sdk.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Arcadia Systems",
|
|
@@ -12,8 +12,7 @@
|
|
|
12
12
|
"**/*.css"
|
|
13
13
|
],
|
|
14
14
|
"publishConfig": {
|
|
15
|
-
"access": "public"
|
|
16
|
-
"provenance": true
|
|
15
|
+
"access": "public"
|
|
17
16
|
},
|
|
18
17
|
"keywords": [
|
|
19
18
|
"sui",
|
|
@@ -63,6 +62,7 @@
|
|
|
63
62
|
"build": "tsup",
|
|
64
63
|
"dev": "tsup --watch",
|
|
65
64
|
"typecheck": "tsc --noEmit",
|
|
65
|
+
"docs-gate": "bun scripts/docs-gate.ts",
|
|
66
66
|
"lint": "biome check .",
|
|
67
67
|
"lint:fix": "biome check --write .",
|
|
68
68
|
"test": "vitest run",
|
|
@@ -70,14 +70,14 @@
|
|
|
70
70
|
"test:coverage": "vitest run --coverage",
|
|
71
71
|
"check:exports": "publint && attw --pack . --profile esm-only --exclude-entrypoints styles.css",
|
|
72
72
|
"build:example": "bun run build && cd examples/web && bun run build",
|
|
73
|
-
"prepublishOnly": "bun run lint && bun run typecheck && bun run test && bun run build && bun run check:exports"
|
|
73
|
+
"prepublishOnly": "bun run lint && bun run typecheck && bun run docs-gate && bun run test && bun run build && bun run check:exports"
|
|
74
74
|
},
|
|
75
75
|
"engines": {
|
|
76
76
|
"node": ">=18.0.0",
|
|
77
77
|
"bun": ">=1.2.0"
|
|
78
78
|
},
|
|
79
79
|
"peerDependencies": {
|
|
80
|
-
"@arcadiasystems/morse-sdk": ">=0.
|
|
80
|
+
"@arcadiasystems/morse-sdk": ">=0.8.1 <0.9.0",
|
|
81
81
|
"@mysten/seal": ">=1.1.3 <1.2.0",
|
|
82
82
|
"@mysten/sui": ">=2.16.2 <2.17.0",
|
|
83
83
|
"@mysten/walrus": ">=1.1.6 <1.2.0",
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
}
|
|
94
94
|
},
|
|
95
95
|
"devDependencies": {
|
|
96
|
-
"@arcadiasystems/morse-sdk": "0.
|
|
96
|
+
"@arcadiasystems/morse-sdk": "0.8.1",
|
|
97
97
|
"@arethetypeswrong/cli": "^0.18.1",
|
|
98
98
|
"@biomejs/biome": "2.4.7",
|
|
99
99
|
"@mysten/seal": "1.1.3",
|
|
@@ -1 +0,0 @@
|
|
|
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"]}
|