@arcadiasystems/morse-uploader 0.3.0 → 0.4.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 +38 -0
- package/README.md +13 -5
- package/dist/{chunk-K2J3ZILI.js → chunk-ZV3GFXNW.js} +265 -28
- package/dist/chunk-ZV3GFXNW.js.map +1 -0
- package/dist/index.d.ts +57 -14
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/{types-C6w30zCM.d.ts → types-C5hwiHjr.d.ts} +63 -6
- package/dist/ui.d.ts +5 -4
- package/dist/ui.js +2 -3
- package/dist/ui.js.map +1 -1
- package/package.json +3 -3
- package/dist/chunk-K2J3ZILI.js.map +0 -1
|
@@ -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/share-link-seal-error.ts","../src/files/download-error-message.ts","../src/files/epoch-duration-ms.ts","../src/files/estimate-storage-usd.ts","../src/utils/format-bytes.ts","../src/files/file-too-large-error.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/assert-signer-matches-address.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/utils/bytes-equal.ts","../src/files/assert-share-link-opens-file.ts","../src/files/use-file-download.ts","../src/files/fetch-relay-tip-strategy.ts","../src/files/max-blob-bytes-for-tip.ts","../src/files/resolve-upload-size-limit.ts","../src/files/with-ciphertext-size-limit.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-usd.ts"],"names":["formatUserMessage","useState","useCallback","useRef","useEffect","SEAL_UNAVAILABLE"],"mappings":";;;;;;AAKO,IAAM,iCAAA,GAAoC;;;ACJ1C,SAAS,WAAW,KAAA,EAA2B;AACrD,EAAA,IAAI,GAAA,GAAM,EAAA;AACV,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACzB,IAAA,GAAA,IAAO,KAAK,QAAA,CAAS,EAAE,CAAA,CAAE,QAAA,CAAS,GAAG,GAAG,CAAA;AAAA,EACzC;AACA,EAAA,OAAO,GAAA;AACR;;;ACDO,SAAS,aAAA,GAA+B;AAE9C,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAe,OAAO,MAAA,CAAO,aAAa,WAAA,EAAa;AAC5E,IAAA,OAAO,IAAA;AAAA,EACR;AACA,EAAA,MAAM,EAAE,MAAA,EAAO,GAAI,MAAA,CAAO,QAAA;AAC1B,EAAA,OAAO,MAAA,CAAO,MAAA,GAAS,CAAA,GAAI,MAAA,GAAS,IAAA;AACrC;;;ACZO,SAAS,oBAAoB,KAAA,EAAuB;AAC1D,EAAA,OAAO,KAAA,CAAM,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAA;AAChC;;;ACEA,IAAM,eAAA,GAAkB,cAAA;AASjB,SAAS,eAAe,IAAA,EAAkC;AAChE,EAAA,MAAM,OAAO,mBAAA,CAAoB,IAAA,CAAK,OAAA,IAAW,aAAA,MAAmB,EAAE,CAAA;AACtE,EAAA,MAAM,IAAA,GAAA,CAAQ,IAAA,CAAK,OAAA,IAAW,eAAA,EAAiB,OAAA;AAAA,IAC9C,SAAA;AAAA,IACA,kBAAA,CAAmB,KAAK,MAAM;AAAA,GAC/B;AACA,EAAA,MAAM,SAAmB,EAAC;AAC1B,EAAA,IAAI,IAAA,CAAK,YAAY,MAAA,EAAW;AAC/B,IAAA,MAAA,CAAO,IAAA,CAAK,CAAA,IAAA,EAAO,IAAA,CAAK,OAAO,CAAA,CAAE,CAAA;AAAA,EAClC;AACA,EAAA,IAAI,IAAA,CAAK,YAAA,KAAiB,MAAA,IAAa,IAAA,CAAK,cAAc,MAAA,EAAW;AACpE,IAAA,MAAA,CAAO,KAAK,CAAA,EAAA,EAAK,UAAA,CAAW,IAAA,CAAK,YAAY,CAAC,CAAA,CAAE,CAAA;AAChD,IAAA,MAAA,CAAO,KAAK,CAAA,EAAA,EAAK,UAAA,CAAW,IAAA,CAAK,SAAS,CAAC,CAAA,CAAE,CAAA;AAAA,EAC9C;AACA,EAAA,MAAM,KAAA,GAAQ,OAAO,MAAA,KAAW,CAAA,GAAI,KAAK,CAAA,CAAA,EAAI,MAAA,CAAO,IAAA,CAAK,GAAG,CAAC,CAAA,CAAA;AAC7D,EAAA,OAAO,CAAA,EAAG,IAAI,CAAA,EAAG,IAAI,GAAG,KAAK,CAAA,CAAA;AAC9B;;;AC5BA,IAAM,QAAA,GAA+D;AAAA,EACpE,OAAA,EACC,qHAAA;AAAA,EACD,QAAA,EACC;AACF,CAAA;AAQO,IAAM,kBAAA,GAAN,cAAiC,KAAA,CAAM;AAAA,EACpC,MAAA;AAAA,EAET,YAAY,MAAA,EAAkC;AAC7C,IAAA,KAAA,CAAM,QAAA,CAAS,MAAM,CAAC,CAAA;AACtB,IAAA,IAAA,CAAK,IAAA,GAAO,oBAAA;AACZ,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAAA,EACf;AACD;AChBA,IAAM,MAAA,GAA6D;AAAA,EAClE,OAAA,EAAS,oBAAA;AAAA,EACT,QAAA,EAAU;AACX,CAAA;AAOO,SAAS,qBAAqB,KAAA,EAAgC;AACpE,EAAA,IAAI,EAAE,iBAAiB,kBAAA,CAAA,EAAqB;AAC3C,IAAA,OAAO,kBAAkB,KAAK,CAAA;AAAA,EAC/B;AACA,EAAA,OAAO;AAAA,IACN,KAAA,EAAO,MAAA,CAAO,KAAA,CAAM,MAAM,CAAA;AAAA,IAC1B,aAAa,KAAA,CAAM,OAAA;AAAA,IACnB,KAAA,EAAO;AAAA,GACR;AACD;;;ACzBA,IAAM,UAAA,GAAa,EAAA,GAAK,EAAA,GAAK,EAAA,GAAK,GAAA;AAGlC,IAAM,iBAAA,GAA2D;AAAA,EAChE,OAAA,EAAS,UAAA;AAAA,EACT,SAAS,EAAA,GAAK;AACf,CAAA;AAOO,SAAS,gBAAgB,OAAA,EAAwC;AACvE,EAAA,OAAO,kBAAkB,OAAO,CAAA;AACjC;;;ACZA,IAAM,4BAAA,GAA+B,KAAA;AACrC,IAAM,YAAA,GAAe,GAAA;AACrB,IAAM,UAAA,GAAa,EAAA,GAAK,EAAA,GAAK,EAAA,GAAK,GAAA;AAClC,IAAM,eAAe,EAAA,GAAK,UAAA;AAQnB,SAAS,mBAAmB,IAAA,EAAoC;AACtE,EAAA,MAAM,SAAA,GAAY,IAAA,CAAK,SAAA,IAAa,IAAA,CAAK,uBAAA,IAA2B,CAAA,CAAA;AACpE,EAAA,MAAM,KAAK,SAAA,GAAY,YAAA;AACvB,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,MAAA,GAAS,IAAA,CAAK,eAAA;AACnC,EAAA,MAAM,SAAS,OAAA,GAAU,YAAA;AACzB,EAAA,MAAM,OAAO,OAAA,GAAU,UAAA;AACvB,EAAA,MAAM,IAAA,GAAO,KAAK,gBAAA,IAAoB,4BAAA;AACtC,EAAA,OAAO,EAAE,KAAK,EAAA,GAAK,MAAA,GAAS,MAAM,EAAA,EAAI,MAAA,EAAQ,MAAM,SAAA,EAAU;AAC/D;;;ACtBA,IAAM,QAAQ,CAAC,GAAA,EAAK,MAAM,IAAA,EAAM,IAAA,EAAM,MAAM,IAAI,CAAA;AAChD,IAAM,IAAA,GAAO,IAAA;AAQN,SAAS,WAAA,CAAY,KAAA,EAAe,cAAA,GAAiB,CAAA,EAAW;AACtE,EAAA,IAAI,CAAC,MAAA,CAAO,QAAA,CAAS,KAAK,CAAA,IAAK,SAAS,CAAA,EAAG;AAC1C,IAAA,OAAO,KAAA;AAAA,EACR;AACA,EAAA,MAAM,WAAW,IAAA,CAAK,GAAA;AAAA,IACrB,IAAA,CAAK,MAAM,IAAA,CAAK,GAAA,CAAI,KAAK,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,IAAI,CAAC,CAAA;AAAA,IAC3C,MAAM,MAAA,GAAS;AAAA,GAChB;AACA,EAAA,MAAM,KAAA,GAAQ,QAAQ,IAAA,IAAQ,QAAA;AAC9B,EAAA,MAAM,IAAA,GAAO,KAAA,CAAM,QAAQ,CAAA,IAAK,GAAA;AAEhC,EAAA,IAAI,aAAa,CAAA,EAAG;AACnB,IAAA,OAAO,CAAA,EAAG,KAAK,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA;AAAA,EACxB;AACA,EAAA,OAAO,GAAG,KAAA,CAAM,OAAA,CAAQ,cAAc,CAAC,IAAI,IAAI,CAAA,CAAA;AAChD;;;ACfO,IAAM,iBAAA,GAAN,cAAgC,KAAA,CAAM;AAAA,EACnC,SAAA;AAAA,EACA,eAAA;AAAA,EACA,KAAA;AAAA,EAET,YAAY,IAAA,EAA6B;AACxC,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,eAAA,GAClB,yBAAA,GACA,cAAA;AACH,IAAA,KAAA;AAAA,MACC,GAAG,OAAO,CAAA,CAAA,EAAI,cAAc,IAAA,CAAK,SAAS,CAAC,CAAA,WAAA,EAAc,aAAA,CAAc,KAAK,KAAA,CAAM,QAAQ,CAAC,CAAA,mGAAA,EAAsG,IAAA,CAAK,MAAM,UAAA,CAAW,cAAA,CAAe,OAAO,CAAC,CAAA,MAAA;AAAA,KAC/O;AACA,IAAA,IAAA,CAAK,IAAA,GAAO,mBAAA;AACZ,IAAA,IAAA,CAAK,YAAY,IAAA,CAAK,SAAA;AACtB,IAAA,IAAA,CAAK,kBAAkB,IAAA,CAAK,eAAA;AAC5B,IAAA,IAAA,CAAK,QAAQ,IAAA,CAAK,KAAA;AAAA,EACnB;AACD;AAEA,SAAS,cAAc,KAAA,EAAuB;AAC7C,EAAA,OAAO,CAAA,EAAG,YAAY,KAAK,CAAC,KAAK,KAAA,CAAM,cAAA,CAAe,OAAO,CAAC,CAAA,OAAA,CAAA;AAC/D;ACfO,SAAS,kBAAA,CACf,OACA,OAAA,EACiB;AACjB,EAAA,IAAI,iBAAiB,iBAAA,EAAmB;AACvC,IAAA,OAAO;AAAA,MACN,KAAA,EAAO,gBAAA;AAAA,MACP,aAAa,KAAA,CAAM,OAAA;AAAA,MACnB,KAAA,EAAO;AAAA,KACR;AAAA,EACD;AACA,EAAA,MAAM,SAAA,GAAY,wBAAA,CAAyB,SAAA,CAAU,KAAK,CAAC,CAAA;AAC3D,EAAA,IAAI,cAAc,IAAA,EAAM;AACvB,IAAA,OAAOA,kBAAkB,KAAK,CAAA;AAAA,EAC/B;AACA,EAAA,OAAO;AAAA,IACN,KAAA,EAAO,CAAA,WAAA,EAAc,SAAA,CAAU,MAAM,CAAA,CAAA;AAAA,IACrC,WAAA,EAAa,QAAA,CAAS,SAAA,EAAW,OAAO,CAAA;AAAA,IACxC,KAAA,EAAO;AAAA,GACR;AACD;AAYA,IAAM,eAAA,GACL,0FAAA;AAED,SAAS,yBAAyB,IAAA,EAAgC;AACjE,EAAA,MAAM,KAAA,GAAQ,eAAA,CAAgB,IAAA,CAAK,IAAI,CAAA;AACvC,EAAA,IAAI,UAAU,IAAA,EAAM;AACnB,IAAA,OAAO,IAAA;AAAA,EACR;AACA,EAAA,MAAM,GAAG,QAAA,EAAU,QAAA,EAAU,SAAS,CAAA,GAAI,KAAA;AAC1C,EAAA,OAAO;AAAA,IACN,GAAG,KAAK,QAAkB,CAAA;AAAA,IAC1B,QAAA,EAAU,OAAO,QAAkB,CAAA;AAAA,IACnC,SAAA,EAAW,OAAO,SAAmB;AAAA,GACtC;AACD;AAMA,SAAS,KAAK,QAAA,EAA+D;AAC5E,EAAA,IAAI,QAAA,CAAS,QAAA,CAAS,YAAY,CAAA,EAAG;AACpC,IAAA,OAAO,EAAE,MAAA,EAAQ,KAAA,EAAO,QAAA,EAAU,CAAA,EAAE;AAAA,EACrC;AACA,EAAA,IAAI,QAAA,CAAS,QAAA,CAAS,YAAY,CAAA,EAAG;AACpC,IAAA,OAAO,EAAE,MAAA,EAAQ,KAAA,EAAO,QAAA,EAAU,CAAA,EAAE;AAAA,EACrC;AACA,EAAA,MAAM,MAAA,GAAS,QAAA,CAAS,KAAA,CAAM,IAAI,EAAE,GAAA,EAAI;AACxC,EAAA,OAAO,EAAE,MAAA,EAAQ,MAAA,IAAU,OAAA,EAAS,UAAU,IAAA,EAAK;AACpD;AAEA,SAAS,QAAA,CAAS,WAAsB,OAAA,EAA0B;AACjE,EAAA,MAAM,OAAO,MAAA,CAAO,SAAA,CAAU,UAAU,SAAA,CAAU,QAAA,EAAU,UAAU,MAAM,CAAA;AAC5E,EAAA,MAAM,IAAA,GAAO,MAAA;AAAA,IACZ,SAAA,CAAU,SAAA;AAAA,IACV,SAAA,CAAU,QAAA;AAAA,IACV,SAAA,CAAU;AAAA,GACX;AACA,EAAA,OAAO,CAAA,kBAAA,EAAqB,IAAI,CAAA,8BAAA,EAAiC,IAAI,KAAK,KAAA,CAAM,SAAA,CAAU,MAAA,EAAQ,OAAO,CAAC,CAAA,CAAA;AAC3G;AAEA,SAAS,KAAA,CAAM,QAAgB,OAAA,EAA0B;AACxD,EAAA,IAAI,YAAY,SAAA,EAAW;AAC1B,IAAA,OAAO,MAAA,KAAW,QACf,0FAAA,GACA,0DAAA;AAAA,EACJ;AACA,EAAA,IAAI,YAAY,SAAA,EAAW;AAC1B,IAAA,OAAO,qCAAqC,MAAM,CAAA,oGAAA,CAAA;AAAA,EACnD;AACA,EAAA,OAAO,0BAA0B,MAAM,CAAA,aAAA,CAAA;AACxC;AAEA,SAAS,MAAA,CACR,KAAA,EACA,QAAA,EACA,MAAA,EACS;AACT,EAAA,IAAI,aAAa,IAAA,EAAM;AACtB,IAAA,OAAO,CAAA,EAAG,KAAA,CAAM,QAAA,EAAU,IAAI,MAAM,CAAA,CAAA;AAAA,EACrC;AACA,EAAA,MAAM,KAAA,GAAQ,GAAA,IAAO,MAAA,CAAO,QAAQ,CAAA;AACpC,EAAA,MAAM,QAAQ,KAAA,GAAQ,KAAA;AACtB,EAAA,MAAM,YAAY,KAAA,GAAQ,KAAA,EAAO,UAAS,CAAE,QAAA,CAAS,UAAU,GAAG,CAAA;AAClE,EAAA,MAAM,OAAA,GAAU,QAAA,CAAS,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AAC1C,EAAA,MAAM,IAAA,GAAO,YAAY,EAAA,GAAK,KAAA,CAAM,UAAS,GAAI,CAAA,EAAG,KAAK,CAAA,CAAA,EAAI,OAAO,CAAA,CAAA;AACpE,EAAA,OAAO,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,MAAM,CAAA,CAAA;AACzB;AAGA,SAAS,UAAU,KAAA,EAAwB;AAC1C,EAAA,MAAM,QAAkB,EAAC;AACzB,EAAA,IAAI,OAAA,GAAU,KAAA;AACd,EAAA,KAAA,IACK,KAAA,GAAQ,GACZ,OAAA,KAAY,IAAA,IAAQ,YAAY,MAAA,IAAa,KAAA,GAAQ,CAAA,EACrD,KAAA,IAAS,CAAA,EACR;AACD,IAAA,IAAI,mBAAmB,KAAA,EAAO;AAC7B,MAAA,KAAA,CAAM,IAAA,CAAK,QAAQ,OAAO,CAAA;AAC1B,MAAA,OAAA,GAAU,OAAA,CAAQ,KAAA;AAClB,MAAA;AAAA,IACD;AACA,IAAA,KAAA,CAAM,IAAA,CAAK,MAAA,CAAO,OAAO,CAAC,CAAA;AAC1B,IAAA;AAAA,EACD;AACA,EAAA,OAAO,KAAA,CAAM,KAAK,KAAK,CAAA;AACxB;;;ACrIA,IAAM,MAAA,GAAoC;AAAA,EACzC,UAAA,EAAY,YAAA;AAAA,EACZ,SAAA,EAAW,qBAAA;AAAA,EACX,UAAA,EAAY,wBAAA;AAAA,EACZ,QAAA,EAAU;AACX,CAAA;AAGO,SAAS,iBAAiB,KAAA,EAA0B;AAC1D,EAAA,OAAO,OAAO,KAAK,CAAA;AACpB;;;ACVA,IAAM,gBAAA,GAAyC;AAAA,EAC9C,YAAA;AAAA,EACA,WAAA;AAAA,EACA,YAAA;AAAA,EACA;AACD,CAAA;AACA,IAAM,aAAA,GAAsC;AAAA,EAC3C,WAAA;AAAA,EACA,YAAA;AAAA,EACA;AACD,CAAA;AAMO,SAAS,aAAa,SAAA,EAA0C;AACtE,EAAA,OAAO,YAAY,gBAAA,GAAmB,aAAA;AACvC;ACbO,IAAM,iBAAA,GAAoB,cAAwC,IAAI;;;ACEtE,SAAS,oBAAA,GAA0C;AACzD,EAAA,MAAM,OAAA,GAAU,WAAW,iBAAiB,CAAA;AAC5C,EAAA,IAAI,YAAY,IAAA,EAAM;AACrB,IAAA,MAAM,IAAI,KAAA;AAAA,MACT;AAAA,KACD;AAAA,EACD;AACA,EAAA,OAAO,OAAA;AACR;ACEO,SAAS,0BAAA,CACf,QACA,OAAA,EACO;AACP,EAAA,MAAM,aAAA,GAAgB,mBAAA,CAAoB,MAAA,CAAO,YAAA,EAAc,CAAA;AAC/D,EAAA,MAAM,cAAA,GAAiB,oBAAoB,OAAO,CAAA;AAClD,EAAA,IAAI,kBAAkB,cAAA,EAAgB;AACrC,IAAA,MAAM,IAAI,SAAA;AAAA,MACT,CAAA,iEAAA,EAAoE,aAAa,CAAA,qBAAA,EAAwB,cAAc,CAAA,8EAAA;AAAA,KACxH;AAAA,EACD;AACD;;;AClBA,IAAM,gBAAA,GACL,6SAAA;AAED,IAAM,eAAA,GAAkB,EAAA;AAejB,SAAS,cAAA,GAAuC;AACtD,EAAA,MAAM,EAAE,MAAM,UAAA,EAAY,MAAA,EAAQ,QAAQ,MAAA,EAAQ,OAAA,KACjD,oBAAA,EAAqB;AACtB,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,SAA4B,MAAM,CAAA;AAC9D,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAI,SAA4B,IAAI,CAAA;AACxD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAkB,IAAI,CAAA;AAKhD,EAAA,MAAM,UAAA,GAAa,OAA6B,IAAI,CAAA;AAEpD,EAAA,MAAM,KAAA,GAAQ,YAAY,MAAM;AAC/B,IAAA,SAAA,CAAU,MAAM,CAAA;AAChB,IAAA,OAAA,CAAQ,IAAI,CAAA;AACZ,IAAA,QAAA,CAAS,IAAI,CAAA;AAAA,EACd,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,OAAA,GAAU,WAAA;AAAA,IACf,OAAO,KAAA,KAAiD;AACvD,MAAA,SAAA,CAAU,SAAS,CAAA;AACnB,MAAA,OAAA,CAAQ,IAAI,CAAA;AACZ,MAAA,QAAA,CAAS,IAAI,CAAA;AAEb,MAAA,IAAI,SAAS,IAAA,EAAM;AAClB,QAAA,MAAM,WAAA,GAAc,IAAI,KAAA,CAAM,gBAAgB,CAAA;AAC9C,QAAA,QAAA,CAAS,WAAW,CAAA;AACpB,QAAA,SAAA,CAAU,OAAO,CAAA;AACjB,QAAA,MAAM,WAAA;AAAA,MACP;AAEA,MAAA,MAAM,SAAA,GAAY,MAAA,CAAO,iBAAA,IAAqB,MAAA,CAAO,SAAA;AACrD,MAAA,MAAM,aAAa,YAAY;AAC9B,QAAA,MAAM,SAAS,UAAA,CAAW,OAAA;AAC1B,QAAA,IACC,WAAW,IAAA,IACX,MAAA,CAAO,YAAY,OAAA,IACnB,MAAA,CAAO,cAAc,SAAA,EACpB;AACD,UAAA,OAAO,MAAA,CAAO,GAAA;AAAA,QACf;AACA,QAAA,0BAAA,CAA2B,QAAQ,OAAO,CAAA;AAC1C,QAAA,MAAM,GAAA,GAAM,MAAM,UAAA,CAAW,MAAA,CAAO;AAAA,UACnC,OAAA;AAAA,UACA,SAAA;AAAA,UACA,MAAA,EAAQ,MAAM,MAAA,IAAU,eAAA;AAAA,UACxB,MAAA;AAAA,UACA,SAAA,EAAW;AAAA,SACX,CAAA;AACD,QAAA,UAAA,CAAW,OAAA,GAAU,EAAE,OAAA,EAAS,SAAA,EAAW,GAAA,EAAI;AAC/C,QAAA,OAAO,GAAA;AAAA,MACR,CAAA;AAEA,MAAA,IAAI;AACH,QAAA,MAAM,aAAa,MAAM,UAAA,CAAW,QAAA,CAAS,KAAA,CAAM,KAAK,MAAM,CAAA;AAC9D,QAAA,MAAM,OAAA,GAAU;AAAA,UACf,MAAA,EAAQ,wBAAA,CAAyB,KAAA,CAAM,YAAA,EAAc,MAAM,SAAS,CAAA;AAAA,UACpE,MAAA,EAAQ,MAAM,IAAA,CAAK;AAAA,SACpB;AACA,QAAA,IAAI;AACH,UAAA,MAAM,SAAA,GAAY,MAAM,IAAA,CAAK,yBAAA,CAA0B,UAAA,EAAY;AAAA,YAClE,GAAG,OAAA;AAAA,YACH,UAAA,EAAY,MAAM,UAAA;AAAW,WAC7B,CAAA;AACD,UAAA,OAAA,CAAQ,SAAS,CAAA;AACjB,UAAA,SAAA,CAAU,SAAS,CAAA;AACnB,UAAA,OAAO,SAAA;AAAA,QACR,SAAS,GAAA,EAAK;AAEb,UAAA,IAAI,GAAA,YAAe,SAAA,IAAa,GAAA,CAAI,IAAA,KAAS,iBAAA,EAAmB;AAC/D,YAAA,UAAA,CAAW,OAAA,GAAU,IAAA;AACrB,YAAA,MAAM,SAAA,GAAY,MAAM,IAAA,CAAK,yBAAA,CAA0B,UAAA,EAAY;AAAA,cAClE,GAAG,OAAA;AAAA,cACH,UAAA,EAAY,MAAM,UAAA;AAAW,aAC7B,CAAA;AACD,YAAA,OAAA,CAAQ,SAAS,CAAA;AACjB,YAAA,SAAA,CAAU,SAAS,CAAA;AACnB,YAAA,OAAO,SAAA;AAAA,UACR;AACA,UAAA,MAAM,GAAA;AAAA,QACP;AAAA,MACD,SAAS,GAAA,EAAK;AACb,QAAA,QAAA,CAAS,GAAG,CAAA;AACZ,QAAA,SAAA,CAAU,OAAO,CAAA;AACjB,QAAA,MAAM,GAAA;AAAA,MACP;AAAA,IACD,CAAA;AAAA,IACA,CAAC,IAAA,EAAM,UAAA,EAAY,MAAA,EAAQ,MAAA,EAAQ,QAAQ,OAAO;AAAA,GACnD;AAEA,EAAA,OAAO,EAAE,MAAA,EAAQ,IAAA,EAAM,KAAA,EAAO,SAAS,KAAA,EAAM;AAC9C;;;ACxHA,IAAM,uBAAA,uBAA8B,GAAA,CAAI;AAAA,EACvC,WAAA;AAAA,EACA,YAAA;AAAA,EACA,WAAA;AAAA,EACA,YAAA;AAAA,EACA,YAAA;AAAA,EACA;AACD,CAAC,CAAA;AAGM,SAAS,mBAAmB,WAAA,EAA8B;AAChE,EAAA,OAAO,uBAAA,CAAwB,GAAA,CAAI,WAAA,CAAY,WAAA,EAAa,CAAA;AAC7D;;;ACTO,SAAS,eAAA,CAAgB,WAAmB,QAAA,EAAwB;AAC1E,EAAA,IAAI,OAAO,aAAa,WAAA,EAAa;AACpC,IAAA;AAAA,EACD;AACA,EAAA,MAAM,MAAA,GAAS,QAAA,CAAS,aAAA,CAAc,GAAG,CAAA;AACzC,EAAA,MAAA,CAAO,IAAA,GAAO,SAAA;AACd,EAAA,MAAA,CAAO,QAAA,GAAW,QAAA;AAClB,EAAA,MAAA,CAAO,GAAA,GAAM,UAAA;AACb,EAAA,QAAA,CAAS,IAAA,CAAK,YAAY,MAAM,CAAA;AAChC,EAAA,MAAA,CAAO,KAAA,EAAM;AACb,EAAA,MAAA,CAAO,MAAA,EAAO;AACf;ACTO,SAAS,iBACf,MAAA,EACyB;AACzB,EAAA,MAAM,EAAE,MAAA,EAAO,GAAI,oBAAA,EAAqB;AACxC,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAIC,SAAqB,MAAM,CAAA;AACvD,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAIA,SAA+B,IAAI,CAAA;AAC3D,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,SAAkB,IAAI,CAAA;AAEhD,EAAA,MAAM,OAAA,GAAUC,YAAY,YAA2C;AACtE,IAAA,IAAI,WAAW,IAAA,EAAM;AACpB,MAAA,OAAO,IAAA;AAAA,IACR;AACA,IAAA,SAAA,CAAU,SAAS,CAAA;AACnB,IAAA,QAAA,CAAS,IAAI,CAAA;AACb,IAAA,IAAI;AACH,MAAA,MAAM,MAAA,GAAS,MAAM,MAAA,CAAO,gBAAA,CAAiB,MAAM,CAAA;AACnD,MAAA,OAAA,CAAQ,MAAM,CAAA;AACd,MAAA,SAAA,CAAU,SAAS,CAAA;AACnB,MAAA,OAAO,MAAA;AAAA,IACR,SAAS,GAAA,EAAK;AACb,MAAA,QAAA,CAAS,GAAG,CAAA;AACZ,MAAA,SAAA,CAAU,OAAO,CAAA;AACjB,MAAA,MAAM,GAAA;AAAA,IACP;AAAA,EACD,CAAA,EAAG,CAAC,MAAA,EAAQ,MAAM,CAAC,CAAA;AAEnB,EAAA,SAAA,CAAU,MAAM;AACf,IAAA,IAAI,WAAW,IAAA,EAAM;AACpB,MAAA,OAAA,EAAQ,CAAE,KAAA,CAAM,MAAM,MAAS,CAAA;AAAA,IAChC;AAAA,EACD,CAAA,EAAG,CAAC,OAAA,EAAS,MAAM,CAAC,CAAA;AAEpB,EAAA,OAAO,EAAE,MAAA,EAAQ,IAAA,EAAM,KAAA,EAAO,OAAA,EAAQ;AACvC;;;ACtCO,SAAS,UAAA,CAAW,GAAe,CAAA,EAAwB;AACjE,EAAA,IAAI,CAAA,CAAE,MAAA,KAAW,CAAA,CAAE,MAAA,EAAQ;AAC1B,IAAA,OAAO,KAAA;AAAA,EACR;AACA,EAAA,OAAO,CAAA,CAAE,MAAM,CAAC,IAAA,EAAM,UAAU,IAAA,KAAS,CAAA,CAAE,KAAK,CAAC,CAAA;AAClD;;;ACiBO,SAAS,wBAAA,CACf,eACA,IAAA,EACO;AACP,EAAA,IAAI,kBAAkB,IAAA,EAAM;AAC3B,IAAA;AAAA,EACD;AACA,EAAA,IAAI,SAAS,IAAA,EAAM;AAClB,IAAA,MAAM,IAAI,mBAAmB,SAAS,CAAA;AAAA,EACvC;AACA,EAAA,IAAI,CAAC,UAAA,CAAW,IAAA,CAAK,YAAA,EAAc,aAAa,CAAA,EAAG;AAClD,IAAA,MAAM,IAAI,mBAAmB,UAAU,CAAA;AAAA,EACxC;AACD;;;ACPO,SAAS,eAAA,CACf,QACA,IAAA,EACwB;AACxB,EAAA,MAAM,EAAE,MAAA,EAAQ,UAAA,EAAW,GAAI,oBAAA,EAAqB;AACpD,EAAA,MAAM,EAAE,MAAA,EAAQ,UAAA,EAAY,IAAA,EAAK,GAAI,iBAAiB,MAAM,CAAA;AAC5D,EAAA,MAAM,EAAE,OAAA,EAAQ,GAAI,cAAA,EAAe;AACnC,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAID,SAA6B,MAAM,CAAA;AAC/D,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAIA,SAAwB,IAAI,CAAA;AAC9D,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,SAAkB,IAAI,CAAA;AAChD,EAAA,MAAM,MAAA,GAASE,OAAsB,IAAI,CAAA;AACzC,EAAA,MAAM,WAAA,GAAcA,OAA+B,IAAI,CAAA;AAGvD,EAAAC,UAAU,MAAM;AACf,IAAA,IAAI,cAAc,IAAA,EAAM;AACvB,MAAA;AAAA,IACD;AACA,IAAA,OAAO,MAAM,GAAA,CAAI,eAAA,CAAgB,SAAS,CAAA;AAAA,EAC3C,CAAA,EAAG,CAAC,SAAS,CAAC,CAAA;AAKd,EAAAA,UAAU,MAAM;AACf,IAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,IAAA,WAAA,CAAY,OAAA,GAAU,UAAA;AACtB,IAAA,OAAO,MAAM,WAAW,KAAA,EAAM;AAAA,EAC/B,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,YAAY,IAAA,KAAS,IAAA;AAC3B,EAAA,MAAM,UAAU,IAAA,KAAS,IAAA,GAAO,KAAA,GAAQ,kBAAA,CAAmB,KAAK,WAAW,CAAA;AAG3E,EAAA,MAAM,UAAA,GAAaF,YAAY,YAAoC;AAClE,IAAA,IAAI,SAAS,IAAA,EAAM;AAClB,MAAA,OAAO,IAAA;AAAA,IACR;AACA,IAAA,MAAM,MAAA,GAAS,YAAY,OAAA,EAAS,MAAA;AACpC,IAAA,SAAA,CAAU,SAAS,CAAA;AACnB,IAAA,QAAA,CAAS,IAAI,CAAA;AACb,IAAA,IAAI;AACH,MAAA,MAAM,aAAA,GAAgB,MAAM,MAAA,CAAO,0BAAA;AAAA,QAClC,IAAA,CAAK,EAAA;AAAA,QACL;AAAA,OACD;AACA,MAAA,MAAA,EAAQ,cAAA,EAAe;AACvB,MAAA,wBAAA,CAAyB,eAAe,IAAI,CAAA;AAC5C,MAAA,IAAI,KAAA;AACJ,MAAA,IAAI,SAAS,IAAA,EAAM;AAClB,QAAA,KAAA,GAAQ,MAAM,OAAA,CAAQ;AAAA,UACrB,IAAA;AAAA,UACA,cAAc,IAAA,CAAK,YAAA;AAAA,UACnB,WAAW,IAAA,CAAK;AAAA,SAChB,CAAA;AAAA,MACF,CAAA,MAAO;AACN,QAAA,KAAA,GAAQ,MAAM,UAAA,CAAW,QAAA,CAAS,IAAA,CAAK,MAAM,CAAA;AAAA,MAC9C;AACA,MAAA,MAAM,MAAM,GAAA,CAAI,eAAA;AAAA,QACf,IAAI,KAAK,CAAC,KAAiB,GAAG,EAAE,IAAA,EAAM,IAAA,CAAK,WAAA,EAAa;AAAA,OACzD;AACA,MAAA,MAAA,CAAO,OAAA,GAAU,GAAA;AACjB,MAAA,YAAA,CAAa,GAAG,CAAA;AAChB,MAAA,SAAA,CAAU,OAAO,CAAA;AACjB,MAAA,OAAO,GAAA;AAAA,IACR,SAAS,GAAA,EAAK;AAEb,MAAA,IAAI,MAAA,EAAQ,YAAY,IAAA,EAAM;AAC7B,QAAA,OAAO,IAAA;AAAA,MACR;AACA,MAAA,QAAA,CAAS,GAAG,CAAA;AACZ,MAAA,SAAA,CAAU,OAAO,CAAA;AACjB,MAAA,OAAO,IAAA;AAAA,IACR;AAAA,EACD,GAAG,CAAC,IAAA,EAAM,MAAM,MAAA,EAAQ,OAAA,EAAS,UAAU,CAAC,CAAA;AAE5C,EAAA,MAAM,IAAA,GAAOA,YAAY,YAA2B;AACnD,IAAA,MAAM,UAAA,EAAW;AAAA,EAClB,CAAA,EAAG,CAAC,UAAU,CAAC,CAAA;AAEf,EAAA,MAAM,IAAA,GAAOA,YAAY,MAAM;AAC9B,IAAA,IAAI,MAAA,CAAO,OAAA,KAAY,IAAA,IAAQ,IAAA,KAAS,IAAA,EAAM;AAC7C,MAAA,eAAA,CAAgB,MAAA,CAAO,OAAA,EAAS,IAAA,CAAK,IAAI,CAAA;AAAA,IAC1C;AAAA,EACD,CAAA,EAAG,CAAC,IAAI,CAAC,CAAA;AAET,EAAA,MAAM,QAAA,GAAWA,YAAY,YAA2B;AACvD,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,OAAA,IAAY,MAAM,UAAA,EAAW;AAChD,IAAA,IAAI,GAAA,KAAQ,IAAA,IAAQ,IAAA,KAAS,IAAA,EAAM;AAClC,MAAA,eAAA,CAAgB,GAAA,EAAK,KAAK,IAAI,CAAA;AAAA,IAC/B;AAAA,EACD,CAAA,EAAG,CAAC,UAAA,EAAY,IAAI,CAAC,CAAA;AAErB,EAAA,OAAO;AAAA,IACN,UAAA;AAAA,IACA,IAAA;AAAA,IACA,MAAA;AAAA,IACA,SAAA;AAAA,IACA,SAAA;AAAA,IACA,OAAA;AAAA,IACA,KAAA;AAAA,IACA,IAAA;AAAA,IACA,IAAA;AAAA,IACA;AAAA,GACD;AACD;ACnIA,IAAM,SAAA,GAAY,wBAAA;AAElB,IAAM,UAAA,GAAa,GAAA;AAWnB,eAAsB,sBACrB,IAAA,EAC8B;AAC9B,EAAA,IAAI,QAAA;AACJ,EAAA,IAAI;AACH,IAAA,QAAA,GAAW,MAAM,KAAA,CAAM,CAAA,EAAG,IAAI,CAAA,cAAA,CAAA,EAAkB;AAAA,MAC/C,MAAA,EAAQ,WAAA,CAAY,OAAA,CAAQ,UAAU;AAAA,KACtC,CAAA;AAAA,EACF,SAAS,KAAA,EAAO;AACf,IAAA,MAAM,IAAI,cAAA;AAAA,MACT,uCAAuC,IAAI,CAAA,wBAAA,CAAA;AAAA,MAC3C,EAAE,KAAA,EAAO,SAAA,EAAW,SAAA;AAAU,KAC/B;AAAA,EACD;AACA,EAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AACjB,IAAA,MAAM,IAAI,cAAA;AAAA,MACT,CAAA,oBAAA,EAAuB,IAAI,CAAA,eAAA,EAAkB,QAAA,CAAS,MAAM,CAAA,+BAAA,CAAA;AAAA,MAC5D,EAAE,WAAW,SAAA;AAAU,KACxB;AAAA,EACD;AACA,EAAA,IAAI,IAAA;AACJ,EAAA,IAAI;AACH,IAAA,IAAA,GAAO,MAAM,SAAS,IAAA,EAAK;AAAA,EAC5B,SAAS,KAAA,EAAO;AACf,IAAA,MAAM,UAAA,CAAW,MAAM,KAAK,CAAA;AAAA,EAC7B;AACA,EAAA,IAAI,OAAO,SAAS,QAAA,EAAU;AAC7B,IAAA,OAAO,IAAA;AAAA,EACR;AACA,EAAA,MAAM,OAAO,KAAA,CAAM,KAAA,CAAM,IAAA,EAAM,UAAU,GAAG,MAAM,CAAA;AAClD,EAAA,MAAM,QAAA,GAAW,KAAA,CAAM,IAAA,EAAM,OAAO,CAAA;AACpC,EAAA,IAAI,MAAA,CAAO,QAAQ,CAAA,EAAG;AACrB,IAAA,OAAO,EAAE,OAAO,QAAA,EAAS;AAAA,EAC1B;AACA,EAAA,MAAM,MAAA,GAAS,KAAA,CAAM,IAAA,EAAM,QAAQ,CAAA;AACnC,EAAA,MAAM,IAAA,GAAO,KAAA,CAAM,MAAA,EAAQ,MAAM,CAAA;AACjC,EAAA,MAAM,aAAA,GAAgB,KAAA,CAAM,MAAA,EAAQ,0BAA0B,CAAA;AAC9D,EAAA,IAAI,MAAA,CAAO,IAAI,CAAA,IAAK,MAAA,CAAO,aAAa,CAAA,EAAG;AAC1C,IAAA,OAAO,EAAE,MAAA,EAAQ,EAAE,IAAA,EAAM,eAAc,EAAE;AAAA,EAC1C;AACA,EAAA,MAAM,WAAW,IAAI,CAAA;AACtB;AAEA,SAAS,KAAA,CAAM,OAAgB,GAAA,EAAsB;AACpD,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,IAAA,EAAM;AAChD,IAAA,OAAO,MAAA;AAAA,EACR;AACA,EAAA,OAAQ,MAA4C,GAAG,CAAA;AACxD;AAEA,SAAS,OAAO,KAAA,EAAiC;AAChD,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,OAAO,aAAA,CAAc,KAAK,KAAK,KAAA,IAAS,CAAA;AAC7E;AAEA,SAAS,UAAA,CAAW,MAAc,KAAA,EAAiC;AAClE,EAAA,OAAO,IAAI,cAAA;AAAA,IACV,uBAAuB,IAAI,CAAA,4CAAA,CAAA;AAAA,IAC3B,KAAA,KAAU,SACP,EAAE,SAAA,EAAW,WAAU,GACvB,EAAE,KAAA,EAAO,SAAA,EAAW,SAAA;AAAU,GAClC;AACD;;;ACtEA,IAAM,QAAA,GAAW,GAAA;AACjB,IAAM,iBAAA,GAAoB,IAAA,CAAK,KAAA,CAAA,CAAO,QAAA,GAAW,KAAK,CAAC,CAAA;AACvD,IAAM,eAAA,GAAkB,WAAW,CAAA,GAAI,iBAAA;AACvC,IAAM,oBAAoB,QAAA,GAAW,iBAAA;AAErC,IAAM,gBAAA,GAAmB,MAAA,CAAO,eAAA,GAAkB,iBAAiB,CAAA;AAEnE,IAAM,wBAAA,GAA2B,MAAA;AAAA,EAAA,CAC/B,kBAAkB,iBAAA,IAAqB;AACzC,CAAA;AAEA,IAAM,iBAAiB,MAAA,CAAO,QAAA,IAAY,QAAA,GAAW,EAAA,GAAK,IAAI,EAAA,CAAG,CAAA;AACjE,IAAM,aAAA,GAAgB,KAAA;AAmBf,SAAS,kBAAA,CACf,UACA,UAAA,EACgB;AAChB,EAAA,IAAI,WAAW,QAAA,EAAU;AACxB,IAAA,OAAO,MAAA,CAAO,QAAA,CAAS,KAAK,CAAA,IAAK,aAAa,IAAA,GAAO,CAAA;AAAA,EACtD;AACA,EAAA,MAAM,IAAA,GAAO,MAAA,CAAO,QAAA,CAAS,MAAA,CAAO,IAAI,CAAA;AACxC,EAAA,MAAM,aAAA,GAAgB,MAAA,CAAO,QAAA,CAAS,MAAA,CAAO,aAAa,CAAA;AAC1D,EAAA,IAAI,kBAAkB,EAAA,EAAI;AACzB,IAAA,OAAO,IAAA,IAAQ,aAAa,IAAA,GAAO,CAAA;AAAA,EACpC;AAGA,EAAA,MAAM,eAAA,GAAA,CAAoB,UAAA,GAAa,IAAA,IAAQ,aAAA,GAAiB,aAAA;AAChE,EAAA,IAAI,kBAAkB,cAAA,EAAgB;AACrC,IAAA,OAAO,CAAA;AAAA,EACR;AACA,EAAA,MAAM,OAAA,GAAA,CAAW,kBAAkB,cAAA,IAAkB,wBAAA;AACrD,EAAA,MAAM,WAAA,GAAc,UAAW,OAAA,GAAU,EAAA;AACzC,EAAA,OAAO,MAAA,CAAO,cAAc,gBAAgB,CAAA;AAC7C;;;ACzCA,eAAsB,uBACrB,KAAA,EACkC;AAClC,EAAA,IAAI,UAAU,MAAA,EAAW;AACxB,IAAA,OAAO,IAAA;AAAA,EACR;AACA,EAAA,MAAM,EAAE,SAAQ,GAAI,KAAA;AACpB,EAAA,IAAI,OAAA,KAAY,MAAA,IAAa,OAAA,KAAY,IAAA,EAAM;AAC9C,IAAA,OAAO,IAAA;AAAA,EACR;AAGA,EAAA,IAAI,OAAA,CAAQ,QAAQ,MAAA,IAAa,CAAC,OAAO,QAAA,CAAS,OAAA,CAAQ,GAAG,CAAA,EAAG;AAC/D,IAAA,OAAO,IAAA;AAAA,EACR;AAEA,EAAA,MAAM,aAAa,MAAA,CAAO,IAAA,CAAK,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAC,CAAA;AACjD,EAAA,MAAM,QAAA,GACL,UAAU,OAAA,GAAU,OAAA,CAAQ,OAAO,MAAM,qBAAA,CAAsB,MAAM,IAAI,CAAA;AAC1E,EAAA,IAAI,aAAa,IAAA,EAAM;AACtB,IAAA,OAAO,IAAA;AAAA,EACR;AACA,EAAA,MAAM,QAAA,GAAW,kBAAA,CAAmB,QAAA,EAAU,UAAU,CAAA;AACxD,EAAA,IAAI,aAAa,IAAA,EAAM;AACtB,IAAA,OAAO,IAAA;AAAA,EACR;AACA,EAAA,IAAI,aAAa,CAAA,EAAG;AACnB,IAAA,MAAM,IAAI,kBAAA;AAAA,MACT,kBAAkB,UAAA,CAAW,cAAA,CAAe,OAAO,CAAC,CAAA,yCAAA,EAA4C,MAAM,IAAI,CAAA,yHAAA;AAAA,KAC3G;AAAA,EACD;AACA,EAAA,OAAO,EAAE,UAAU,UAAA,EAAW;AAC/B;;;AChCO,SAAS,uBAAA,CACf,QACA,KAAA,EACyC;AACzC,EAAA,OAAO;AAAA,IACN,YAAY,CAAC,IAAA,EAAM,YAAY,MAAA,CAAO,UAAA,CAAW,MAAM,OAAO,CAAA;AAAA,IAC9D,aAAa,CAAC,OAAA,EAAS,YAAY,MAAA,CAAO,WAAA,CAAY,SAAS,OAAO,CAAA;AAAA,IACtE,eAAA,EAAiB,OAAO,IAAA,EAAM,OAAA,KAAY;AACzC,MAAA,IAAI,IAAA,CAAK,MAAA,GAAS,KAAA,CAAM,QAAA,EAAU;AACjC,QAAA,MAAM,IAAI,iBAAA,CAAkB;AAAA,UAC3B,WAAW,IAAA,CAAK,MAAA;AAAA,UAChB,eAAA,EAAiB,IAAA;AAAA,UACjB;AAAA,SACA,CAAA;AAAA,MACF;AACA,MAAA,OAAO,MAAA,CAAO,eAAA,CAAgB,IAAA,EAAM,OAAO,CAAA;AAAA,IAC5C;AAAA,GACD;AACD;;;ACjBA,IAAMG,iBAAAA,GACL,mTAAA;AAED,IAAM,qBAAA,GAAwB,0BAAA;AAYvB,SAAS,aAAA,GAAqC;AACpD,EAAA,MAAM,EAAE,SAAS,MAAA,EAAQ,WAAA,EAAa,aAAa,IAAA,EAAM,OAAA,KACxD,oBAAA,EAAqB;AACtB,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAIJ,SAA2B,MAAM,CAAA;AAC7D,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,SAA2B,IAAI,CAAA;AACzD,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAIA,SAAqC,IAAI,CAAA;AACjE,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,SAAkB,IAAI,CAAA;AAEhD,EAAA,MAAM,KAAA,GAAQC,YAAY,MAAM;AAC/B,IAAA,SAAA,CAAU,MAAM,CAAA;AAChB,IAAA,QAAA,CAAS,IAAI,CAAA;AACb,IAAA,OAAA,CAAQ,IAAI,CAAA;AACZ,IAAA,QAAA,CAAS,IAAI,CAAA;AAAA,EACd,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,MAAA,GAASA,WAAAA;AAAA,IACd,OAAO,KAAA,KAAyD;AAC/D,MAAA,SAAA,CAAU,WAAW,CAAA;AACrB,MAAA,QAAA,CAAS,IAAI,CAAA;AACb,MAAA,OAAA,CAAQ,IAAI,CAAA;AACZ,MAAA,QAAA,CAAS,IAAI,CAAA;AACb,MAAA,IAAI;AAIH,QAAA,MAAM,SAAA,GAAY,MAAM,sBAAA,CAAuB,WAAW,CAAA;AAC1D,QAAA,IAAI,cAAc,IAAA,IAAQ,KAAA,CAAM,IAAA,CAAK,IAAA,GAAO,UAAU,QAAA,EAAU;AAC/D,UAAA,MAAM,IAAI,iBAAA,CAAkB;AAAA,YAC3B,SAAA,EAAW,MAAM,IAAA,CAAK,IAAA;AAAA,YACtB,eAAA,EAAiB,KAAA;AAAA,YACjB,KAAA,EAAO;AAAA,WACP,CAAA;AAAA,QACF;AACA,QAAA,MAAM,QAAQ,IAAI,UAAA,CAAW,MAAM,KAAA,CAAM,IAAA,CAAK,aAAa,CAAA;AAC3D,QAAA,MAAM,WAAA,GAAc,KAAA,CAAM,IAAA,CAAK,IAAA,IAAQ,qBAAA;AACvC,QAAA,MAAM,UAAA,GAAa,KAAA,CAAM,UAAA,IAAc,EAAC;AACxC,QAAA,MAAM,aAAA,GAAgB;AAAA,UACrB,QAAQ,KAAA,CAAM,MAAA;AAAA,UACd,SAAA,EAAW,MAAM,SAAA,IAAa;AAAA,SAC/B;AACA,QAAA,MAAM,UAAA,GAAa,CAAC,KAAA,KACnB,QAAA,CAAS,MAAM,KAAK,CAAA;AAErB,QAAA,IAAI,SAAA;AACJ,QAAA,IAAI,KAAA,CAAM,YAAY,IAAA,EAAM;AAC3B,UAAA,IAAI,SAAS,IAAA,EAAM;AAClB,YAAA,MAAM,IAAI,MAAMG,iBAAgB,CAAA;AAAA,UACjC;AACA,UAAA,MAAM,SAAS,MAAM,qCAAA;AAAA,YACpB,OAAA;AAAA,YACA,MAAA;AAAA,YACA;AAAA,cACC,QACC,SAAA,KAAc,IAAA,GACX,WAAA,GACA,uBAAA,CAAwB,aAAa,SAAS,CAAA;AAAA,cAClD,IAAA;AAAA,cACA,SAAA,EAAW,KAAA;AAAA,cACX,UAAA;AAAA,cACA,IAAA,EAAM,MAAM,IAAA,CAAK,IAAA;AAAA,cACjB,WAAA;AAAA,cACA,MAAA,EAAQ,aAAA;AAAA,cACR;AAAA;AACD,WACD;AACA,UAAA,SAAA,GAAY;AAAA,YACX,QAAQ,MAAA,CAAO,MAAA;AAAA,YACf,QAAQ,MAAA,CAAO,MAAA;AAAA,YACf,SAAA,EAAW,IAAA;AAAA,YACX,cAAc,MAAA,CAAO,YAAA;AAAA,YACrB,WAAW,MAAA,CAAO,SAAA;AAAA,YAClB,WAAW,cAAA,CAAe;AAAA,cACzB,QAAQ,MAAA,CAAO,MAAA;AAAA,cACf,OAAA;AAAA,cACA,cAAc,MAAA,CAAO,YAAA;AAAA,cACrB,WAAW,MAAA,CAAO;AAAA,aAClB;AAAA,WACF;AAAA,QACD,CAAA,MAAO;AACN,UAAA,MAAM,MAAA,GAAS,MAAM,4BAAA,CAA6B,OAAA,EAAS,MAAA,EAAQ;AAAA,YAClE,MAAA,EAAQ,WAAA;AAAA,YACR,KAAA;AAAA,YACA,UAAA;AAAA,YACA,IAAA,EAAM,MAAM,IAAA,CAAK,IAAA;AAAA,YACjB,WAAA;AAAA,YACA,MAAA,EAAQ,aAAA;AAAA,YACR;AAAA,WACA,CAAA;AACD,UAAA,SAAA,GAAY;AAAA,YACX,QAAQ,MAAA,CAAO,MAAA;AAAA,YACf,QAAQ,MAAA,CAAO,MAAA;AAAA,YACf,SAAA,EAAW,KAAA;AAAA,YACX,YAAA,EAAc,IAAA;AAAA,YACd,SAAA,EAAW,IAAA;AAAA,YACX,WAAW,cAAA,CAAe,EAAE,QAAQ,MAAA,CAAO,MAAA,EAAQ,SAAS;AAAA,WAC7D;AAAA,QACD;AACA,QAAA,OAAA,CAAQ,SAAS,CAAA;AACjB,QAAA,SAAA,CAAU,SAAS,CAAA;AACnB,QAAA,OAAO,SAAA;AAAA,MACR,SAAS,GAAA,EAAK;AACb,QAAA,QAAA,CAAS,GAAG,CAAA;AACZ,QAAA,SAAA,CAAU,OAAO,CAAA;AACjB,QAAA,MAAM,GAAA;AAAA,MACP;AAAA,IACD,CAAA;AAAA,IACA,CAAC,OAAA,EAAS,MAAA,EAAQ,WAAA,EAAa,WAAA,EAAa,MAAM,OAAO;AAAA,GAC1D;AAEA,EAAA,OAAO,EAAE,MAAA,EAAQ,KAAA,EAAO,IAAA,EAAM,KAAA,EAAO,QAAQ,KAAA,EAAM;AACpD;AChIO,SAAS,uBACf,OAAA,EAC+B;AAC/B,EAAA,MAAM,EAAE,OAAA,EAAQ,GAAI,oBAAA,EAAqB;AACzC,EAAA,MAAM,EAAE,SAAA,EAAW,MAAA,EAAQ,OAAA,EAAS,kBAAiB,GAAI,OAAA;AAEzD,EAAA,MAAM,QAAA,GAAW,OAAA;AAAA,IAChB,MACC,kBAAA,CAAmB;AAAA,MAClB,SAAA;AAAA,MACA,MAAA;AAAA,MACA,eAAA,EAAiB,gBAAgB,OAAO,CAAA;AAAA,MACxC,uBAAA,EAAyB,UACtB,iCAAA,GACA,CAAA;AAAA,MACH,GAAI,gBAAA,KAAqB,MAAA,GAAY,EAAC,GAAI,EAAE,gBAAA;AAAiB,KAC7D,CAAA;AAAA,IACF,CAAC,OAAA,EAAS,SAAA,EAAW,MAAA,EAAQ,SAAS,gBAAgB;AAAA,GACvD;AAEA,EAAA,OAAO,EAAE,QAAA,EAAS;AACnB;AC7BO,SAAS,YAAA,CAAa,UAAU,GAAA,EAA0B;AAChE,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAIJ,SAAS,KAAK,CAAA;AAC1C,EAAA,MAAM,KAAA,GAAQE,OAAkD,MAAS,CAAA;AAEzE,EAAAC,SAAAA,CAAU,MAAM,MAAM,YAAA,CAAa,MAAM,OAAO,CAAA,EAAG,EAAE,CAAA;AAErD,EAAA,MAAM,IAAA,GAAOF,WAAAA;AAAA,IACZ,OAAO,IAAA,KAAmC;AACzC,MAAA,IACC,OAAO,SAAA,KAAc,WAAA,IACrB,SAAA,CAAU,cAAc,MAAA,EACvB;AACD,QAAA,OAAO,KAAA;AAAA,MACR;AACA,MAAA,IAAI;AACH,QAAA,MAAM,SAAA,CAAU,SAAA,CAAU,SAAA,CAAU,IAAI,CAAA;AAAA,MACzC,CAAA,CAAA,MAAQ;AACP,QAAA,OAAO,KAAA;AAAA,MACR;AACA,MAAA,SAAA,CAAU,IAAI,CAAA;AACd,MAAA,YAAA,CAAa,MAAM,OAAO,CAAA;AAC1B,MAAA,KAAA,CAAM,UAAU,UAAA,CAAW,MAAM,SAAA,CAAU,KAAK,GAAG,OAAO,CAAA;AAC1D,MAAA,OAAO,IAAA;AAAA,IACR,CAAA;AAAA,IACA,CAAC,OAAO;AAAA,GACT;AAEA,EAAA,OAAO,EAAE,QAAQ,IAAA,EAAK;AACvB;ACtBO,SAAS,YAAY,OAAA,EAAgD;AAC3E,EAAA,MAAM;AAAA,IACL,OAAA;AAAA,IACA,MAAA;AAAA,IACA,QAAA,GAAW,IAAA;AAAA,IACX,QAAA,GAAW,KAAA;AAAA,IACX;AAAA,GACD,GAAI,OAAA;AACJ,EAAA,MAAM,QAAA,GAAWC,OAAyB,IAAI,CAAA;AAC9C,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAIF,SAAS,KAAK,CAAA;AAElD,EAAA,MAAM,IAAA,GAAOC,YAAY,MAAM;AAC9B,IAAA,IAAI,QAAA,EAAU;AACb,MAAA;AAAA,IACD;AACA,IAAA,QAAA,CAAS,SAAS,KAAA,EAAM;AAAA,EACzB,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AAEb,EAAA,MAAM,IAAA,GAAOA,WAAAA;AAAA,IACZ,CAAC,IAAA,KAA0B;AAC1B,MAAA,IAAI,IAAA,KAAS,IAAA,IAAQ,IAAA,CAAK,MAAA,KAAW,CAAA,EAAG;AACvC,QAAA;AAAA,MACD;AACA,MAAA,OAAA,CAAQ,KAAA,CAAM,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,IACzB,CAAA;AAAA,IACA,CAAC,OAAO;AAAA,GACT;AAEA,EAAA,MAAM,YAAA,GAAeA,WAAAA;AAAA,IACpB,OAA0B;AAAA,MACzB,IAAA,EAAM,QAAA;AAAA,MACN,QAAA,EAAU,WAAW,EAAA,GAAK,CAAA;AAAA,MAC1B,eAAA,EAAiB,QAAA;AAAA,MACjB,eAAA,EAAiB,aAAa,MAAA,GAAS,MAAA;AAAA,MACvC,GAAI,SAAA,KAAc,MAAA,GAAY,EAAC,GAAI,EAAE,cAAc,SAAA,EAAU;AAAA,MAC7D,OAAA,EAAS,MAAM,IAAA,EAAK;AAAA,MACpB,SAAA,EAAW,CAAC,KAAA,KAAU;AACrB,QAAA,IAAI,QAAA,EAAU;AACb,UAAA;AAAA,QACD;AAEA,QAAA,IAAI,KAAA,CAAM,GAAA,KAAQ,OAAA,IAAW,KAAA,CAAM,QAAQ,GAAA,EAAK;AAC/C,UAAA,KAAA,CAAM,cAAA,EAAe;AACrB,UAAA,IAAA,EAAK;AAAA,QACN;AAAA,MACD,CAAA;AAAA,MACA,WAAA,EAAa,CAAC,KAAA,KAAU;AACvB,QAAA,IAAI,QAAA,EAAU;AACb,UAAA;AAAA,QACD;AACA,QAAA,KAAA,CAAM,cAAA,EAAe;AACrB,QAAA,aAAA,CAAc,IAAI,CAAA;AAAA,MACnB,CAAA;AAAA,MACA,UAAA,EAAY,CAAC,KAAA,KAAU;AAEtB,QAAA,IAAI,CAAC,QAAA,EAAU;AACd,UAAA,KAAA,CAAM,cAAA,EAAe;AAAA,QACtB;AAAA,MACD,CAAA;AAAA,MACA,WAAA,EAAa,CAAC,KAAA,KAAU;AACvB,QAAA,IAAI,QAAA,EAAU;AACb,UAAA;AAAA,QACD;AACA,QAAA,KAAA,CAAM,cAAA,EAAe;AACrB,QAAA,aAAA,CAAc,KAAK,CAAA;AAAA,MACpB,CAAA;AAAA,MACA,MAAA,EAAQ,CAAC,KAAA,KAAU;AAClB,QAAA,IAAI,QAAA,EAAU;AACb,UAAA;AAAA,QACD;AACA,QAAA,KAAA,CAAM,cAAA,EAAe;AACrB,QAAA,aAAA,CAAc,KAAK,CAAA;AACnB,QAAA,IAAA,CAAK,KAAA,CAAM,YAAA,EAAc,KAAA,IAAS,IAAI,CAAA;AAAA,MACvC;AAAA,KACD,CAAA;AAAA,IACA,CAAC,QAAA,EAAU,UAAA,EAAY,IAAA,EAAM,MAAM,SAAS;AAAA,GAC7C;AAEA,EAAA,MAAM,aAAA,GAAgBA,WAAAA;AAAA,IACrB,OAA2B;AAAA,MAC1B,GAAA,EAAK,QAAA;AAAA,MACL,IAAA,EAAM,MAAA;AAAA,MACN,QAAA;AAAA,MACA,QAAA,EAAU,EAAA;AAAA,MACV,QAAA;AAAA,MACA,GAAI,MAAA,KAAW,MAAA,GAAY,EAAC,GAAI,EAAE,MAAA,EAAO;AAAA,MACzC,UAAU,CAAC,KAAA,KAAU,IAAA,CAAK,KAAA,CAAM,OAAO,KAAK,CAAA;AAAA;AAAA;AAAA,MAG5C,OAAA,EAAS,CAAC,KAAA,KAAU,KAAA,CAAM,eAAA;AAAgB,KAC3C,CAAA;AAAA,IACA,CAAC,MAAA,EAAQ,QAAA,EAAU,QAAA,EAAU,IAAI;AAAA,GAClC;AAEA,EAAA,OAAO,EAAE,UAAA,EAAY,IAAA,EAAM,YAAA,EAAc,eAAe,QAAA,EAAS;AAClE;;;AC5GA,IAAM,UAAA,GAAa,IAAA;AAQZ,SAAS,UAAU,GAAA,EAAqB;AAC9C,EAAA,IAAI,OAAO,CAAA,EAAG;AACb,IAAA,OAAO,OAAA;AAAA,EACR;AACA,EAAA,IAAI,OAAO,IAAA,EAAM;AAChB,IAAA,OAAO,CAAA,CAAA,EAAI,GAAA,CAAI,OAAA,CAAQ,CAAC,CAAC,CAAA,CAAA;AAAA,EAC1B;AACA,EAAA,IAAI,MAAM,UAAA,EAAY;AACrB,IAAA,OAAO,MAAM,UAAU,CAAA,CAAA;AAAA,EACxB;AACA,EAAA,OAAO,CAAA,CAAA,EAAI,GAAA,CAAI,cAAA,CAAe,OAAA,EAAS;AAAA,IACtC,wBAAA,EAA0B,CAAA;AAAA,IAC1B,WAAA,EAAa;AAAA,GACb,CAAC,CAAA,CAAA;AACH","file":"chunk-ZV3GFXNW.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","import type { ShareLinkSealErrorReason } from \"./types.js\";\n\nconst MESSAGES: Readonly<Record<ShareLinkSealErrorReason, string>> = {\n\tmissing:\n\t\t\"This file is encrypted, and the link is missing the details needed to decrypt it. Ask the sender for the full link.\",\n\tmismatch:\n\t\t\"This file is encrypted, and the link's decryption details are for a different file. The link may have been damaged or mixed up with another one. Ask the sender for the full link.\",\n};\n\n/**\n * The file is encrypted on chain, and the share link cannot open it: the link\n * has no Seal material, or its Seal prefix is not the file's. Raised by\n * {@link useFileDownload} before any bytes are read or the wallet is asked to\n * sign, so a damaged link never serves ciphertext as if it were the file.\n */\nexport class ShareLinkSealError extends Error {\n\treadonly reason: ShareLinkSealErrorReason;\n\n\tconstructor(reason: ShareLinkSealErrorReason) {\n\t\tsuper(MESSAGES[reason]);\n\t\tthis.name = \"ShareLinkSealError\";\n\t\tthis.reason = reason;\n\t}\n}\n","import {\n\ttype FormattedError,\n\tformatUserMessage,\n} from \"@arcadiasystems/morse-sdk\";\nimport { ShareLinkSealError } from \"./share-link-seal-error.js\";\nimport type { ShareLinkSealErrorReason } from \"./types.js\";\n\nconst TITLES: Readonly<Record<ShareLinkSealErrorReason, string>> = {\n\tmissing: \"Link is incomplete\",\n\tmismatch: \"Link does not match this file\",\n};\n\n/**\n * Render a download failure for the UI. Defers to the SDK's `formatUserMessage`\n * except for a {@link ShareLinkSealError}, which the SDK does not know and\n * would title \"Unexpected error\" when it is really \"ask for the full link\".\n */\nexport function downloadErrorMessage(error: unknown): FormattedError {\n\tif (!(error instanceof ShareLinkSealError)) {\n\t\treturn formatUserMessage(error);\n\t}\n\treturn {\n\t\ttitle: TITLES[error.reason],\n\t\tdescription: error.message,\n\t\tcause: error,\n\t};\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","// 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","import { formatBytes } from \"../utils/format-bytes.js\";\nimport type { FileTooLargeErrorArgs, UploadSizeLimit } from \"./types.js\";\n\n/**\n * The file is too large to upload through the Walrus upload relay without a\n * tip above `sendTip.max`. Raised by {@link useFileUpload} before the file is\n * read, encrypted, encoded or signed for, or, when only Seal's envelope takes\n * it over, after encryption and before encoding. Render it with\n * {@link uploadErrorMessage}, or narrow on it and read `limit`.\n */\nexport class FileTooLargeError extends Error {\n\treadonly sizeBytes: number;\n\treadonly afterEncryption: boolean;\n\treadonly limit: UploadSizeLimit;\n\n\tconstructor(args: FileTooLargeErrorArgs) {\n\t\tconst subject = args.afterEncryption\n\t\t\t? \"Encrypted, this file is\"\n\t\t\t: \"This file is\";\n\t\tsuper(\n\t\t\t`${subject} ${describeBytes(args.sizeBytes)}, over the ${describeBytes(args.limit.maxBytes)} limit for one upload. The Walrus upload relay's tip grows with file size, and this app caps it at ${args.limit.maxTipMist.toLocaleString(\"en-US\")} MIST.`,\n\t\t);\n\t\tthis.name = \"FileTooLargeError\";\n\t\tthis.sizeBytes = args.sizeBytes;\n\t\tthis.afterEncryption = args.afterEncryption;\n\t\tthis.limit = args.limit;\n\t}\n}\n\nfunction describeBytes(bytes: number): string {\n\treturn `${formatBytes(bytes)} (${bytes.toLocaleString(\"en-US\")} bytes)`;\n}\n","import {\n\ttype FormattedError,\n\tformatUserMessage,\n} from \"@arcadiasystems/morse-sdk\";\nimport { FileTooLargeError } from \"./file-too-large-error.js\";\n\n/**\n * Render an upload failure for the UI. Defers to the SDK's `formatUserMessage`\n * except for two cases it cannot title. A {@link FileTooLargeError} is this\n * package's own, and the SDK would call it \"Unexpected error\". An\n * insufficient-balance failure the SDK sees only as a transport error carrying\n * `@mysten/sui`'s raw text: a coin type, an address, and two unscaled\n * integers. That reads as a network problem when it is really \"top up the\n * wallet\", so it is rewritten here with the amounts in whole coins and the\n * network's way of getting more.\n */\nexport function uploadErrorMessage(\n\terror: unknown,\n\tnetwork?: \"testnet\" | \"mainnet\",\n): FormattedError {\n\tif (error instanceof FileTooLargeError) {\n\t\treturn {\n\t\t\ttitle: \"File too large\",\n\t\t\tdescription: error.message,\n\t\t\tcause: error,\n\t\t};\n\t}\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 { UserError } from \"@mysten/seal\";\nimport type { Signer } from \"@mysten/sui/cryptography\";\nimport { normalizeSuiAddress } from \"@mysten/sui/utils\";\n\n/**\n * Refuse to build a Seal session for `address` with a signer for another\n * account.\n *\n * `@mysten/seal` up to 1.4 made this check inside `SessionKey.create`; 1.5\n * dropped it to allow address aliases. Without it a mismatch costs a wallet\n * popup and then fails at the key servers, and `useFileDecrypt` would by then\n * have cached the session for `address`, so every later decrypt reuses it and\n * fails the same way. Handles from `useMorseFiles` always agree, because the\n * SDK's `WalletStandardSigner` requires the account's key to derive to its\n * address; this keeps hand-built handles failing early, as they did before.\n *\n * @throws {UserError} When the signer's address is not `address`. Same class\n * and leading message as the check Seal used to make.\n */\nexport function assertSignerMatchesAddress(\n\tsigner: Signer,\n\taddress: string,\n): void {\n\tconst signerAddress = normalizeSuiAddress(signer.toSuiAddress());\n\tconst sessionAddress = normalizeSuiAddress(address);\n\tif (signerAddress !== sessionAddress) {\n\t\tthrow new UserError(\n\t\t\t`Signer address does not match session key address: the signer is ${signerAddress}, the session is for ${sessionAddress}. Build the handles with useMorseFiles, or pass a signer for the same account.`,\n\t\t);\n\t}\n}\n","import { buildRecipientFileSealId, SealError } from \"@arcadiasystems/morse-sdk\";\nimport { SessionKey } from \"@mysten/seal\";\nimport { useCallback, useRef, useState } from \"react\";\nimport { assertSignerMatchesAddress } from \"./assert-signer-matches-address.js\";\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\tassertSignerMatchesAddress(signer, address);\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","/** True when both byte arrays have the same length and the same bytes. */\nexport function bytesEqual(a: Uint8Array, b: Uint8Array): boolean {\n\tif (a.length !== b.length) {\n\t\treturn false;\n\t}\n\treturn a.every((byte, index) => byte === b[index]);\n}\n","import { bytesEqual } from \"../utils/bytes-equal.js\";\nimport { ShareLinkSealError } from \"./share-link-seal-error.js\";\nimport type { SealMaterial } from \"./types.js\";\n\n/**\n * Refuse a share link that cannot open the file it names, given the file's\n * Seal prefix as read from chain.\n *\n * A file with no on-chain prefix passes whatever the link carries. Files\n * encrypted before the prefix was stored on chain have none, and their links\n * still carry the Seal material, so a missing prefix does not prove a file is\n * public: the link's material decides, as it always has.\n *\n * A link whose prefix differs from the file's cannot decrypt it, so it is\n * refused here rather than costing the user a wallet signature for a Seal\n * session first. It is not repaired with the on-chain prefix: the nonce is not\n * on chain, so a link with one damaged field cannot be told apart from a link\n * to a different file.\n *\n * @throws {ShareLinkSealError} With reason `\"missing\"` when the file has a\n * prefix and the link has no Seal material, or `\"mismatch\"` when the link's\n * prefix is not the file's.\n */\nexport function assertShareLinkOpensFile(\n\tonChainPrefix: Uint8Array | null,\n\tseal: SealMaterial | null,\n): void {\n\tif (onChainPrefix === null) {\n\t\treturn;\n\t}\n\tif (seal === null) {\n\t\tthrow new ShareLinkSealError(\"missing\");\n\t}\n\tif (!bytesEqual(seal.sealIdPrefix, onChainPrefix)) {\n\t\tthrow new ShareLinkSealError(\"mismatch\");\n\t}\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 { assertShareLinkOpensFile } from \"./assert-share-link-opens-file.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. Fetching starts by reading the file's Seal\n * prefix from chain, the one reliable sign that a file is encrypted; it is\n * read only then, so a page that just shows the metadata pays no extra round\n * trip. With Seal material from the share link the bytes are decrypted (one\n * SessionKey signature); without it they are read straight from Walrus as a\n * public file. A file with an on-chain prefix whose link has no Seal material,\n * or material for another prefix, fails with a {@link ShareLinkSealError}\n * instead. Files encrypted before the prefix was stored on chain have none, so\n * for them the link still decides.\n *\n * @param fileId The on-chain RecipientFile id (from the share link).\n * @param seal The Seal material (prefix + nonce) from the share link; null when it has none.\n */\nexport function useFileDownload(\n\tfileId: RecipientFileId | null,\n\tseal: SealMaterial | null,\n): UseFileDownloadResult {\n\tconst { reader, 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\tconst lifetimeRef = useRef<AbortController | 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\t// Aborted on unmount (null until mounted), so a prefix read for a page that\n\t// is gone stops there instead of going on to read bytes or ask the wallet\n\t// to sign.\n\tuseEffect(() => {\n\t\tconst controller = new AbortController();\n\t\tlifetimeRef.current = controller;\n\t\treturn () => controller.abort();\n\t}, []);\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\tconst signal = lifetimeRef.current?.signal;\n\t\tsetStatus(\"loading\");\n\t\tsetError(null);\n\t\ttry {\n\t\t\tconst onChainPrefix = await reader.getRecipientFileSealPrefix(\n\t\t\t\tfile.id,\n\t\t\t\tsignal,\n\t\t\t);\n\t\t\tsignal?.throwIfAborted();\n\t\t\tassertShareLinkOpensFile(onChainPrefix, seal);\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\t// Unmounted mid-fetch: there is no page left to report to.\n\t\t\tif (signal?.aborted === true) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tsetError(err);\n\t\t\tsetStatus(\"error\");\n\t\t\treturn null;\n\t\t}\n\t}, [file, seal, reader, 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 { TransportError } from \"@arcadiasystems/morse-sdk\";\nimport type { TipStrategy } from \"@mysten/walrus\";\n\nconst OPERATION = \"walrus.relay.tipConfig\";\n/** The timeout `@mysten/walrus` uses for relay requests. */\nconst TIMEOUT_MS = 30_000;\n\n/**\n * Read how the upload relay at `host` wants to be tipped, from the\n * `/v1/tip-config` endpoint `@mysten/walrus` reads before it sends a tip.\n * `null` when the relay charges no tip, which it says by answering with a\n * JSON string rather than an object.\n *\n * @throws {TransportError} When the relay cannot be reached, answers with an\n * error status, or sends a tip config this version cannot read.\n */\nexport async function fetchRelayTipStrategy(\n\thost: string,\n): Promise<TipStrategy | null> {\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(`${host}/v1/tip-config`, {\n\t\t\tsignal: AbortSignal.timeout(TIMEOUT_MS),\n\t\t});\n\t} catch (cause) {\n\t\tthrow new TransportError(\n\t\t\t`Could not reach the upload relay at ${host} to read its tip config.`,\n\t\t\t{ cause, operation: OPERATION },\n\t\t);\n\t}\n\tif (!response.ok) {\n\t\tthrow new TransportError(\n\t\t\t`The upload relay at ${host} answered HTTP ${response.status} when asked for its tip config.`,\n\t\t\t{ operation: OPERATION },\n\t\t);\n\t}\n\tlet body: unknown;\n\ttry {\n\t\tbody = await response.json();\n\t} catch (cause) {\n\t\tthrow unreadable(host, cause);\n\t}\n\tif (typeof body === \"string\") {\n\t\treturn null;\n\t}\n\tconst kind = field(field(body, \"send_tip\"), \"kind\");\n\tconst constant = field(kind, \"const\");\n\tif (isMist(constant)) {\n\t\treturn { const: constant };\n\t}\n\tconst linear = field(kind, \"linear\");\n\tconst base = field(linear, \"base\");\n\tconst perEncodedKib = field(linear, \"encoded_size_mul_per_kib\");\n\tif (isMist(base) && isMist(perEncodedKib)) {\n\t\treturn { linear: { base, perEncodedKib } };\n\t}\n\tthrow unreadable(host);\n}\n\nfunction field(value: unknown, key: string): unknown {\n\tif (typeof value !== \"object\" || value === null) {\n\t\treturn undefined;\n\t}\n\treturn (value as Readonly<Record<string, unknown>>)[key];\n}\n\nfunction isMist(value: unknown): value is number {\n\treturn typeof value === \"number\" && Number.isSafeInteger(value) && value >= 0;\n}\n\nfunction unreadable(host: string, cause?: unknown): TransportError {\n\treturn new TransportError(\n\t\t`The upload relay at ${host} sent a tip config this version cannot read.`,\n\t\tcause === undefined\n\t\t\t? { operation: OPERATION }\n\t\t\t: { cause, operation: OPERATION },\n\t);\n}\n","import type { TipStrategy } from \"@mysten/walrus\";\n\n// Walrus RS2 encoding for the shard count Testnet and Walrus Mainnet both run.\n// The SDK reads n_shards from the system object; it is fixed when a Walrus\n// system is created, so it is a constant here. The derivation follows\n// @mysten/walrus's getSourceSymbols (RS2 has no decoding safety limit) and\n// encodedBlobLength.\nconst N_SHARDS = 1000;\nconst MAX_FAULTY_SHARDS = Math.floor((N_SHARDS - 1) / 3);\nconst PRIMARY_SYMBOLS = N_SHARDS - 2 * MAX_FAULTY_SHARDS;\nconst SECONDARY_SYMBOLS = N_SHARDS - MAX_FAULTY_SHARDS;\n/** Unencoded bytes per unit of symbol size: 334 * 667 = 222,778. */\nconst BYTES_PER_SYMBOL = BigInt(PRIMARY_SYMBOLS * SECONDARY_SYMBOLS);\n/** Encoded bytes per unit of symbol size, over all slivers: 1,001,000. */\nconst ENCODED_BYTES_PER_SYMBOL = BigInt(\n\t(PRIMARY_SYMBOLS + SECONDARY_SYMBOLS) * N_SHARDS,\n);\n/** Per shard, two 32-byte digests for every shard and a 32-byte blob id: 64,032,000. */\nconst METADATA_BYTES = BigInt(N_SHARDS * (N_SHARDS * 32 * 2 + 32));\nconst BYTES_PER_KIB = 1024n;\n\n/**\n * The largest blob, in bytes, whose upload relay tip is at most `maxTipMist`.\n * `null` when the tip does not grow with size and fits under the cap, so the\n * cap sets no size limit. `0` when no blob fits, not even an empty one, since\n * Walrus encodes an empty blob as one byte.\n *\n * Inverts the tip `@mysten/walrus` 1.2.35 computes in `calculateUploadRelayTip`:\n *\n * symbols = floor((max(size, 1) - 1) / 222,778) + 1, rounded up to even\n * encoded = 64,032,000 + 1,001,000 * symbols\n * tip = base + perEncodedKib * ceil(encoded / 1024) (linear)\n * tip = const (const)\n *\n * The tip changes only when `symbols` does, in steps of two, and a blob of\n * `symbols * 222,778` bytes is the largest at each step. The limit is that\n * size for the largest even `symbols` whose tip fits.\n */\nexport function maxBlobBytesForTip(\n\tstrategy: TipStrategy,\n\tmaxTipMist: bigint,\n): number | null {\n\tif (\"const\" in strategy) {\n\t\treturn BigInt(strategy.const) <= maxTipMist ? null : 0;\n\t}\n\tconst base = BigInt(strategy.linear.base);\n\tconst perEncodedKib = BigInt(strategy.linear.perEncodedKib);\n\tif (perEncodedKib === 0n) {\n\t\treturn base <= maxTipMist ? null : 0;\n\t}\n\t// ceil(encoded / 1024) <= maxKib exactly when encoded <= maxKib * 1024.\n\t// A base above the cap leaves this at or below zero, under the metadata.\n\tconst maxEncodedBytes = ((maxTipMist - base) / perEncodedKib) * BYTES_PER_KIB;\n\tif (maxEncodedBytes < METADATA_BYTES) {\n\t\treturn 0;\n\t}\n\tconst symbols = (maxEncodedBytes - METADATA_BYTES) / ENCODED_BYTES_PER_SYMBOL;\n\tconst evenSymbols = symbols - (symbols % 2n);\n\treturn Number(evenSymbols * BYTES_PER_SYMBOL);\n}\n","import { ConfigurationError } from \"@arcadiasystems/morse-sdk\";\nimport { fetchRelayTipStrategy } from \"./fetch-relay-tip-strategy.js\";\nimport { maxBlobBytesForTip } from \"./max-blob-bytes-for-tip.js\";\nimport type { UploadRelayOptions, UploadSizeLimit } from \"./types.js\";\n\n/**\n * The largest upload the relay's tip cap allows, or `null` when nothing caps\n * the size: no relay, no tip, no finite `max`, or a tip that does not grow\n * with size and fits under `max`.\n *\n * Resolves the tip the way `@mysten/walrus` does: an inline `sendTip.kind` is\n * used as is, and `{ max }` alone means reading the relay's `/v1/tip-config`,\n * which happens here, before the upload starts.\n *\n * @throws {ConfigurationError} When `max` is below the relay's tip for even\n * the smallest blob, so the relay would refuse every upload.\n * @throws {TransportError} When the relay's tip config cannot be read.\n */\nexport async function resolveUploadSizeLimit(\n\trelay: UploadRelayOptions | undefined,\n): Promise<UploadSizeLimit | null> {\n\tif (relay === undefined) {\n\t\treturn null;\n\t}\n\tconst { sendTip } = relay;\n\tif (sendTip === undefined || sendTip === null) {\n\t\treturn null;\n\t}\n\t// The SDK refuses a tip only when it is greater than `max`, which no tip is\n\t// when `max` is missing, infinite or NaN.\n\tif (sendTip.max === undefined || !Number.isFinite(sendTip.max)) {\n\t\treturn null;\n\t}\n\t// Tips are whole MIST, so a fractional cap allows what its floor allows.\n\tconst maxTipMist = BigInt(Math.floor(sendTip.max));\n\tconst strategy =\n\t\t\"kind\" in sendTip ? sendTip.kind : await fetchRelayTipStrategy(relay.host);\n\tif (strategy === null) {\n\t\treturn null;\n\t}\n\tconst maxBytes = maxBlobBytesForTip(strategy, maxTipMist);\n\tif (maxBytes === null) {\n\t\treturn null;\n\t}\n\tif (maxBytes === 0) {\n\t\tthrow new ConfigurationError(\n\t\t\t`sendTip.max is ${maxTipMist.toLocaleString(\"en-US\")} MIST, below the tip the upload relay at ${relay.host} charges for even the smallest file, so it would refuse every upload. Raise sendTip.max in walrusWriteConfig.uploadRelay.`,\n\t\t);\n\t}\n\treturn { maxBytes, maxTipMist };\n}\n","import type {\n\tWalrusFlowCapable,\n\tWalrusWriteAdapter,\n} from \"@arcadiasystems/morse-sdk\";\nimport { FileTooLargeError } from \"./file-too-large-error.js\";\nimport type { UploadSizeLimit } from \"./types.js\";\n\n/**\n * Wrap `walrus` so `startBlobUpload` refuses a blob over `limit` before\n * handing it on. For encrypted uploads: Seal's envelope depends on the key\n * servers, so the size Walrus stores is known only once the SDK has\n * encrypted, and the SDK's upload helper passes the ciphertext straight to\n * `startBlobUpload`, which encodes it and then signs the register\n * transaction. Checking on entry is the last point before either.\n *\n * The upload helpers call only `startBlobUpload`; the other methods pass\n * through unchanged.\n */\nexport function withCiphertextSizeLimit(\n\twalrus: WalrusWriteAdapter & WalrusFlowCapable,\n\tlimit: UploadSizeLimit,\n): WalrusWriteAdapter & WalrusFlowCapable {\n\treturn {\n\t\tuploadBlob: (data, options) => walrus.uploadBlob(data, options),\n\t\tuploadQuilt: (patches, options) => walrus.uploadQuilt(patches, options),\n\t\tstartBlobUpload: async (data, options) => {\n\t\t\tif (data.length > limit.maxBytes) {\n\t\t\t\tthrow new FileTooLargeError({\n\t\t\t\t\tsizeBytes: data.length,\n\t\t\t\t\tafterEncryption: true,\n\t\t\t\t\tlimit,\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn walrus.startBlobUpload(data, options);\n\t\t},\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 { FileTooLargeError } from \"./file-too-large-error.js\";\nimport { resolveUploadSizeLimit } from \"./resolve-upload-size-limit.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\";\nimport { withCiphertextSizeLimit } from \"./with-ciphertext-size-limit.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 *\n * With an upload relay whose tip is capped, a file too large for the cap\n * fails with a {@link FileTooLargeError} naming the limit, before it is read.\n */\nexport function useFileUpload(): UseFileUploadResult {\n\tconst { adapter, config, walrusWrite, uploadRelay, 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\t// The SDK refuses a tip over the cap too, but only after encoding\n\t\t\t\t// the whole file, and as a transport error. Checking against the\n\t\t\t\t// relay's tip config first refuses the file before it is read.\n\t\t\t\tconst sizeLimit = await resolveUploadSizeLimit(uploadRelay);\n\t\t\t\tif (sizeLimit !== null && input.file.size > sizeLimit.maxBytes) {\n\t\t\t\t\tthrow new FileTooLargeError({\n\t\t\t\t\t\tsizeBytes: input.file.size,\n\t\t\t\t\t\tafterEncryption: false,\n\t\t\t\t\t\tlimit: sizeLimit,\n\t\t\t\t\t});\n\t\t\t\t}\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:\n\t\t\t\t\t\t\t\tsizeLimit === null\n\t\t\t\t\t\t\t\t\t? walrusWrite\n\t\t\t\t\t\t\t\t\t: withCiphertextSizeLimit(walrusWrite, sizeLimit),\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, uploadRelay, 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","// 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,11 +1,12 @@
|
|
|
1
|
-
import { B as BuildShareLinkArgs, U as UsdCostArgs, a as UsdCostEstimate, M as MorseFilesHandles, P as ParsedShareLink,
|
|
2
|
-
export { C as CompletedFileUpload,
|
|
1
|
+
import { B as BuildShareLinkArgs, U as UsdCostArgs, a as UsdCostEstimate, b as UploadSizeLimit, F as FileTooLargeErrorArgs, M as MorseFilesHandles, P as ParsedShareLink, S as ShareLinkSealErrorReason, c as FilePhase, d as UseFileDecryptResult, e as SealMaterial, f as UseFileDownloadResult, g as UseFileUploadResult, h as UseMorseFilesOptions, i as UseMorseFilesResult, j as UseRecipientFileResult, k as UseStorageCostEstimateOptions, l as UseStorageCostEstimateResult } from './types-C5hwiHjr.js';
|
|
2
|
+
export { C as CompletedFileUpload, m as FileDecryptInput, n as FileDecryptStatus, o as FileDownloadStatus, p as FileUploadInput, q as FileUploadStatus, L as LoadStatus, r as MorseFileDownloaderProps, s as MorseFileUploaderProps, t as MorseFilesWalletCallbacks, u as SetupStatus, v as UploadRelayOptions } from './types-C5hwiHjr.js';
|
|
3
|
+
import { FormattedError, RecipientFileId } from '@arcadiasystems/morse-sdk';
|
|
3
4
|
import { ReactNode, ReactElement, RefObject, ChangeEvent, MouseEvent, KeyboardEvent, DragEvent } from 'react';
|
|
4
5
|
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';
|
|
9
|
+
import '@mysten/walrus';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Approximate bytes the Seal envelope adds to a ciphertext, used to nudge the
|
|
@@ -23,6 +24,13 @@ declare const DEFAULT_ENCRYPTION_OVERHEAD_BYTES = 150;
|
|
|
23
24
|
*/
|
|
24
25
|
declare function buildShareLink(args: BuildShareLinkArgs): string;
|
|
25
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Render a download failure for the UI. Defers to the SDK's `formatUserMessage`
|
|
29
|
+
* except for a {@link ShareLinkSealError}, which the SDK does not know and
|
|
30
|
+
* would title "Unexpected error" when it is really "ask for the full link".
|
|
31
|
+
*/
|
|
32
|
+
declare function downloadErrorMessage(error: unknown): FormattedError;
|
|
33
|
+
|
|
26
34
|
/**
|
|
27
35
|
* Nominal Walrus epoch length in milliseconds for a network. Used by the USD
|
|
28
36
|
* cost estimate to convert an epoch count into months. These are the documented
|
|
@@ -38,6 +46,20 @@ declare function epochDurationMs(network: "testnet" | "mainnet"): number;
|
|
|
38
46
|
*/
|
|
39
47
|
declare function estimateStorageUsd(args: UsdCostArgs): UsdCostEstimate;
|
|
40
48
|
|
|
49
|
+
/**
|
|
50
|
+
* The file is too large to upload through the Walrus upload relay without a
|
|
51
|
+
* tip above `sendTip.max`. Raised by {@link useFileUpload} before the file is
|
|
52
|
+
* read, encrypted, encoded or signed for, or, when only Seal's envelope takes
|
|
53
|
+
* it over, after encryption and before encoding. Render it with
|
|
54
|
+
* {@link uploadErrorMessage}, or narrow on it and read `limit`.
|
|
55
|
+
*/
|
|
56
|
+
declare class FileTooLargeError extends Error {
|
|
57
|
+
readonly sizeBytes: number;
|
|
58
|
+
readonly afterEncryption: boolean;
|
|
59
|
+
readonly limit: UploadSizeLimit;
|
|
60
|
+
constructor(args: FileTooLargeErrorArgs);
|
|
61
|
+
}
|
|
62
|
+
|
|
41
63
|
/** Props for {@link MorseFilesProvider}. */
|
|
42
64
|
interface MorseFilesProviderProps {
|
|
43
65
|
/** The handles from {@link useMorseFiles} (memoized by that hook). */
|
|
@@ -75,13 +97,26 @@ declare function parseShareLink(href: string): ParsedShareLink | null;
|
|
|
75
97
|
*/
|
|
76
98
|
declare function resolveSealServers(fromOptions: readonly KeyServerConfig[] | undefined, fromConfig: readonly KeyServerConfig[]): readonly KeyServerConfig[] | undefined;
|
|
77
99
|
|
|
100
|
+
/**
|
|
101
|
+
* The file is encrypted on chain, and the share link cannot open it: the link
|
|
102
|
+
* has no Seal material, or its Seal prefix is not the file's. Raised by
|
|
103
|
+
* {@link useFileDownload} before any bytes are read or the wallet is asked to
|
|
104
|
+
* sign, so a damaged link never serves ciphertext as if it were the file.
|
|
105
|
+
*/
|
|
106
|
+
declare class ShareLinkSealError extends Error {
|
|
107
|
+
readonly reason: ShareLinkSealErrorReason;
|
|
108
|
+
constructor(reason: ShareLinkSealErrorReason);
|
|
109
|
+
}
|
|
110
|
+
|
|
78
111
|
/**
|
|
79
112
|
* Render an upload failure for the UI. Defers to the SDK's `formatUserMessage`
|
|
80
|
-
* except for
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
113
|
+
* except for two cases it cannot title. A {@link FileTooLargeError} is this
|
|
114
|
+
* package's own, and the SDK would call it "Unexpected error". An
|
|
115
|
+
* insufficient-balance failure the SDK sees only as a transport error carrying
|
|
116
|
+
* `@mysten/sui`'s raw text: a coin type, an address, and two unscaled
|
|
117
|
+
* integers. That reads as a network problem when it is really "top up the
|
|
118
|
+
* wallet", so it is rewritten here with the amounts in whole coins and the
|
|
119
|
+
* network's way of getting more.
|
|
85
120
|
*/
|
|
86
121
|
declare function uploadErrorMessage(error: unknown, network?: "testnet" | "mainnet"): FormattedError;
|
|
87
122
|
|
|
@@ -105,13 +140,18 @@ declare function useFileDecrypt(): UseFileDecryptResult;
|
|
|
105
140
|
|
|
106
141
|
/**
|
|
107
142
|
* Drive the single-file download page. Loads the on-chain metadata, then
|
|
108
|
-
* fetches the bytes on demand.
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
143
|
+
* fetches the bytes on demand. Fetching starts by reading the file's Seal
|
|
144
|
+
* prefix from chain, the one reliable sign that a file is encrypted; it is
|
|
145
|
+
* read only then, so a page that just shows the metadata pays no extra round
|
|
146
|
+
* trip. With Seal material from the share link the bytes are decrypted (one
|
|
147
|
+
* SessionKey signature); without it they are read straight from Walrus as a
|
|
148
|
+
* public file. A file with an on-chain prefix whose link has no Seal material,
|
|
149
|
+
* or material for another prefix, fails with a {@link ShareLinkSealError}
|
|
150
|
+
* instead. Files encrypted before the prefix was stored on chain have none, so
|
|
151
|
+
* for them the link still decides.
|
|
112
152
|
*
|
|
113
153
|
* @param fileId The on-chain RecipientFile id (from the share link).
|
|
114
|
-
* @param seal The Seal material (prefix + nonce)
|
|
154
|
+
* @param seal The Seal material (prefix + nonce) from the share link; null when it has none.
|
|
115
155
|
*/
|
|
116
156
|
declare function useFileDownload(fileId: RecipientFileId | null, seal: SealMaterial | null): UseFileDownloadResult;
|
|
117
157
|
|
|
@@ -121,6 +161,9 @@ declare function useFileDownload(fileId: RecipientFileId | null, seal: SealMater
|
|
|
121
161
|
* uploads a public file. Either way the recipient set is set at creation in two
|
|
122
162
|
* wallet popups. Tracks the phase stepper and returns the fileId, the seal
|
|
123
163
|
* material (encrypted only), and a share link.
|
|
164
|
+
*
|
|
165
|
+
* With an upload relay whose tip is capped, a file too large for the cap
|
|
166
|
+
* fails with a {@link FileTooLargeError} naming the limit, before it is read.
|
|
124
167
|
*/
|
|
125
168
|
declare function useFileUpload(): UseFileUploadResult;
|
|
126
169
|
|
|
@@ -276,4 +319,4 @@ declare function resolveOrigin(): string | null;
|
|
|
276
319
|
*/
|
|
277
320
|
declare function triggerDownload(objectUrl: string, filename: string): void;
|
|
278
321
|
|
|
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 };
|
|
322
|
+
export { BuildShareLinkArgs, DEFAULT_ENCRYPTION_OVERHEAD_BYTES, type DropzoneInputProps, type DropzoneRootProps, FilePhase, FileTooLargeError, FileTooLargeErrorArgs, MorseFilesHandles, MorseFilesProvider, type MorseFilesProviderProps, ParsedShareLink, SealMaterial, ShareLinkSealError, ShareLinkSealErrorReason, UploadSizeLimit, UsdCostArgs, UsdCostEstimate, type UseClipboardResult, type UseDropzoneOptions, type UseDropzoneResult, UseFileDecryptResult, UseFileDownloadResult, UseFileUploadResult, UseMorseFilesOptions, UseMorseFilesResult, UseRecipientFileResult, UseStorageCostEstimateOptions, UseStorageCostEstimateResult, buildShareLink, bytesToHex, downloadErrorMessage, 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, uploadErrorMessage, uploadPhaseLabel, uploadPhases, useClipboard, useDropzone, useFileDecrypt, useFileDownload, useFileUpload, useMorseFilesContext, useRecipientFile, useStorageCostEstimate } from './chunk-
|
|
1
|
+
import { MorseFilesContext } from './chunk-ZV3GFXNW.js';
|
|
2
|
+
export { DEFAULT_ENCRYPTION_OVERHEAD_BYTES, FileTooLargeError, ShareLinkSealError, buildShareLink, bytesToHex, downloadErrorMessage, epochDurationMs, estimateStorageUsd, formatBytes, formatUsd, isPreviewableImage, resolveOrigin, triggerDownload, uploadErrorMessage, uploadPhaseLabel, uploadPhases, useClipboard, useDropzone, useFileDecrypt, useFileDownload, useFileUpload, useMorseFilesContext, useRecipientFile, useStorageCostEstimate } from './chunk-ZV3GFXNW.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';
|
|
@@ -133,6 +133,7 @@ function useMorseFiles(options) {
|
|
|
133
133
|
},
|
|
134
134
|
signer
|
|
135
135
|
);
|
|
136
|
+
const uploadRelay = current.walrusWrite === void 0 ? current.walrusWriteConfig?.uploadRelay : void 0;
|
|
136
137
|
const walrusRead = current.walrusRead ?? HttpAggregatorReadAdapter.fromMorseConfig(config, client);
|
|
137
138
|
const sealServers = resolveSealServers(
|
|
138
139
|
current.sealKeyServers,
|
|
@@ -156,6 +157,7 @@ function useMorseFiles(options) {
|
|
|
156
157
|
adapter,
|
|
157
158
|
signer,
|
|
158
159
|
walrusWrite,
|
|
160
|
+
...uploadRelay === void 0 ? {} : { uploadRelay },
|
|
159
161
|
walrusRead,
|
|
160
162
|
seal,
|
|
161
163
|
reader
|
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/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"]}
|
|
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\t// The relay the default adapter sends through, so useFileUpload can\n\t\t\t\t// check its tip cap. A prebuilt adapter's relay is not visible here.\n\t\t\t\tconst uploadRelay =\n\t\t\t\t\tcurrent.walrusWrite === undefined\n\t\t\t\t\t\t? current.walrusWriteConfig?.uploadRelay\n\t\t\t\t\t\t: undefined;\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\t...(uploadRelay === undefined ? {} : { uploadRelay }),\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"]}
|
|
@@ -3,6 +3,7 @@ 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';
|
|
6
|
+
import { UploadRelayConfig } from '@mysten/walrus';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Types for the 0.4.0 RecipientFile module. A RecipientFile carries its
|
|
@@ -20,6 +21,20 @@ interface MorseFilesWalletCallbacks {
|
|
|
20
21
|
digest: string;
|
|
21
22
|
}>;
|
|
22
23
|
}
|
|
24
|
+
/** The Walrus upload relay the default write adapter sends blobs through. */
|
|
25
|
+
interface UploadRelayOptions {
|
|
26
|
+
/** The relay's base URL, which is per network. */
|
|
27
|
+
readonly host: string;
|
|
28
|
+
/**
|
|
29
|
+
* How to tip the relay, passed to `@mysten/walrus` as is. With `{ max }`
|
|
30
|
+
* the SDK reads the relay's `/v1/tip-config` and refuses a tip above `max`
|
|
31
|
+
* MIST. Without it no tip is sent, and the Walrus Mainnet relay requires
|
|
32
|
+
* one. Because the Walrus Mainnet tip grows with the blob's encoded size,
|
|
33
|
+
* `max` also caps the file size; {@link useFileUpload} refuses a larger
|
|
34
|
+
* file with a {@link FileTooLargeError} before reading it.
|
|
35
|
+
*/
|
|
36
|
+
readonly sendTip?: UploadRelayConfig["sendTip"];
|
|
37
|
+
}
|
|
23
38
|
/** Options for the {@link useMorseFiles} setup hook. */
|
|
24
39
|
interface UseMorseFilesOptions {
|
|
25
40
|
readonly network: "testnet" | "mainnet";
|
|
@@ -38,10 +53,7 @@ interface UseMorseFilesOptions {
|
|
|
38
53
|
/** Options for the default direct write adapter (wasm/relay), if not overridden. */
|
|
39
54
|
readonly walrusWriteConfig?: {
|
|
40
55
|
readonly wasmUrl?: string;
|
|
41
|
-
readonly uploadRelay?:
|
|
42
|
-
readonly host: string;
|
|
43
|
-
readonly sendTip?: unknown;
|
|
44
|
-
};
|
|
56
|
+
readonly uploadRelay?: UploadRelayOptions;
|
|
45
57
|
};
|
|
46
58
|
/** Prebuilt Walrus read adapter; defaults to the aggregator. */
|
|
47
59
|
readonly walrusRead?: WalrusReadAdapter;
|
|
@@ -78,6 +90,15 @@ interface MorseFilesHandles {
|
|
|
78
90
|
readonly signer: Signer;
|
|
79
91
|
/** Flow-capable write adapter (required for the single-PTB file ops). */
|
|
80
92
|
readonly walrusWrite: WalrusWriteAdapter & WalrusFlowCapable;
|
|
93
|
+
/**
|
|
94
|
+
* The upload relay `walrusWrite` sends blobs through. {@link useFileUpload}
|
|
95
|
+
* reads its tip cap to refuse a file too large for it before the upload
|
|
96
|
+
* starts. {@link useMorseFiles} sets it when it builds `walrusWrite` from
|
|
97
|
+
* `walrusWriteConfig`, and leaves it unset for a prebuilt `walrusWrite`,
|
|
98
|
+
* whose relay it cannot see. Without it the SDK still refuses an over-cap
|
|
99
|
+
* tip before signing, but only after encoding the whole file.
|
|
100
|
+
*/
|
|
101
|
+
readonly uploadRelay?: UploadRelayOptions;
|
|
81
102
|
readonly walrusRead: WalrusReadAdapter;
|
|
82
103
|
/**
|
|
83
104
|
* `null` when no Seal key servers are available, which on mainnet means no
|
|
@@ -118,6 +139,27 @@ interface FileUploadInput {
|
|
|
118
139
|
readonly epochs: number;
|
|
119
140
|
readonly deletable?: boolean;
|
|
120
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* The largest upload the relay's tip cap allows, derived from the relay's tip
|
|
144
|
+
* config and `sendTip.max`. Carried by {@link FileTooLargeError}.
|
|
145
|
+
*/
|
|
146
|
+
interface UploadSizeLimit {
|
|
147
|
+
/** Largest blob, in bytes, whose relay tip is at most `maxTipMist`. */
|
|
148
|
+
readonly maxBytes: number;
|
|
149
|
+
/** The tip cap the limit comes from (`sendTip.max`), in MIST. */
|
|
150
|
+
readonly maxTipMist: bigint;
|
|
151
|
+
}
|
|
152
|
+
/** Arguments for {@link FileTooLargeError}. */
|
|
153
|
+
interface FileTooLargeErrorArgs {
|
|
154
|
+
/** Bytes Walrus would have stored. */
|
|
155
|
+
readonly sizeBytes: number;
|
|
156
|
+
/**
|
|
157
|
+
* True when `sizeBytes` is the size after Seal encryption: the file fit,
|
|
158
|
+
* and the envelope took it over. False when the file itself is over.
|
|
159
|
+
*/
|
|
160
|
+
readonly afterEncryption: boolean;
|
|
161
|
+
readonly limit: UploadSizeLimit;
|
|
162
|
+
}
|
|
121
163
|
/** A completed upload plus its share link. */
|
|
122
164
|
interface CompletedFileUpload {
|
|
123
165
|
readonly fileId: RecipientFileId;
|
|
@@ -179,6 +221,11 @@ interface UseStorageCostEstimateResult {
|
|
|
179
221
|
}
|
|
180
222
|
/** Load/work status for {@link useFileDownload}. */
|
|
181
223
|
type FileDownloadStatus = "idle" | "loading" | "ready" | "error";
|
|
224
|
+
/**
|
|
225
|
+
* Why a share link cannot open the encrypted file it names: it carries no Seal
|
|
226
|
+
* material (`"missing"`), or its Seal prefix is not the file's (`"mismatch"`).
|
|
227
|
+
*/
|
|
228
|
+
type ShareLinkSealErrorReason = "missing" | "mismatch";
|
|
182
229
|
/** Return value of {@link useFileDownload}. */
|
|
183
230
|
interface UseFileDownloadResult {
|
|
184
231
|
/** Status of the on-chain metadata load. */
|
|
@@ -188,10 +235,20 @@ interface UseFileDownloadResult {
|
|
|
188
235
|
readonly status: FileDownloadStatus;
|
|
189
236
|
/** Object URL for the fetched bytes; null until loaded. */
|
|
190
237
|
readonly objectUrl: string | null;
|
|
191
|
-
/**
|
|
238
|
+
/**
|
|
239
|
+
* True when the share link carries Seal material, so `load` decrypts. A file
|
|
240
|
+
* with an on-chain Seal prefix opened from a link without that material
|
|
241
|
+
* reads false here, and `load` fails with a {@link ShareLinkSealError}
|
|
242
|
+
* instead of serving it.
|
|
243
|
+
*/
|
|
192
244
|
readonly encrypted: boolean;
|
|
193
245
|
/** True when the content type is a previewable image. */
|
|
194
246
|
readonly isImage: boolean;
|
|
247
|
+
/**
|
|
248
|
+
* Why the last `load` or `download` failed. A {@link ShareLinkSealError}
|
|
249
|
+
* when the file is encrypted on chain and the link cannot open it; render
|
|
250
|
+
* any value with {@link downloadErrorMessage}.
|
|
251
|
+
*/
|
|
195
252
|
readonly error: unknown;
|
|
196
253
|
/** Fetch the bytes (public read or Seal decrypt) and build the object URL. */
|
|
197
254
|
readonly load: () => Promise<void>;
|
|
@@ -279,4 +336,4 @@ interface ParsedShareLink {
|
|
|
279
336
|
readonly network: "testnet" | "mainnet" | null;
|
|
280
337
|
}
|
|
281
338
|
|
|
282
|
-
export type { BuildShareLinkArgs as B, CompletedFileUpload as C,
|
|
339
|
+
export type { BuildShareLinkArgs as B, CompletedFileUpload as C, FileTooLargeErrorArgs as F, LoadStatus as L, MorseFilesHandles as M, ParsedShareLink as P, ShareLinkSealErrorReason as S, UsdCostArgs as U, UsdCostEstimate as a, UploadSizeLimit as b, FilePhase as c, UseFileDecryptResult as d, SealMaterial as e, UseFileDownloadResult as f, UseFileUploadResult as g, UseMorseFilesOptions as h, UseMorseFilesResult as i, UseRecipientFileResult as j, UseStorageCostEstimateOptions as k, UseStorageCostEstimateResult as l, FileDecryptInput as m, FileDecryptStatus as n, FileDownloadStatus as o, FileUploadInput as p, FileUploadStatus as q, MorseFileDownloaderProps as r, MorseFileUploaderProps as s, MorseFilesWalletCallbacks as t, SetupStatus as u, UploadRelayOptions as v };
|
package/dist/ui.d.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { r as MorseFileDownloaderProps, s as MorseFileUploaderProps } from './types-C5hwiHjr.js';
|
|
2
2
|
import { ReactElement } from 'react';
|
|
3
3
|
import '@arcadiasystems/morse-sdk';
|
|
4
4
|
import '@mysten/seal';
|
|
5
5
|
import '@mysten/sui/cryptography';
|
|
6
6
|
import '@mysten/sui/grpc';
|
|
7
7
|
import '@mysten/sui/transactions';
|
|
8
|
+
import '@mysten/walrus';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Drop-in single-file viewer/downloader for a share link. Loads the on-chain
|
|
11
12
|
* metadata, then downloads (public read) or decrypts (recipient) the bytes with
|
|
12
|
-
* the correct filename and MIME type. A
|
|
13
|
-
*
|
|
14
|
-
* lives in {@link useFileDownload}.
|
|
13
|
+
* the correct filename and MIME type. A file with an on-chain Seal prefix
|
|
14
|
+
* opened without its Seal material shows an error asking for the full link
|
|
15
|
+
* instead of serving ciphertext. Logic lives in {@link useFileDownload}.
|
|
15
16
|
*/
|
|
16
17
|
declare function MorseFileDownloader(props: MorseFileDownloaderProps): ReactElement;
|
|
17
18
|
|
package/dist/ui.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { useFileDownload, formatBytes, useFileUpload, useClipboard, useMorseFilesContext, useStorageCostEstimate, useDropzone, uploadPhases, uploadErrorMessage, formatUsd, uploadPhaseLabel } from './chunk-
|
|
2
|
-
import { formatUserMessage } from '@arcadiasystems/morse-sdk';
|
|
1
|
+
import { useFileDownload, downloadErrorMessage, formatBytes, useFileUpload, useClipboard, useMorseFilesContext, useStorageCostEstimate, useDropzone, uploadPhases, uploadErrorMessage, formatUsd, uploadPhaseLabel } from './chunk-ZV3GFXNW.js';
|
|
3
2
|
import { useMemo, useState } from 'react';
|
|
4
3
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
5
4
|
|
|
@@ -22,7 +21,7 @@ function MorseFileDownloader(props) {
|
|
|
22
21
|
if (dl.metaStatus === "error" || file === null) {
|
|
23
22
|
return /* @__PURE__ */ jsx("section", { className: `mu-view ${className ?? ""}`, "data-mu": "view", children: /* @__PURE__ */ jsx("p", { className: "mu-error", role: "alert", children: "Could not load this file. The id may be wrong or the record may have been deleted." }) });
|
|
24
23
|
}
|
|
25
|
-
const bytesError = dl.error === null ? null :
|
|
24
|
+
const bytesError = dl.error === null ? null : downloadErrorMessage(dl.error);
|
|
26
25
|
return /* @__PURE__ */ jsxs("section", { className: `mu-view ${className ?? ""}`, "data-mu": "view", children: [
|
|
27
26
|
/* @__PURE__ */ jsxs("header", { className: "mu-view-head", children: [
|
|
28
27
|
/* @__PURE__ */ jsx("strong", { className: "mu-view-name", children: file.name }),
|