feat: implemented multiple preferred file (blossom) servers for user app data and meta
This commit is contained in:
parent
62639f1986
commit
070e8c39f0
@ -1,6 +1,6 @@
|
|||||||
<mxfile host="drawio-plugin" modified="2024-12-16T12:18:54.992Z" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36" etag="AV-hpNJbeaKzNFoRwzLd" version="22.1.22" type="embed">
|
<mxfile host="drawio-plugin" modified="2024-12-24T14:10:11.548Z" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36" etag="j0ni0qfsydyzknoEy44Z" version="22.1.22" type="embed">
|
||||||
<diagram id="ADjf0_COJFV7FXKRQUJh" name="Page-1">
|
<diagram id="ADjf0_COJFV7FXKRQUJh" name="Page-1">
|
||||||
<mxGraphModel dx="942" dy="687" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
|
<mxGraphModel dx="1130" dy="824" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" background="#ffffff" math="0" shadow="0">
|
||||||
<root>
|
<root>
|
||||||
<mxCell id="0" />
|
<mxCell id="0" />
|
||||||
<mxCell id="1" parent="0" />
|
<mxCell id="1" parent="0" />
|
||||||
@ -61,44 +61,119 @@
|
|||||||
<mxCell id="31" value="Show suggested Blossom servers" style="whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
<mxCell id="31" value="Show suggested Blossom servers" style="whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
<mxGeometry x="665" y="250" width="120" height="60" as="geometry" />
|
<mxGeometry x="665" y="250" width="120" height="60" as="geometry" />
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="33" value="" style="endArrow=none;dashed=1;html=1;dashPattern=1 3;strokeWidth=2;rounded=0;" edge="1" parent="1">
|
<mxCell id="33" value="" style="endArrow=none;dashed=1;html=1;dashPattern=1 3;strokeWidth=2;rounded=0;" parent="1" edge="1">
|
||||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||||
<mxPoint x="40" y="426" as="sourcePoint" />
|
<mxPoint x="40" y="426" as="sourcePoint" />
|
||||||
<mxPoint x="820" y="426" as="targetPoint" />
|
<mxPoint x="820" y="426" as="targetPoint" />
|
||||||
</mxGeometry>
|
</mxGeometry>
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="39" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="34" target="38">
|
<mxCell id="39" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="34" target="38" edge="1">
|
||||||
<mxGeometry relative="1" as="geometry" />
|
<mxGeometry relative="1" as="geometry" />
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="34" value="User 1" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
<mxCell id="34" value="User 1" style="ellipse;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
<mxGeometry x="40" y="510" width="170" height="80" as="geometry" />
|
<mxGeometry x="40" y="620" width="170" height="80" as="geometry" />
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="35" value="<span style="color: rgb(0, 0, 0); font-family: Helvetica; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: center; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(251, 251, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; float: none; display: inline !important;">Servers: A, B, C</span>" style="text;whiteSpace=wrap;html=1;fontSize=15;" vertex="1" parent="1">
|
<mxCell id="35" value="<span style="color: rgb(0, 0, 0); font-family: Helvetica; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: center; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(251, 251, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; float: none; display: inline !important;">Servers: A, B, C</span>" style="text;whiteSpace=wrap;html=1;fontSize=15;" parent="1" vertex="1">
|
||||||
<mxGeometry x="70" y="460" width="110" height="30" as="geometry" />
|
<mxGeometry x="70" y="570" width="110" height="30" as="geometry" />
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="42" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="36" target="41">
|
<mxCell id="42" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="36" target="41" edge="1">
|
||||||
<mxGeometry relative="1" as="geometry" />
|
<mxGeometry relative="1" as="geometry" />
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="36" value="User 2" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
<mxCell id="36" value="User 2" style="ellipse;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
<mxGeometry x="320" y="510" width="170" height="80" as="geometry" />
|
<mxGeometry x="320" y="620" width="170" height="80" as="geometry" />
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="37" value="<span style="color: rgb(0, 0, 0); font-family: Helvetica; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: center; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(251, 251, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; float: none; display: inline !important;">Servers: D, E</span>" style="text;whiteSpace=wrap;html=1;fontSize=15;" vertex="1" parent="1">
|
<mxCell id="37" value="<span style="color: rgb(0, 0, 0); font-family: Helvetica; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: center; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(251, 251, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; float: none; display: inline !important;">Servers: D, E</span>" style="text;whiteSpace=wrap;html=1;fontSize=15;" parent="1" vertex="1">
|
||||||
<mxGeometry x="350" y="460" width="110" height="30" as="geometry" />
|
<mxGeometry x="350" y="570" width="110" height="30" as="geometry" />
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="40" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="38" target="36">
|
<mxCell id="40" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="38" target="36" edge="1">
|
||||||
<mxGeometry relative="1" as="geometry" />
|
<mxGeometry relative="1" as="geometry" />
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="38" value="Creates document, sign and publish to A, B, C" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
<mxCell id="38" value="Creates document, sign and publish to A, B, C" style="ellipse;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
<mxGeometry x="40" y="660" width="170" height="80" as="geometry" />
|
<mxGeometry x="40" y="770" width="170" height="80" as="geometry" />
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="44" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="41" target="43">
|
<mxCell id="44" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="41" target="43" edge="1">
|
||||||
<mxGeometry relative="1" as="geometry" />
|
<mxGeometry relative="1" as="geometry" />
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="41" value="Reads the files From A, B, C" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
<mxCell id="41" value="Reads the files From A, B, C" style="ellipse;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
<mxGeometry x="320" y="660" width="170" height="80" as="geometry" />
|
<mxGeometry x="320" y="770" width="170" height="80" as="geometry" />
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="43" value="Sign, update the meta.json and publish to D, E" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
<mxCell id="43" value="Sign, update the meta.json and publish to D, E" style="ellipse;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
<mxGeometry x="320" y="810" width="170" height="80" as="geometry" />
|
<mxGeometry x="320" y="920" width="170" height="80" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="45" value="<span style="color: rgb(0, 0, 0); font-family: Helvetica; font-size: 18px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: center; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(251, 251, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; float: none; display: inline !important;">File Servers (Blossom)</span>" style="text;whiteSpace=wrap;html=1;fontSize=18;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="330" y="440" width="190" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="46" value="<span style="color: rgb(0, 0, 0); font-family: Helvetica; font-size: 18px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: center; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(251, 251, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; float: none; display: inline !important;">Sigits</span>" style="text;whiteSpace=wrap;html=1;fontSize=18;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="40" y="510" width="50" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="47" value="<span style="color: rgb(0, 0, 0); font-family: Helvetica; font-size: 18px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: center; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(251, 251, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; float: none; display: inline !important;">User App Data</span>" style="text;whiteSpace=wrap;html=1;fontSize=18;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="40" y="1050" width="120" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="52" value="Loads the page" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="49" target="51" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="49" value="User" style="ellipse;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="40" y="1290" width="170" height="80" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="50" value="<span style="color: rgb(0, 0, 0); font-family: Helvetica; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: center; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(251, 251, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; float: none; display: inline !important;">Servers: A, B, C</span>" style="text;whiteSpace=wrap;html=1;fontSize=15;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="70" y="1240" width="110" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="61" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="51" target="60" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="51" value="Fetche the app data from A, B, C" style="ellipse;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="40" y="1450" width="170" height="80" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="64" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="58" target="63" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="58" value="List the sigits found in the app data" style="ellipse;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="40" y="1750" width="170" height="80" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="62" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="60" target="58" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="60" value="We have 3 data sources, we merge all 3 sets into 1 object" style="ellipse;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="40" y="1600" width="170" height="80" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="63" value="User opens a SIGIT (flow is on the top)" style="whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="65" y="1920" width="120" height="60" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="65" value="<p style="margin:0px;margin-top:4px;text-align:center;text-decoration:underline;"><b>interface UserAppData</b></p><hr><p style="margin: 0px 0px 0px 8px; font-size: 14px;">sigits<br style="border-color: var(--border-color); text-align: center;"><span style="text-align: center;">blossomVersions</span><br style="border-color: var(--border-color); text-align: center;"><span style="text-align: center;">processedGiftWrapps</span><br style="border-color: var(--border-color); text-align: center;"><span style="text-align: center;">keyPair</span><br></p>" style="verticalAlign=top;align=left;overflow=fill;fontSize=12;fontFamily=Helvetica;html=1;whiteSpace=wrap;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="40" y="1100" width="160" height="110" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="68" value="Creates a SIGIT" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="66" target="67" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="66" value="User" style="ellipse;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="320" y="1290" width="170" height="80" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="70" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="67" target="69" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="67" value="Publish the SIGIT to A,B,C" style="ellipse;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="320" y="1450" width="170" height="80" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="72" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="69" target="71" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="69" value="Capture the SIGIT urls and publish them in UserAppData to the servers A,B,C" style="ellipse;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="320" y="1610" width="170" height="80" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="74" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="71" target="73">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="71" value="Keep last&nbsp; 10 versions of blossom which includes SIGIT" style="ellipse;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="320" y="1770" width="170" height="80" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="79" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" edge="1" parent="1" source="73" target="77">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="73" value="Every blossom version can have multiple links that user added" style="whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="345" y="1920" width="120" height="60" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="77" value="Get latest version, if it has multiple URLs choose the first one which matches the hash" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="320" y="2060" width="170" height="80" as="geometry" />
|
||||||
</mxCell>
|
</mxCell>
|
||||||
</root>
|
</root>
|
||||||
</mxGraphModel>
|
</mxGraphModel>
|
||||||
|
@ -39,9 +39,13 @@ export const SignatureStrategy: MarkStrategy = {
|
|||||||
|
|
||||||
if (await isOnline()) {
|
if (await isOnline()) {
|
||||||
try {
|
try {
|
||||||
const url = await uploadToFileStorage(file)
|
const urls = await uploadToFileStorage(file)
|
||||||
console.info(`${file.name} uploaded to file storage`)
|
console.info(
|
||||||
return url
|
`${file.name} uploaded to following file storages: ${urls.join(', ')}`
|
||||||
|
)
|
||||||
|
// This bit was returning an url, and return of this function is being set to mark.value, so it kind of
|
||||||
|
// does not make sense to return an url to the file storage
|
||||||
|
return value
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
console.error(
|
console.error(
|
||||||
@ -51,7 +55,7 @@ export const SignatureStrategy: MarkStrategy = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// TOOD: offline
|
// TODO: offline
|
||||||
}
|
}
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
@ -86,7 +86,7 @@ export const useSigitMeta = (meta: Meta): FlatMeta => {
|
|||||||
}>({})
|
}>({})
|
||||||
const [markConfig, setMarkConfig] = useState<Mark[]>([])
|
const [markConfig, setMarkConfig] = useState<Mark[]>([])
|
||||||
const [title, setTitle] = useState<string>('')
|
const [title, setTitle] = useState<string>('')
|
||||||
const [zipUrl, setZipUrl] = useState<string>('')
|
const [zipUrls, setZipUrls] = useState<string[]>([])
|
||||||
|
|
||||||
const [parsedSignatureEvents, setParsedSignatureEvents] = useState<{
|
const [parsedSignatureEvents, setParsedSignatureEvents] = useState<{
|
||||||
[signer: `npub1${string}`]: DocSignatureEvent
|
[signer: `npub1${string}`]: DocSignatureEvent
|
||||||
@ -133,7 +133,7 @@ export const useSigitMeta = (meta: Meta): FlatMeta => {
|
|||||||
setId(id)
|
setId(id)
|
||||||
setSig(sig)
|
setSig(sig)
|
||||||
|
|
||||||
const { title, signers, viewers, fileHashes, markConfig, zipUrl } =
|
const { title, signers, viewers, fileHashes, markConfig, zipUrls } =
|
||||||
await parseCreateSignatureEventContent(content)
|
await parseCreateSignatureEventContent(content)
|
||||||
|
|
||||||
setTitle(title)
|
setTitle(title)
|
||||||
@ -141,7 +141,7 @@ export const useSigitMeta = (meta: Meta): FlatMeta => {
|
|||||||
setViewers(viewers)
|
setViewers(viewers)
|
||||||
setFileHashes(fileHashes)
|
setFileHashes(fileHashes)
|
||||||
setMarkConfig(markConfig)
|
setMarkConfig(markConfig)
|
||||||
setZipUrl(zipUrl)
|
setZipUrls(zipUrls)
|
||||||
|
|
||||||
let encryptionKey: string | undefined
|
let encryptionKey: string | undefined
|
||||||
if (meta.keys) {
|
if (meta.keys) {
|
||||||
@ -322,7 +322,7 @@ export const useSigitMeta = (meta: Meta): FlatMeta => {
|
|||||||
fileHashes,
|
fileHashes,
|
||||||
markConfig,
|
markConfig,
|
||||||
title,
|
title,
|
||||||
zipUrl,
|
zipUrls,
|
||||||
parsedSignatureEvents,
|
parsedSignatureEvents,
|
||||||
completedAt,
|
completedAt,
|
||||||
signedStatus,
|
signedStatus,
|
||||||
|
@ -55,7 +55,8 @@ import {
|
|||||||
DEFAULT_TOOLBOX,
|
DEFAULT_TOOLBOX,
|
||||||
settleAllFullfilfedPromises,
|
settleAllFullfilfedPromises,
|
||||||
DEFAULT_LOOK_UP_RELAY_LIST,
|
DEFAULT_LOOK_UP_RELAY_LIST,
|
||||||
uploadMetaToFileStorage
|
uploadMetaToFileStorage,
|
||||||
|
isValidNip05
|
||||||
} from '../../utils'
|
} from '../../utils'
|
||||||
import { Container } from '../../components/Container'
|
import { Container } from '../../components/Container'
|
||||||
import fileListStyles from '../../components/FileList/style.module.scss'
|
import fileListStyles from '../../components/FileList/style.module.scss'
|
||||||
@ -264,8 +265,7 @@ export const CreatePage = () => {
|
|||||||
// Otherwize if search already provided some results, user must manually click the search button
|
// Otherwize if search already provided some results, user must manually click the search button
|
||||||
if (!foundUsers.length) {
|
if (!foundUsers.length) {
|
||||||
// If it's NIP05 (includes @ or is a valid domain) send request to .well-known
|
// If it's NIP05 (includes @ or is a valid domain) send request to .well-known
|
||||||
const domainRegex = /^[a-zA-Z0-9@.-]+\.[a-zA-Z]{2,}$/
|
if (isValidNip05(userSearchInput)) {
|
||||||
if (domainRegex.test(userSearchInput)) {
|
|
||||||
setSearchUsersLoading(true)
|
setSearchUsersLoading(true)
|
||||||
|
|
||||||
const pubkey = await handleSearchUserNip05(userSearchInput)
|
const pubkey = await handleSearchUserNip05(userSearchInput)
|
||||||
@ -756,10 +756,10 @@ export const CreatePage = () => {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload the file to the storage
|
// Upload the file to the storage/s
|
||||||
const uploadFile = async (
|
const uploadFiles = async (
|
||||||
arrayBuffer: ArrayBuffer
|
arrayBuffer: ArrayBuffer
|
||||||
): Promise<string | null> => {
|
): Promise<string[] | null> => {
|
||||||
const blob = new Blob([arrayBuffer])
|
const blob = new Blob([arrayBuffer])
|
||||||
// Create a File object with the Blob data
|
// Create a File object with the Blob data
|
||||||
const file = new File([blob], `compressed-${unixNow()}.sigit`, {
|
const file = new File([blob], `compressed-${unixNow()}.sigit`, {
|
||||||
@ -818,14 +818,14 @@ export const CreatePage = () => {
|
|||||||
fileHashes: {
|
fileHashes: {
|
||||||
[key: string]: string
|
[key: string]: string
|
||||||
},
|
},
|
||||||
zipUrl: string
|
zipUrls: string[]
|
||||||
) => {
|
) => {
|
||||||
const content: CreateSignatureEventContent = {
|
const content: CreateSignatureEventContent = {
|
||||||
signers: signers.map((signer) => hexToNpub(signer.pubkey)),
|
signers: signers.map((signer) => hexToNpub(signer.pubkey)),
|
||||||
viewers: viewers.map((viewer) => hexToNpub(viewer.pubkey)),
|
viewers: viewers.map((viewer) => hexToNpub(viewer.pubkey)),
|
||||||
fileHashes,
|
fileHashes,
|
||||||
markConfig,
|
markConfig,
|
||||||
zipUrl,
|
zipUrls,
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -888,15 +888,15 @@ export const CreatePage = () => {
|
|||||||
|
|
||||||
const markConfig = createMarks(fileHashes)
|
const markConfig = createMarks(fileHashes)
|
||||||
|
|
||||||
setLoadingSpinnerDesc('Uploading files.zip to file storage')
|
setLoadingSpinnerDesc('Uploading files.zip to file storages')
|
||||||
const fileUrl = await uploadFile(encryptedArrayBuffer)
|
const fileUrls = await uploadFiles(encryptedArrayBuffer)
|
||||||
if (!fileUrl) return
|
if (!fileUrls) return
|
||||||
|
|
||||||
setLoadingSpinnerDesc('Generating create signature')
|
setLoadingSpinnerDesc('Generating create signature')
|
||||||
const createSignature = await generateCreateSignature(
|
const createSignature = await generateCreateSignature(
|
||||||
markConfig,
|
markConfig,
|
||||||
fileHashes,
|
fileHashes,
|
||||||
fileUrl
|
fileUrls
|
||||||
)
|
)
|
||||||
if (!createSignature) return
|
if (!createSignature) return
|
||||||
|
|
||||||
@ -934,11 +934,11 @@ export const CreatePage = () => {
|
|||||||
const event = await updateUsersAppData(meta)
|
const event = await updateUsersAppData(meta)
|
||||||
if (!event) return
|
if (!event) return
|
||||||
|
|
||||||
const metaUrl = await uploadMetaToFileStorage(meta, encryptionKey)
|
const metaUrls = await uploadMetaToFileStorage(meta, encryptionKey)
|
||||||
|
|
||||||
setLoadingSpinnerDesc('Sending notifications to counterparties')
|
setLoadingSpinnerDesc('Sending notifications to counterparties')
|
||||||
const promises = sendNotifications({
|
const promises = sendNotifications({
|
||||||
metaUrl,
|
metaUrls,
|
||||||
keys: meta.keys
|
keys: meta.keys
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -964,7 +964,7 @@ export const CreatePage = () => {
|
|||||||
const createSignature = await generateCreateSignature(
|
const createSignature = await generateCreateSignature(
|
||||||
markConfig,
|
markConfig,
|
||||||
fileHashes,
|
fileHashes,
|
||||||
''
|
[]
|
||||||
)
|
)
|
||||||
if (!createSignature) return
|
if (!createSignature) return
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ import {
|
|||||||
getRelayInfo,
|
getRelayInfo,
|
||||||
getRelayMap,
|
getRelayMap,
|
||||||
hexToNpub,
|
hexToNpub,
|
||||||
|
isValidRelayUri,
|
||||||
publishRelayMap,
|
publishRelayMap,
|
||||||
shorten
|
shorten
|
||||||
} from '../../../utils'
|
} from '../../../utils'
|
||||||
@ -149,12 +150,7 @@ export const RelaysPage = () => {
|
|||||||
const relayURI = `${webSocketPrefix}${newRelayURI?.trim().replace(webSocketPrefix, '')}`
|
const relayURI = `${webSocketPrefix}${newRelayURI?.trim().replace(webSocketPrefix, '')}`
|
||||||
|
|
||||||
// Check if new relay URI is a valid string
|
// Check if new relay URI is a valid string
|
||||||
if (
|
if (relayURI && !isValidRelayUri(relayURI)) {
|
||||||
relayURI &&
|
|
||||||
!/^wss:\/\/[-a-zA-Z0-9@:%._\\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}/.test(
|
|
||||||
relayURI
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
if (relayURI !== webSocketPrefix) {
|
if (relayURI !== webSocketPrefix) {
|
||||||
setNewRelayURIerror(
|
setNewRelayURIerror(
|
||||||
'New relay URI is not valid. Example of valid relay URI: wss://sigit.relay.io'
|
'New relay URI is not valid. Example of valid relay URI: wss://sigit.relay.io'
|
||||||
|
@ -22,7 +22,7 @@ import {
|
|||||||
} from '../../../utils/file-servers.ts'
|
} from '../../../utils/file-servers.ts'
|
||||||
import { useAppSelector } from '../../../hooks'
|
import { useAppSelector } from '../../../hooks'
|
||||||
import { useDidMount } from '../../../hooks'
|
import { useDidMount } from '../../../hooks'
|
||||||
import { SIGIT_BLOSSOM } from '../../../utils'
|
import { isValidUrl } from '../../../utils'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { cloneDeep } from 'lodash'
|
import { cloneDeep } from 'lodash'
|
||||||
|
|
||||||
@ -79,11 +79,7 @@ export const ServersPage = () => {
|
|||||||
if (!serverURL) return
|
if (!serverURL) return
|
||||||
|
|
||||||
// Check if new server is a valid URL
|
// Check if new server is a valid URL
|
||||||
if (
|
if (!isValidUrl(serverURL)) {
|
||||||
!/^(https?:\/\/)(?!-)([A-Za-z0-9-]{1,63}\.)+[A-Za-z]{2,6}$/gim.test(
|
|
||||||
serverURL
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
if (serverURL !== protocol) {
|
if (serverURL !== protocol) {
|
||||||
setNewRelayURLerror(errors.urlNotValid)
|
setNewRelayURLerror(errors.urlNotValid)
|
||||||
return
|
return
|
||||||
@ -109,10 +105,7 @@ export const ServersPage = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleDeleteServer = (serverURL: string) => {
|
const handleDeleteServer = (serverURL: string) => {
|
||||||
if (
|
if (Object.keys(blossomServersMap).length === 1)
|
||||||
serverURL === SIGIT_BLOSSOM &&
|
|
||||||
Object.keys(blossomServersMap).length === 1
|
|
||||||
)
|
|
||||||
return serverRequirementWarning()
|
return serverRequirementWarning()
|
||||||
|
|
||||||
// Remove server from the list
|
// Remove server from the list
|
||||||
@ -213,6 +206,7 @@ export const ServersPage = () => {
|
|||||||
<ServerItem
|
<ServerItem
|
||||||
key={key}
|
key={key}
|
||||||
serverURL={key}
|
serverURL={key}
|
||||||
|
preventDelete={Object.keys(blossomServersMap).length === 1}
|
||||||
handleDeleteServer={handleDeleteServer}
|
handleDeleteServer={handleDeleteServer}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
@ -225,10 +219,15 @@ export const ServersPage = () => {
|
|||||||
|
|
||||||
interface ServerItemProps {
|
interface ServerItemProps {
|
||||||
serverURL: string
|
serverURL: string
|
||||||
|
preventDelete?: boolean
|
||||||
handleDeleteServer?: (serverURL: string) => void
|
handleDeleteServer?: (serverURL: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const ServerItem = ({ serverURL, handleDeleteServer }: ServerItemProps) => {
|
const ServerItem = ({
|
||||||
|
serverURL,
|
||||||
|
handleDeleteServer,
|
||||||
|
preventDelete
|
||||||
|
}: ServerItemProps) => {
|
||||||
return (
|
return (
|
||||||
<Box className={styles.server}>
|
<Box className={styles.server}>
|
||||||
<List>
|
<List>
|
||||||
@ -244,7 +243,7 @@ const ServerItem = ({ serverURL, handleDeleteServer }: ServerItemProps) => {
|
|||||||
|
|
||||||
<Box
|
<Box
|
||||||
onClick={() => handleDeleteServer && handleDeleteServer(serverURL)}
|
onClick={() => handleDeleteServer && handleDeleteServer(serverURL)}
|
||||||
className={`${styles.leaveServerContainer} ${serverURL === SIGIT_BLOSSOM ? styles.disabled : ''}`}
|
className={`${styles.leaveServerContainer} ${preventDelete ? styles.disabled : ''}`}
|
||||||
>
|
>
|
||||||
<DeleteIcon />
|
<DeleteIcon />
|
||||||
<span>Remove</span>
|
<span>Remove</span>
|
||||||
|
@ -287,26 +287,42 @@ export const SignPage = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const { zipUrl, encryptionKey } = res
|
const { zipUrls, encryptionKey } = res
|
||||||
|
|
||||||
setLoadingSpinnerDesc('Fetching file from file server')
|
for (let i = 0; i < zipUrls.length; i++) {
|
||||||
axios
|
const zipUrl = zipUrls[i]
|
||||||
.get(zipUrl, {
|
const isLastZipUrl = i === zipUrls.length - 1
|
||||||
responseType: 'arraybuffer'
|
|
||||||
})
|
setLoadingSpinnerDesc('Fetching file from file server')
|
||||||
.then((res) => {
|
|
||||||
|
const res = await axios
|
||||||
|
.get(zipUrl, {
|
||||||
|
responseType: 'arraybuffer'
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(
|
||||||
|
`error occurred in getting file from ${zipUrls}`,
|
||||||
|
err
|
||||||
|
)
|
||||||
|
toast.error(
|
||||||
|
err.message || `error occurred in getting file from ${zipUrls}`
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
setIsLoading(false)
|
||||||
|
|
||||||
|
if (res) {
|
||||||
handleArrayBufferFromBlossom(res.data, encryptionKey)
|
handleArrayBufferFromBlossom(res.data, encryptionKey)
|
||||||
setMeta(metaInNavState)
|
setMeta(metaInNavState)
|
||||||
})
|
break
|
||||||
.catch((err) => {
|
} else {
|
||||||
console.error(`error occurred in getting file from ${zipUrl}`, err)
|
// No data returned, break from the loop
|
||||||
toast.error(
|
if (isLastZipUrl) {
|
||||||
err.message || `error occurred in getting file from ${zipUrl}`
|
break
|
||||||
)
|
}
|
||||||
})
|
}
|
||||||
.finally(() => {
|
}
|
||||||
setIsLoading(false)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
processSigit()
|
processSigit()
|
||||||
@ -471,6 +487,10 @@ export const SignPage = () => {
|
|||||||
setMeta(parsedMetaJson)
|
setMeta(parsedMetaJson)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the signing process
|
||||||
|
* When user signs, files will automatically be published to all user preferred servers
|
||||||
|
*/
|
||||||
const handleSign = async () => {
|
const handleSign = async () => {
|
||||||
if (Object.entries(files).length === 0 || !meta) return
|
if (Object.entries(files).length === 0 || !meta) return
|
||||||
|
|
||||||
@ -652,9 +672,9 @@ export const SignPage = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let metaUrl: string
|
let metaUrls: string[]
|
||||||
try {
|
try {
|
||||||
metaUrl = await uploadMetaToFileStorage(meta, encryptionKey)
|
metaUrls = await uploadMetaToFileStorage(meta, encryptionKey)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
toast.error(error.message)
|
toast.error(error.message)
|
||||||
@ -696,7 +716,10 @@ export const SignPage = () => {
|
|||||||
setLoadingSpinnerDesc('Sending notifications')
|
setLoadingSpinnerDesc('Sending notifications')
|
||||||
const users = Array.from(userSet)
|
const users = Array.from(userSet)
|
||||||
const promises = users.map((user) =>
|
const promises = users.map((user) =>
|
||||||
sendNotification(npubToHex(user)!, { metaUrl, keys: meta.keys })
|
sendNotification(npubToHex(user)!, {
|
||||||
|
metaUrls: metaUrls,
|
||||||
|
keys: meta.keys
|
||||||
|
})
|
||||||
)
|
)
|
||||||
await Promise.all(promises)
|
await Promise.all(promises)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
@ -216,7 +216,7 @@ export const VerifyPage = () => {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
submittedBy,
|
submittedBy,
|
||||||
zipUrl,
|
zipUrls,
|
||||||
encryptionKey,
|
encryptionKey,
|
||||||
signers,
|
signers,
|
||||||
viewers,
|
viewers,
|
||||||
@ -376,7 +376,7 @@ export const VerifyPage = () => {
|
|||||||
const users = Array.from(userSet)
|
const users = Array.from(userSet)
|
||||||
const promises = users.map((user) =>
|
const promises = users.map((user) =>
|
||||||
sendNotification(npubToHex(user)!, {
|
sendNotification(npubToHex(user)!, {
|
||||||
metaUrl,
|
metaUrls: metaUrl,
|
||||||
keys: meta.keys!
|
keys: meta.keys!
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@ -403,35 +403,55 @@ export const VerifyPage = () => {
|
|||||||
const processSigit = async () => {
|
const processSigit = async () => {
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
|
|
||||||
|
// We have multiple zipUrls, we should fetch one by one and take the first one which successfully decrypts
|
||||||
|
// If file is altered decrytption will fail
|
||||||
setLoadingSpinnerDesc('Fetching file from file server')
|
setLoadingSpinnerDesc('Fetching file from file server')
|
||||||
try {
|
|
||||||
const res = await axios.get(zipUrl, {
|
|
||||||
responseType: 'arraybuffer'
|
|
||||||
})
|
|
||||||
|
|
||||||
const fileName = zipUrl.split('/').pop()
|
for (let i = 0; i < zipUrls.length; i++) {
|
||||||
const file = new File([res.data], fileName!)
|
const zipUrl = ''
|
||||||
|
const isLastZipUrl = i === zipUrls.length - 1
|
||||||
|
|
||||||
const encryptedArrayBuffer = await file.arrayBuffer()
|
try {
|
||||||
const arrayBuffer = await decryptArrayBuffer(
|
// Fetch zip data
|
||||||
encryptedArrayBuffer,
|
const res = await axios.get(zipUrl, {
|
||||||
encryptionKey
|
responseType: 'arraybuffer'
|
||||||
).catch((err) => {
|
})
|
||||||
console.log('err in decryption:>> ', err)
|
|
||||||
toast.error(err.message || 'An error occurred in decrypting file.')
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
|
|
||||||
if (arrayBuffer) {
|
// Prepare file from response
|
||||||
|
const fileName = zipUrl.split('/').pop()
|
||||||
|
const file = new File([res.data], fileName!)
|
||||||
|
const encryptedArrayBuffer = await file.arrayBuffer()
|
||||||
|
|
||||||
|
// Decrypt the array buffer
|
||||||
|
const arrayBuffer = await decryptArrayBuffer(
|
||||||
|
encryptedArrayBuffer,
|
||||||
|
encryptionKey
|
||||||
|
).catch((err) => {
|
||||||
|
console.error('Error in decryption:>> ', err)
|
||||||
|
toast.error(
|
||||||
|
err.message || 'An error occurred in decrypting file.'
|
||||||
|
)
|
||||||
|
return null // Continue iteration for next zipUrl
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!arrayBuffer) {
|
||||||
|
if (!isLastZipUrl) continue // Skip to next zipUrl if decryption fails
|
||||||
|
break // If last zipUrl break out of loop
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load zip archive
|
||||||
const zip = await JSZip.loadAsync(arrayBuffer).catch((err) => {
|
const zip = await JSZip.loadAsync(arrayBuffer).catch((err) => {
|
||||||
console.log('err in loading zip file :>> ', err)
|
console.error('Error in loading zip file :>> ', err)
|
||||||
toast.error(
|
toast.error(
|
||||||
err.message || 'An error occurred in loading zip file.'
|
err.message || 'An error occurred in loading zip file.'
|
||||||
)
|
)
|
||||||
return null
|
return null // Skip to next zipUrl
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!zip) return
|
if (!zip) {
|
||||||
|
if (!isLastZipUrl) continue // Skip to next zipUrl
|
||||||
|
break // If last zipUrl break out of loop
|
||||||
|
}
|
||||||
|
|
||||||
const files: { [fileName: string]: SigitFile } = {}
|
const files: { [fileName: string]: SigitFile } = {}
|
||||||
const fileHashes: { [key: string]: string | null } = {}
|
const fileHashes: { [key: string]: string | null } = {}
|
||||||
@ -439,47 +459,44 @@ export const VerifyPage = () => {
|
|||||||
(entry) => entry.name
|
(entry) => entry.name
|
||||||
)
|
)
|
||||||
|
|
||||||
// generate hashes for all entries in files folder of zipArchive
|
// Generate hashes for all entries in the files folder of zipArchive
|
||||||
// these hashes can be used to verify the originality of files
|
for (const entryFileName of fileNames) {
|
||||||
for (const fileName of fileNames) {
|
const entryArrayBuffer = await readContentOfZipEntry(
|
||||||
const arrayBuffer = await readContentOfZipEntry(
|
|
||||||
zip,
|
zip,
|
||||||
fileName,
|
entryFileName,
|
||||||
'arraybuffer'
|
'arraybuffer'
|
||||||
)
|
)
|
||||||
|
if (entryArrayBuffer) {
|
||||||
if (arrayBuffer) {
|
files[entryFileName] = await convertToSigitFile(
|
||||||
files[fileName] = await convertToSigitFile(
|
entryArrayBuffer,
|
||||||
arrayBuffer,
|
entryFileName
|
||||||
fileName!
|
|
||||||
)
|
)
|
||||||
const hash = await getHash(arrayBuffer)
|
const hash = await getHash(entryArrayBuffer)
|
||||||
|
|
||||||
if (hash) {
|
if (hash) {
|
||||||
fileHashes[fileName.replace(/^files\//, '')] = hash
|
fileHashes[entryFileName.replace(/^files\//, '')] = hash
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fileHashes[fileName.replace(/^files\//, '')] = null
|
fileHashes[entryFileName.replace(/^files\//, '')] = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setCurrentFileHashes(fileHashes)
|
setCurrentFileHashes(fileHashes)
|
||||||
setFiles(files)
|
setFiles(files)
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
|
} catch (err) {
|
||||||
|
const message = `error occurred in getting file from ${zipUrl}`
|
||||||
|
console.error(message, err)
|
||||||
|
if (err instanceof Error) toast.error(err.message)
|
||||||
|
else toast.error(message)
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
const message = `error occurred in getting file from ${zipUrl}`
|
|
||||||
console.error(message, err)
|
|
||||||
if (err instanceof Error) toast.error(err.message)
|
|
||||||
else toast.error(message)
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
processSigit()
|
processSigit()
|
||||||
}
|
}
|
||||||
}, [encryptionKey, metaInNavState, zipUrl])
|
}, [encryptionKey, metaInNavState, zipUrls])
|
||||||
|
|
||||||
const handleVerify = async () => {
|
const handleVerify = async () => {
|
||||||
if (!selectedFile) return
|
if (!selectedFile) return
|
||||||
|
@ -5,7 +5,7 @@ import { UserAppDataDispatchTypes } from './types'
|
|||||||
const initialState: UserAppData = {
|
const initialState: UserAppData = {
|
||||||
sigits: {},
|
sigits: {},
|
||||||
processedGiftWraps: [],
|
processedGiftWraps: [],
|
||||||
blossomUrls: []
|
blossomVersions: []
|
||||||
}
|
}
|
||||||
|
|
||||||
const reducer = (
|
const reducer = (
|
||||||
|
@ -19,7 +19,6 @@ export interface Meta {
|
|||||||
exportSignature?: string
|
exportSignature?: string
|
||||||
keys?: { sender: string; keys: { [user: `npub1${string}`]: string } }
|
keys?: { sender: string; keys: { [user: `npub1${string}`]: string } }
|
||||||
timestamps?: OpenTimestamp[]
|
timestamps?: OpenTimestamp[]
|
||||||
// TODO Add field: fileServers
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateSignatureEventContent {
|
export interface CreateSignatureEventContent {
|
||||||
@ -28,7 +27,7 @@ export interface CreateSignatureEventContent {
|
|||||||
fileHashes: { [key: string]: string }
|
fileHashes: { [key: string]: string }
|
||||||
markConfig: Mark[]
|
markConfig: Mark[]
|
||||||
title: string
|
title: string
|
||||||
zipUrl: string
|
zipUrls: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SignedEventContent {
|
export interface SignedEventContent {
|
||||||
@ -76,9 +75,14 @@ export interface UserAppData {
|
|||||||
*/
|
*/
|
||||||
keyPair?: Keys
|
keyPair?: Keys
|
||||||
/**
|
/**
|
||||||
* Array for storing Urls for the files that stores all the sigits and processedGiftWraps on blossom.
|
* Array for storing Urls for the files which stores all sigits and processedGiftWraps on file servers (blossom).
|
||||||
|
* We keep the last 10 versions
|
||||||
*/
|
*/
|
||||||
blossomUrls: string[]
|
blossomVersions: BlossomVersion[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BlossomVersion {
|
||||||
|
urls: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DocSignatureEvent extends Event {
|
export interface DocSignatureEvent extends Event {
|
||||||
@ -86,10 +90,10 @@ export interface DocSignatureEvent extends Event {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SigitNotification {
|
export interface SigitNotification {
|
||||||
metaUrl: string
|
metaUrls: string[]
|
||||||
keys?: { sender: string; keys: { [user: `npub1${string}`]: string } }
|
keys?: { sender: string; keys: { [user: `npub1${string}`]: string } }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isSigitNotification(obj: unknown): obj is SigitNotification {
|
export function isSigitNotification(obj: unknown): obj is SigitNotification {
|
||||||
return typeof (obj as SigitNotification).metaUrl === 'string'
|
return typeof (obj as SigitNotification).metaUrls === 'object'
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,8 @@ export enum MetaStorageErrorType {
|
|||||||
'FETCH_FAILED' = 'Fetching meta.json requires an encryption key.',
|
'FETCH_FAILED' = 'Fetching meta.json requires an encryption key.',
|
||||||
'HASH_VERIFICATION_FAILED' = 'Unable to verify meta.json.',
|
'HASH_VERIFICATION_FAILED' = 'Unable to verify meta.json.',
|
||||||
'DECRYPTION_FAILED' = 'Error decryping meta.json.',
|
'DECRYPTION_FAILED' = 'Error decryping meta.json.',
|
||||||
'UNHANDLED_FETCH_ERROR' = 'Unable to fetch meta.json. Something went wrong.'
|
'UNHANDLED_FETCH_ERROR' = 'Unable to fetch meta.json. Something went wrong.',
|
||||||
|
'NO_URLS_PROCESSED_SUCCESSFULLY' = 'No URLs were available to process.'
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MetaStorageError extends Error {
|
export class MetaStorageError extends Error {
|
||||||
|
@ -167,47 +167,72 @@ export const uploadMetaToFileStorage = async (
|
|||||||
|
|
||||||
// Create the encrypted json file from array buffer and hash
|
// Create the encrypted json file from array buffer and hash
|
||||||
const file = new File([encryptedArrayBuffer], `${hash}.json`)
|
const file = new File([encryptedArrayBuffer], `${hash}.json`)
|
||||||
const url = await uploadToFileStorage(file)
|
const urls = await uploadToFileStorage(file)
|
||||||
|
|
||||||
return url
|
return urls
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the meta from one or more file storages, one by one, and it will take the first one, which has matching hash
|
||||||
|
* @param urls urls of meta files
|
||||||
|
* @param encryptionKey
|
||||||
|
*/
|
||||||
export const fetchMetaFromFileStorage = async (
|
export const fetchMetaFromFileStorage = async (
|
||||||
url: string,
|
urls: string[],
|
||||||
encryptionKey: string | undefined
|
encryptionKey: string | undefined
|
||||||
) => {
|
): Promise<Meta> => {
|
||||||
if (!encryptionKey) {
|
if (!encryptionKey) {
|
||||||
throw new MetaStorageError(MetaStorageErrorType.ENCRYPTION_KEY_REQUIRED)
|
throw new MetaStorageError(MetaStorageErrorType.ENCRYPTION_KEY_REQUIRED)
|
||||||
}
|
}
|
||||||
|
|
||||||
const encryptedArrayBuffer = await axios.get(url, {
|
for (let i = 0; i < urls.length; i++) {
|
||||||
responseType: 'arraybuffer'
|
const url = urls[i]
|
||||||
})
|
const isLastUrl = i === urls.length - 1
|
||||||
|
const encryptedArrayBuffer = await axios.get(url, {
|
||||||
// Verify hash
|
responseType: 'arraybuffer'
|
||||||
const parts = url.split('/')
|
|
||||||
const urlHash = parts[parts.length - 1]
|
|
||||||
const hash = await getHash(encryptedArrayBuffer.data)
|
|
||||||
if (hash !== urlHash) {
|
|
||||||
throw new MetaStorageError(MetaStorageErrorType.HASH_VERIFICATION_FAILED)
|
|
||||||
}
|
|
||||||
|
|
||||||
const arrayBuffer = await decryptArrayBuffer(
|
|
||||||
encryptedArrayBuffer.data,
|
|
||||||
encryptionKey
|
|
||||||
).catch((err) => {
|
|
||||||
throw new MetaStorageError(MetaStorageErrorType.DECRYPTION_FAILED, {
|
|
||||||
cause: err
|
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
if (arrayBuffer) {
|
// Verify hash
|
||||||
// Decode meta.json and parse
|
const parts = url.split('/')
|
||||||
const decoder = new TextDecoder()
|
const urlHash = parts[parts.length - 1]
|
||||||
const json = decoder.decode(arrayBuffer)
|
const hash = await getHash(encryptedArrayBuffer.data)
|
||||||
const meta = await parseJson<Meta>(json)
|
if (hash !== urlHash) {
|
||||||
return meta
|
// If no more urls left to try and hash check failed, throw an error
|
||||||
|
if (isLastUrl)
|
||||||
|
throw new MetaStorageError(
|
||||||
|
MetaStorageErrorType.HASH_VERIFICATION_FAILED
|
||||||
|
)
|
||||||
|
// Otherwise, skip to the next url to fetch
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const arrayBuffer = await decryptArrayBuffer(
|
||||||
|
encryptedArrayBuffer.data,
|
||||||
|
encryptionKey
|
||||||
|
).catch((err) => {
|
||||||
|
if (isLastUrl) {
|
||||||
|
throw new MetaStorageError(MetaStorageErrorType.DECRYPTION_FAILED, {
|
||||||
|
cause: err
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (arrayBuffer) {
|
||||||
|
// Decode meta.json and parse
|
||||||
|
const decoder = new TextDecoder()
|
||||||
|
const json = decoder.decode(arrayBuffer)
|
||||||
|
const meta = await parseJson<Meta>(json)
|
||||||
|
return meta
|
||||||
|
} else if (!isLastUrl) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new MetaStorageError(MetaStorageErrorType.UNHANDLED_FETCH_ERROR)
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new MetaStorageError(MetaStorageErrorType.UNHANDLED_FETCH_ERROR)
|
throw new MetaStorageError(
|
||||||
|
MetaStorageErrorType.NO_URLS_PROCESSED_SUCCESSFULLY
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import axios from 'axios'
|
import axios, { AxiosResponse } from 'axios'
|
||||||
import {
|
import {
|
||||||
Event,
|
Event,
|
||||||
EventTemplate,
|
EventTemplate,
|
||||||
@ -11,7 +11,11 @@ import {
|
|||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { NostrController } from '../controllers'
|
import { NostrController } from '../controllers'
|
||||||
import store from '../store/store'
|
import store from '../store/store'
|
||||||
import { CreateSignatureEventContent, Meta } from '../types'
|
import {
|
||||||
|
CreateSignatureEventContent,
|
||||||
|
FileServerPutResponse,
|
||||||
|
Meta
|
||||||
|
} from '../types'
|
||||||
import { hexToNpub, unixNow } from './nostr'
|
import { hexToNpub, unixNow } from './nostr'
|
||||||
import { parseJson } from './string'
|
import { parseJson } from './string'
|
||||||
import { hexToBytes } from '@noble/hashes/utils'
|
import { hexToBytes } from '@noble/hashes/utils'
|
||||||
@ -19,18 +23,23 @@ import { getHash } from './hash.ts'
|
|||||||
import { SIGIT_BLOSSOM } from './const.ts'
|
import { SIGIT_BLOSSOM } from './const.ts'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uploads a file to a file storage service.
|
* Uploads a file to one or more file storage services.
|
||||||
* @param blob The Blob object representing the file to upload.
|
* @param blob The Blob object representing the file to upload.
|
||||||
* @param nostrController The NostrController instance for handling authentication.
|
* @param nostrController The NostrController instance for handling authentication.
|
||||||
* @returns The URL of the uploaded file.
|
* @returns The array of URLs of the uploaded file.
|
||||||
*/
|
*/
|
||||||
export const uploadToFileStorage = async (file: File) => {
|
export const uploadToFileStorage = async (file: File): Promise<string[]> => {
|
||||||
// Define event metadata for authorization
|
// Define event metadata for authorization
|
||||||
const hash = await getHash(await file.arrayBuffer())
|
const hash = await getHash(await file.arrayBuffer())
|
||||||
if (!hash) {
|
if (!hash) {
|
||||||
throw new Error("Can't get file hash.")
|
throw new Error("Can't get file hash.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const preferredServersMap = store.getState().servers.map || {}
|
||||||
|
const preferredServers = Object.keys(preferredServersMap)
|
||||||
|
// If no servers found, use SIGIT as fallback
|
||||||
|
if (!preferredServers.length) preferredServers.push(SIGIT_BLOSSOM)
|
||||||
|
|
||||||
const event: EventTemplate = {
|
const event: EventTemplate = {
|
||||||
kind: 24242,
|
kind: 24242,
|
||||||
content: 'Authorize Upload',
|
content: 'Authorize Upload',
|
||||||
@ -54,16 +63,28 @@ export const uploadToFileStorage = async (file: File) => {
|
|||||||
// Sign the authorization event using the dedicated key stored in user app data
|
// Sign the authorization event using the dedicated key stored in user app data
|
||||||
const authEvent = finalizeEvent(event, hexToBytes(key))
|
const authEvent = finalizeEvent(event, hexToBytes(key))
|
||||||
|
|
||||||
// Upload the file to the file storage service using Axios
|
const uploadPromises: Promise<AxiosResponse<FileServerPutResponse>>[] = []
|
||||||
const response = await axios.put(`${SIGIT_BLOSSOM}/upload`, file, {
|
|
||||||
headers: {
|
|
||||||
Authorization: 'Nostr ' + btoa(JSON.stringify(authEvent)), // Set authorization header
|
|
||||||
'Content-Type': 'application/sigit' // Set content type header
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Return the URL of the uploaded file
|
// Upload the file to the file storage services using Axios
|
||||||
return response.data.url as string
|
for (const preferredServer of preferredServers) {
|
||||||
|
const uploadPromise = axios.put<FileServerPutResponse>(
|
||||||
|
`${preferredServer}/upload`,
|
||||||
|
file,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: 'Nostr ' + btoa(JSON.stringify(authEvent)), // Set authorization header
|
||||||
|
'Content-Type': 'application/sigit' // Set content type header
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
uploadPromises.push(uploadPromise)
|
||||||
|
}
|
||||||
|
|
||||||
|
const responses = await Promise.all(uploadPromises)
|
||||||
|
|
||||||
|
// Return the URLs of the uploaded files
|
||||||
|
return responses.map((response) => response.data.url) as string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -228,7 +249,7 @@ export const extractZipUrlAndEncryptionKey = async (meta: Meta) => {
|
|||||||
if (!createSignatureContent) return null
|
if (!createSignatureContent) return null
|
||||||
|
|
||||||
// Extract the ZIP URL from the create signature content
|
// Extract the ZIP URL from the create signature content
|
||||||
const zipUrl = createSignatureContent.zipUrl
|
const zipUrls = createSignatureContent.zipUrls
|
||||||
|
|
||||||
// Retrieve the user's public key from the state
|
// Retrieve the user's public key from the state
|
||||||
const usersPubkey = store.getState().auth.usersPubkey!
|
const usersPubkey = store.getState().auth.usersPubkey!
|
||||||
@ -259,7 +280,7 @@ export const extractZipUrlAndEncryptionKey = async (meta: Meta) => {
|
|||||||
return {
|
return {
|
||||||
createSignatureEvent,
|
createSignatureEvent,
|
||||||
createSignatureContent,
|
createSignatureContent,
|
||||||
zipUrl,
|
zipUrls: zipUrls,
|
||||||
encryptionKey: decrypted
|
encryptionKey: decrypted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { bytesToHex, hexToBytes } from '@noble/hashes/utils'
|
import { bytesToHex, hexToBytes } from '@noble/hashes/utils'
|
||||||
import axios from 'axios'
|
import axios, { AxiosResponse } from 'axios'
|
||||||
import _, { truncate } from 'lodash'
|
import _, { truncate } from 'lodash'
|
||||||
import {
|
import {
|
||||||
Event,
|
Event,
|
||||||
@ -30,6 +30,8 @@ import {
|
|||||||
import { Keys } from '../store/auth/types'
|
import { Keys } from '../store/auth/types'
|
||||||
import store from '../store/store'
|
import store from '../store/store'
|
||||||
import {
|
import {
|
||||||
|
BlossomVersion,
|
||||||
|
FileServerPutResponse,
|
||||||
isSigitNotification,
|
isSigitNotification,
|
||||||
Meta,
|
Meta,
|
||||||
ProfileMetadata,
|
ProfileMetadata,
|
||||||
@ -431,16 +433,16 @@ export const getUsersAppData = async (): Promise<UserAppData | null> => {
|
|||||||
// Return null if encrypted content retrieval fails
|
// Return null if encrypted content retrieval fails
|
||||||
if (!encryptedContent) return null
|
if (!encryptedContent) return null
|
||||||
|
|
||||||
// Handle case where the encrypted content is an empty object
|
// Handle a case where the encrypted content is an empty object
|
||||||
if (encryptedContent === '{}') {
|
if (encryptedContent === '{}') {
|
||||||
// Generate ephemeral key pair
|
// Generate an ephemeral key pair
|
||||||
const secret = generateSecretKey()
|
const secret = generateSecretKey()
|
||||||
const pubKey = getPublicKey(secret)
|
const pubKey = getPublicKey(secret)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sigits: {},
|
sigits: {},
|
||||||
processedGiftWraps: [],
|
processedGiftWraps: [],
|
||||||
blossomUrls: [],
|
blossomVersions: [],
|
||||||
keyPair: {
|
keyPair: {
|
||||||
private: bytesToHex(secret),
|
private: bytesToHex(secret),
|
||||||
public: pubKey
|
public: pubKey
|
||||||
@ -466,6 +468,7 @@ export const getUsersAppData = async (): Promise<UserAppData | null> => {
|
|||||||
|
|
||||||
// Parse the decrypted content
|
// Parse the decrypted content
|
||||||
const parsedContent = await parseJson<{
|
const parsedContent = await parseJson<{
|
||||||
|
blossomVersions: BlossomVersion[]
|
||||||
blossomUrls: string[]
|
blossomUrls: string[]
|
||||||
keyPair: Keys
|
keyPair: Keys
|
||||||
}>(decrypted).catch((err) => {
|
}>(decrypted).catch((err) => {
|
||||||
@ -481,14 +484,23 @@ export const getUsersAppData = async (): Promise<UserAppData | null> => {
|
|||||||
// Return null if parsing fails
|
// Return null if parsing fails
|
||||||
if (!parsedContent) return null
|
if (!parsedContent) return null
|
||||||
|
|
||||||
const { blossomUrls, keyPair } = parsedContent
|
// If old property blossomUrls is found, convert it to new appraoch blossomVersions
|
||||||
|
if (parsedContent.blossomUrls) {
|
||||||
|
parsedContent.blossomVersions = parsedContent.blossomUrls.map((url) => {
|
||||||
|
return {
|
||||||
|
urls: [url]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const { blossomVersions, keyPair } = parsedContent
|
||||||
|
|
||||||
// Return null if no blossom URLs are found
|
// Return null if no blossom URLs are found
|
||||||
if (blossomUrls.length === 0) return null
|
if (blossomVersions.length === 0) return null
|
||||||
|
|
||||||
// Fetch additional user app data from the first blossom URL
|
// Fetch additional user app data from the last blossom version urls
|
||||||
const dataFromBlossom = await getUserAppDataFromBlossom(
|
const dataFromBlossom = await getUserAppDataFromBlossom(
|
||||||
blossomUrls[0],
|
blossomVersions[0],
|
||||||
keyPair.private
|
keyPair.private
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -499,7 +511,7 @@ export const getUsersAppData = async (): Promise<UserAppData | null> => {
|
|||||||
|
|
||||||
// Return the final user application data
|
// Return the final user application data
|
||||||
return {
|
return {
|
||||||
blossomUrls,
|
blossomVersions: blossomVersions,
|
||||||
keyPair,
|
keyPair,
|
||||||
sigits,
|
sigits,
|
||||||
processedGiftWraps
|
processedGiftWraps
|
||||||
@ -543,9 +555,9 @@ export const updateUsersAppData = async (meta: Meta) => {
|
|||||||
|
|
||||||
if (!isUpdated) return null
|
if (!isUpdated) return null
|
||||||
|
|
||||||
const blossomUrls = [...appData.blossomUrls]
|
const blossomVersions = [...appData.blossomVersions]
|
||||||
|
|
||||||
const newBlossomUrl = await uploadUserAppDataToBlossom(
|
const newBlossomUrls = await uploadUserAppDataToBlossom(
|
||||||
sigits,
|
sigits,
|
||||||
appData.processedGiftWraps,
|
appData.processedGiftWraps,
|
||||||
appData.keyPair.private
|
appData.keyPair.private
|
||||||
@ -560,21 +572,26 @@ export const updateUsersAppData = async (meta: Meta) => {
|
|||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!newBlossomUrl) return null
|
if (!newBlossomUrls) return null
|
||||||
|
|
||||||
// insert new blossom url at the start of the array
|
// insert new server (blossom) urls at the start of the array
|
||||||
blossomUrls.unshift(newBlossomUrl)
|
blossomVersions.unshift({
|
||||||
|
urls: newBlossomUrls
|
||||||
|
})
|
||||||
|
|
||||||
// only keep last 10 blossom urls, delete older ones
|
// only keep last 10 blossom versions (urls), delete older ones
|
||||||
if (blossomUrls.length > 10) {
|
// Every version can be uploaded to multiple servers
|
||||||
const filesToDelete = blossomUrls.splice(10)
|
if (blossomVersions.length > 10) {
|
||||||
filesToDelete.forEach((url) => {
|
const versionsToDelete = blossomVersions.splice(10)
|
||||||
deleteBlossomFile(url, appData.keyPair!.private).catch((err) => {
|
versionsToDelete.forEach((version) => {
|
||||||
console.log(
|
for (const url of version.urls) {
|
||||||
'An error occurred in removing old file of user app data from blossom server',
|
deleteBlossomFile(url, appData.keyPair!.private).catch((err) => {
|
||||||
err
|
console.log(
|
||||||
)
|
`An error occurred while removing an old file of user app data from the file server: ${url}`,
|
||||||
})
|
err
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -586,7 +603,7 @@ export const updateUsersAppData = async (meta: Meta) => {
|
|||||||
.nip04Encrypt(
|
.nip04Encrypt(
|
||||||
usersPubkey,
|
usersPubkey,
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
blossomUrls,
|
blossomVersions: blossomVersions,
|
||||||
keyPair: appData.keyPair
|
keyPair: appData.keyPair
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@ -656,7 +673,7 @@ export const updateUsersAppData = async (meta: Meta) => {
|
|||||||
store.dispatch(
|
store.dispatch(
|
||||||
updateUserAppDataAction({
|
updateUserAppDataAction({
|
||||||
sigits,
|
sigits,
|
||||||
blossomUrls,
|
blossomVersions: blossomVersions,
|
||||||
processedGiftWraps: [...appData.processedGiftWraps],
|
processedGiftWraps: [...appData.processedGiftWraps],
|
||||||
keyPair: {
|
keyPair: {
|
||||||
...appData.keyPair
|
...appData.keyPair
|
||||||
@ -696,7 +713,7 @@ const deleteBlossomFile = async (url: string, privateKey: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to upload user application data to the SIGit Blossom server.
|
* Function to upload user application data to the user preferred File (Blossom) servers.
|
||||||
* @param sigits - An object containing metadata for the user application data.
|
* @param sigits - An object containing metadata for the user application data.
|
||||||
* @param processedGiftWraps - An array of processed gift wrap IDs.
|
* @param processedGiftWraps - An array of processed gift wrap IDs.
|
||||||
* @param privateKey - The private key used for encryption.
|
* @param privateKey - The private key used for encryption.
|
||||||
@ -707,6 +724,11 @@ const uploadUserAppDataToBlossom = async (
|
|||||||
processedGiftWraps: string[],
|
processedGiftWraps: string[],
|
||||||
privateKey: string
|
privateKey: string
|
||||||
) => {
|
) => {
|
||||||
|
const preferredServersMap = store.getState().servers.map || {}
|
||||||
|
const preferredServers = Object.keys(preferredServersMap)
|
||||||
|
// If no servers found, use SIGIT as fallback
|
||||||
|
if (!preferredServers.length) preferredServers.push(SIGIT_BLOSSOM)
|
||||||
|
|
||||||
// Create an object containing the sigits and processed gift wraps
|
// Create an object containing the sigits and processed gift wraps
|
||||||
const obj = {
|
const obj = {
|
||||||
sigits,
|
sigits,
|
||||||
@ -752,31 +774,49 @@ const uploadUserAppDataToBlossom = async (
|
|||||||
|
|
||||||
// Finalize the event with the private key
|
// Finalize the event with the private key
|
||||||
const authEvent = finalizeEvent(event, hexToBytes(privateKey))
|
const authEvent = finalizeEvent(event, hexToBytes(privateKey))
|
||||||
// TODO send to all added preferred blossom/file servers
|
|
||||||
// Upload the file to the file storage service using Axios
|
|
||||||
const response = await axios.put(`${SIGIT_BLOSSOM}/upload`, file, {
|
|
||||||
headers: {
|
|
||||||
Authorization: 'Nostr ' + btoa(JSON.stringify(authEvent)) // Set authorization header
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Return the URL of the uploaded file
|
const uploadPromises: Promise<AxiosResponse<FileServerPutResponse>>[] = []
|
||||||
return response.data.url as string
|
|
||||||
|
// Upload the file to the file storage services using Axios
|
||||||
|
for (const preferredServer of preferredServers) {
|
||||||
|
const uploadPromise = axios.put<FileServerPutResponse>(
|
||||||
|
`${preferredServer}/upload`,
|
||||||
|
file,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: 'Nostr ' + btoa(JSON.stringify(authEvent)) // Set authorization header
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
uploadPromises.push(uploadPromise)
|
||||||
|
}
|
||||||
|
|
||||||
|
const responses = await Promise.all(uploadPromises)
|
||||||
|
|
||||||
|
// Return the URLs of the uploaded files
|
||||||
|
return responses.map((response) => response.data.url) as string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to retrieve and decrypt user application data from Blossom server.
|
* Function to retrieve and decrypt user application data from file (Blossom) servers.
|
||||||
* @param url - The URL to fetch the encrypted data from.
|
* Since we pull from multiple servers, we will take the first one
|
||||||
|
* @param blossomVersion - The URL to fetch the encrypted data from.
|
||||||
* @param privateKey - The private key used for decryption.
|
* @param privateKey - The private key used for decryption.
|
||||||
* @returns A promise that resolves to the decrypted and parsed user application data.
|
* @returns A promise that resolves to the decrypted and parsed user application data.
|
||||||
*/
|
*/
|
||||||
const getUserAppDataFromBlossom = async (url: string, privateKey: string) => {
|
const getUserAppDataFromBlossom = async (
|
||||||
|
blossomVersion: BlossomVersion,
|
||||||
|
privateKey: string
|
||||||
|
) => {
|
||||||
// Initialize errorCode to track HTTP error codes
|
// Initialize errorCode to track HTTP error codes
|
||||||
let errorCode = 0
|
let errorCode = 0
|
||||||
|
|
||||||
|
const blossomUrl = blossomVersion.urls[0]
|
||||||
|
|
||||||
// Fetch the encrypted data from the provided URL
|
// Fetch the encrypted data from the provided URL
|
||||||
const encrypted = await axios
|
const encrypted = await axios
|
||||||
.get(url, {
|
.get(blossomUrl, {
|
||||||
responseType: 'blob' // Expect a blob response
|
responseType: 'blob' // Expect a blob response
|
||||||
})
|
})
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
@ -788,8 +828,13 @@ const getUserAppDataFromBlossom = async (url: string, privateKey: string) => {
|
|||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
// Log and display an error message if the request fails
|
// Log and display an error message if the request fails
|
||||||
console.error(`error occurred in getting file from ${url}`, err)
|
console.error(
|
||||||
toast.error(err.message || `error occurred in getting file from ${url}`)
|
`error occurred in getting file from ${blossomVersion}`,
|
||||||
|
err
|
||||||
|
)
|
||||||
|
toast.error(
|
||||||
|
err.message || `error occurred in getting file from ${blossomVersion}`
|
||||||
|
)
|
||||||
|
|
||||||
// Set errorCode to the HTTP status code if available
|
// Set errorCode to the HTTP status code if available
|
||||||
if (err.request) {
|
if (err.request) {
|
||||||
@ -950,7 +995,10 @@ const processReceivedEvent = async (event: Event, difficulty: number = 5) => {
|
|||||||
encryptionKey = decrypted
|
encryptionKey = decrypted
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
meta = await fetchMetaFromFileStorage(notification.metaUrl, encryptionKey)
|
meta = await fetchMetaFromFileStorage(
|
||||||
|
notification.metaUrls,
|
||||||
|
encryptionKey
|
||||||
|
)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`An error occured fetching meta file from storage`, error)
|
console.error(`An error occured fetching meta file from storage`, error)
|
||||||
return
|
return
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { TimeoutError } from '../types/errors/TimeoutError.ts'
|
import { TimeoutError } from '../types/errors/TimeoutError.ts'
|
||||||
import { CurrentUserFile } from '../types/file.ts'
|
import { CurrentUserFile } from '../types/file.ts'
|
||||||
import { SigitFile } from './file.ts'
|
import { SigitFile } from './file.ts'
|
||||||
|
import { NIP05_REGEX } from '../constants.ts'
|
||||||
|
|
||||||
export const debounceCustom = <T extends (...args: never[]) => void>(
|
export const debounceCustom = <T extends (...args: never[]) => void>(
|
||||||
fn: T,
|
fn: T,
|
||||||
@ -143,3 +144,25 @@ export const isPromiseRejected = <T>(
|
|||||||
): result is PromiseRejectedResult => {
|
): result is PromiseRejectedResult => {
|
||||||
return result.status === 'rejected'
|
return result.status === 'rejected'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if it's valid {protocol}{domain}
|
||||||
|
* @param url
|
||||||
|
*/
|
||||||
|
export const isValidUrl = (url: string) => {
|
||||||
|
return /^(https?:\/\/)(?!-)([A-Za-z0-9-]{1,63}\.)+[A-Za-z]{2,6}$/gim.test(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if it's a valid domain or nip05 format
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
export const isValidNip05 = (value: string) => {
|
||||||
|
return NIP05_REGEX.test(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isValidRelayUri = (value: string) => {
|
||||||
|
return /^wss:\/\/[-a-zA-Z0-9@:%._\\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}/.test(
|
||||||
|
value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user