diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index 016db3f..0782323 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -27,6 +27,7 @@ interface IUser { interface IDatabase { Initialized: boolean; + ipnsData: Number[]; Blogs: IBlog[]; Users: { [key: string]: IUser; }; } diff --git a/src/App.tsx b/src/App.tsx index febd843..717c971 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,6 +8,9 @@ import { useDispatch, useSelector } from 'react-redux'; import SyncWorker from 'components/SyncWorker'; import { connectWallet } from 'slices/web3Slice'; import { AppDispatch } from 'slices/store'; +import { + HashRouter as Router, +} from "react-router-dom"; function App() { const dispatch = useDispatch(); @@ -19,8 +22,9 @@ function App() { return (
- - + + + {loading && }
diff --git a/src/components/BlogComponent.tsx b/src/components/BlogComponent.tsx index fc3cf54..0673190 100644 --- a/src/components/BlogComponent.tsx +++ b/src/components/BlogComponent.tsx @@ -3,7 +3,7 @@ import Avatar from 'react-avatar'; import { useSelector } from "react-redux"; import { formatDate, getW3link, removeHtmlTags } from "utils/helper"; import MarkdownIt from 'markdown-it' ; -import { Link } from "react-router-dom"; +import { Link, useParams } from "react-router-dom"; interface IBlogComponent { blog: IBlog; @@ -14,6 +14,7 @@ const md = new MarkdownIt(); const BlogComponent = (props: IBlogComponent) => { const { blog } = props; const users = useSelector((state) => state.db.Users); + const {ipnsCid} = useParams(); return (
@@ -22,7 +23,7 @@ const BlogComponent = (props: IBlogComponent) => {
{users[blog.Creator]?.Name || blog.Creator}
{formatDate(blog.Date)}
- +

{blog.Title}

{removeHtmlTags(md.render(blog.Body || ""))} diff --git a/src/components/BlogReader.tsx b/src/components/BlogReader.tsx index ecc4a40..ea99d6f 100644 --- a/src/components/BlogReader.tsx +++ b/src/components/BlogReader.tsx @@ -19,8 +19,10 @@ const BlogReader = (props: IBlogReader) => { ); const [blog, setBlog] = useState(undefined); + const {ipnsCid} = useParams(); useEffect(() => { + console.log({blogs}); const blog1 = blogs.find((b) => b.UUID === uuid); setBlog(blog1); }, [blogs, uuid]); @@ -36,9 +38,9 @@ const BlogReader = (props: IBlogReader) => { > {/* {blog?.Body || ""} */}
- {blog?.Creator === account && ( + {blog && blog?.Creator === account && (
- + diff --git a/src/components/NavBar.tsx b/src/components/NavBar.tsx index 024aeaa..d4800ae 100644 --- a/src/components/NavBar.tsx +++ b/src/components/NavBar.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from "react"; import { FaEdit, FaHome, FaPlus } from "react-icons/fa"; import { useSelector, useDispatch } from "react-redux"; -import { Link } from "react-router-dom"; +import { Link, useParams } from "react-router-dom"; import Button from "@mui/material/Button"; import Menu from "@mui/material/Menu"; import MenuItem from "@mui/material/MenuItem"; @@ -33,6 +33,8 @@ const NavBar = (props: INavBar) => { const [anchorEl, setAnchorEl] = React.useState(null); const open = Boolean(anchorEl); const [avatar, setAvatar] = useState(null); + + const {ipnsCid} = useParams(); useEffect(() => { setCurrentUser(account ? users[account] : null); @@ -56,12 +58,12 @@ const NavBar = (props: INavBar) => { }; const handleProfile = () => { - navigate("/main/profile"); + navigate(`/main/${ipnsCid}/profile`); setAnchorEl(null); }; const handleMyBlogs = () => { - navigate("/main/myblogs"); + navigate(`/main/${ipnsCid}/myblogs`); setAnchorEl(null); }; @@ -77,7 +79,7 @@ const NavBar = (props: INavBar) => {
- + @@ -86,7 +88,7 @@ const NavBar = (props: INavBar) => { {account && ( <>
- + diff --git a/src/components/NewBlog.tsx b/src/components/NewBlog.tsx index 69007e4..aa323be 100644 --- a/src/components/NewBlog.tsx +++ b/src/components/NewBlog.tsx @@ -11,7 +11,8 @@ import { Controlled as CodeMirror } from "react-codemirror2"; import "codemirror/lib/codemirror.css"; import "codemirror/theme/material.css"; import "codemirror/mode/markdown/markdown"; -import { removeHtmlTags } from "utils/helper"; +import { getStorageItem, removeHtmlTags } from "utils/helper"; +import config from "utils/config"; interface INewBlog {} @@ -28,6 +29,7 @@ const NewBlog = (props: INewBlog) => { const { uuid } = useParams(); const blogs = useSelector((state) => state.db.Blogs); const [curBlog, setCurBlog] = useState(undefined); + const {ipnsCid} = useParams(); useEffect(() => { const blog1 = blogs.find((b) => b.UUID === uuid); @@ -53,6 +55,7 @@ const NewBlog = (props: INewBlog) => { } dispatch(setLoading(true)); + const myIpnsCid = getStorageItem(config.IPNS_DATA + account, ""); const blog: IBlog = { Type: uuid ? "UPDATE_BLOG" : "ADD_BLOG", UUID: curBlog?.UUID || uuidv4(), @@ -62,7 +65,7 @@ const NewBlog = (props: INewBlog) => { BodyCID: "", }; - dispatch(createBlog(blog)) + dispatch(createBlog({blog, ipnsCid: myIpnsCid})) .unwrap() .then((blog) => { // handle result here @@ -70,7 +73,7 @@ const NewBlog = (props: INewBlog) => { `"${blog.Title}" ${uuid ? "Updated" : "Created"}`, "Blog Created" ); - navigate("/main"); + navigate(`/main/${ipnsCid}`); dispatch(setLoading(false)); }) .catch((rejectedValueOrSerializedError) => { diff --git a/src/components/Profile.tsx b/src/components/Profile.tsx index cf4a7a0..d6435d2 100644 --- a/src/components/Profile.tsx +++ b/src/components/Profile.tsx @@ -1,13 +1,15 @@ -import { Button, Grid, TextField } from "@mui/material"; +import { Button, TextField } from "@mui/material"; import React, { useState, useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; import { createUser, uploadImage } from "slices/dbSlice"; import { AppDispatch } from "slices/store"; import { NotificationManager } from "./Notification"; -import { useNavigate } from "react-router-dom"; +import { useNavigate, useParams } from "react-router-dom"; import Avatar from "react-avatar"; import { setLoading } from "slices/viewState"; -import { getW3link } from "utils/helper"; +import { getStorageItem, getW3link } from "utils/helper"; +import * as Name from "w3name"; +import config from "utils/config"; interface IProfile {} @@ -20,7 +22,7 @@ const Profile = (props: IProfile) => { (state) => state.web3.selectedAddress ); const [avatar, setAvatar] = useState(null); - + const {ipnsCid} = useParams(); const users = useSelector( (state) => state.db.Users ); @@ -44,11 +46,12 @@ const Profile = (props: IProfile) => { try { dispatch(setLoading(true)); + const myIpnsCid = getStorageItem(config.IPNS_DATA + account, ""); // save avatar let imageUrl: any = null; if (avatar) { - imageUrl = await dispatch(uploadImage(avatar)).unwrap(); + imageUrl = await dispatch(uploadImage({file: avatar, ipnsCid: myIpnsCid})).unwrap(); } else if (currentUser?.Image) { imageUrl = currentUser?.Image; } @@ -64,12 +67,12 @@ const Profile = (props: IProfile) => { user.Image = imageUrl; } - dispatch(createUser(user)) + dispatch(createUser({user, ipnsCid: myIpnsCid})) .unwrap() .then((user) => { // handle result here NotificationManager.success("", "Saved"); - navigate("/main"); + navigate(`/main/${ipnsCid}`); dispatch(setLoading(false)); }) .catch((rejectedValueOrSerializedError) => { @@ -122,6 +125,14 @@ const Profile = (props: IProfile) => { value={name} className="name" /> + { (state) => state.web3.selectedAddress ); const [progress, setProgress] = useState(0); + const {ipnsCid} = useParams(); + + // useEffect(() => { + // const lastSyncNumber = + // getStorageItem(config.LAST_SYNC_NUMBER + account, 0) || 0; + // dispatch(resetFromLocalStorage({ lastSyncNumber, account })); + // sync(); + // }, [account]); + + // useEffect(() => { + // (async function() { + // if (!account) { + // return; + // } + + // let ipnsData = (getStorageItem(config.ACCOUNT_IPNS + account, []) || []) as number[]; + // if (!ipnsData.length) { + // ipnsData = await fetchIPNS(account); + // setStorageItem(config.ACCOUNT_IPNS + account, ipnsData) + // } + + // dispatch(setIPNSData(ipnsData)); + + // // redirect page + // const name = await Name.from(new Uint8Array(ipnsData)); + // console.log("Reditect url: ", `/main/${name.toString()}`); + // navigate(`/main/${name.toString()}`); + + // })(); + // }, [account]); useEffect(() => { - const lastSyncNumber = - getStorageItem(config.LAST_SYNC_NUMBER + account, 0) || 0; - dispatch(resetFromLocalStorage({ lastSyncNumber, account })); sync(); - }, [account]); + }, [ipnsCid]); const sync = async () => { - if (!account) { + if (!ipnsCid) { return; } - console.log({ account }); + console.log("ipnsCid: ", ipnsCid); + + // show syncing progress bar dispatch(setSyncing(true)); + const client = new Web3Storage({ token: config.REACT_APP_WEB3_STORAGE_API_TOKEN, } as Service); setProgress(-1); - const uploadObjects: Upload[] = []; - for await (const upload of client.list()) { - uploadObjects.push(upload); - } - uploadObjects.sort((a, b) => (a.created > b.created ? 1 : -1)); - let lastSyncNumber = getStorageItem(config.LAST_SYNC_NUMBER + account, 0) || 0; + // gets all cids uploaded to this ipns + const ipnsData = await fetchIPNSFromName(ipnsCid) || []; + console.log("Stored ipnsData: ", ipnsData); + const cids = await getCIDsFromIPNS(ipnsData); + console.log("Stored cids: ", cids); + + // get the latest synced address + let lastSyncNumber = getStorageItem(config.LAST_SYNC_NUMBER + ipnsCid, 0) || 0; + + // check if the source code has been changed const version = getStorageItem(config.VERSION_LABEL, "") || ""; if (version !== config.version) { lastSyncNumber = 0; @@ -55,67 +90,39 @@ const SyncWorker = (props: ISyncWorker) => { } // reads data from the w3.storage - setProgress((lastSyncNumber * 100) / (uploadObjects.length || 1)); - for (let i = lastSyncNumber; i < uploadObjects.length; i++) { - setStorageItem(config.LAST_SYNC_NUMBER + account, i); - setProgress((i * 100) / (uploadObjects.length || 1)); - - const upload = uploadObjects[i]; - if (upload.name.indexOf("image_") === 0) { - continue; - } - - const res = await client.get(uploadObjects[i].cid); - if (!res) { - continue; - } + setProgress((lastSyncNumber * 100) / (cids.length || 1)); + for (let i = lastSyncNumber; i < cids.length; i++) { + setStorageItem(config.LAST_SYNC_NUMBER + ipnsCid, i); + setProgress((i * 100) / (cids.length || 1)); // get IPFS data - const contentRes = await api.get(getW3link(upload.cid)); + const contentRes = await api.get(getW3link(cids[i])); const { data } = contentRes; // parse each transactions if (["ADD_BLOG", "UPDATE_BLOG"].includes(String(data.Type))) { - if (data.Creator !== account) { - continue; - } - const contentRes1 = await api.get(getW3link(data.BodyCID)); data.Body = contentRes1.data; - } else if (data.Type === "ADD_USER") { - if (data.Wallet !== account) { - continue; - } } const record = { ...data, - CID: upload.cid, - Date: new Date(upload.created), + CID: cids[i], }; // add record to the local storage - setStorageItem(config.LAST_SYNC_RECORD + account + i, record); + setStorageItem(config.LAST_SYNC_RECORD + ipnsCid + i, record); } - setStorageItem(config.LAST_SYNC_NUMBER + account, uploadObjects.length); + console.log("Last sync number title: ", config.LAST_SYNC_NUMBER + ipnsCid); + setStorageItem(config.LAST_SYNC_NUMBER + ipnsCid, cids.length); setProgress(100); - dispatch(resetFromLocalStorage({ lastSyncNumber, account })); + dispatch(resetFromLocalStorage({ lastSyncNumber, name: ipnsCid })); dispatch(setSyncing(false)); setProgress(0); - const bytes = [ - 8, 1, 18, 64, 156, 104, 197, 108, 98, 238, 128, 116, 13, 126, 208, 222, - 80, 194, 183, 170, 167, 73, 250, 124, 95, 102, 184, 215, 105, 153, 34, - 130, 205, 105, 151, 223, 0, 176, 176, 185, 153, 101, 20, 176, 194, 194, - 102, 62, 44, 89, 48, 122, 163, 78, 28, 242, 74, 14, 169, 203, 130, 18, - 135, 200, 126, 8, 192, 182, - ]; - const name = await Name.from(new Uint8Array(bytes)); - const revision = await Name.resolve(name); - - setTimeout(sync, 60000); + // setTimeout(sync, 60000); }; return isSyncing ? ( diff --git a/src/hooks/useIPNSData.ts b/src/hooks/useIPNSData.ts new file mode 100644 index 0000000..fd4bd74 --- /dev/null +++ b/src/hooks/useIPNSData.ts @@ -0,0 +1,29 @@ +import { useEffect, useState } from "react"; +import { useParams } from "react-router-dom"; +import config from "utils/config"; +import { getStorageItem, setStorageItem } from "utils/helper"; +import { fetchIPNSFromName } from "utils/web3.storage"; + +const useIPNSData = () => { + const { ipnsCid } = useParams(); + const [ipnsData, setIpnsData] = useState([]); + useEffect(() => { + (async function() { + if (!ipnsCid) { + return; + } + + let _ipnsData = getStorageItem(config.IPNS_DATA + ipnsCid, []) || []; + if (_ipnsData.length === 0) { + _ipnsData = await fetchIPNSFromName(ipnsCid); + setStorageItem(config.IPNS_DATA + ipnsCid, _ipnsData); + } + + setIpnsData(_ipnsData); + })(); + }, [ipnsCid]); + + return ipnsData; +} + +export default useIPNSData; diff --git a/src/pages/HomeLayout.tsx b/src/pages/HomeLayout.tsx index 33e0b72..6886868 100644 --- a/src/pages/HomeLayout.tsx +++ b/src/pages/HomeLayout.tsx @@ -1,40 +1,41 @@ -import React from "react"; +import React, { useEffect } from "react"; import NavBar from "../components/NavBar"; -import { - Routes, - Route, - Navigate, -} from "react-router-dom"; +import { Routes, Route, Navigate, useParams } from "react-router-dom"; import SideBar from "components/SideBar"; import Home from "components/Home"; import NewBlog from "components/NewBlog"; import Profile from "components/Profile"; import MyBlogs from "components/MyBlogs"; import BlogReader from "components/BlogReader"; +import SyncWorker from "components/SyncWorker"; +import { fetchIPNSFromName } from "utils/web3.storage"; +import { getStorageItem, setStorageItem } from "utils/helper"; +import config from "utils/config"; +import useIPNSData from "hooks/useIPNSData"; const routes: any[] = [ { - path: '', + path: "", component: Home, }, { - path: 'new', + path: "new", component: NewBlog, }, { - path: 'profile', + path: "profile", component: Profile, }, { - path: 'myblogs', + path: "myblogs", component: MyBlogs, }, { - path: 'blogs/:uuid', + path: "blogs/:uuid", component: BlogReader, }, { - path: 'blogs/:uuid/edit', + path: "blogs/:uuid/edit", component: NewBlog, }, ]; @@ -42,20 +43,27 @@ const routes: any[] = [ interface IHomeLayout {} const HomeLayout = (props: IHomeLayout) => { + const { ipnsCid } = useParams(); + const ipnsData = useIPNSData(); + return (
+ +
- - {routes.map((e) => ( - - ))} - } /> - + {ipnsCid && ( + + {routes.map((e) => ( + + ))} + } /> + + )}
diff --git a/src/pages/MainLayout.tsx b/src/pages/MainLayout.tsx index cfee85d..159b2ab 100644 --- a/src/pages/MainLayout.tsx +++ b/src/pages/MainLayout.tsx @@ -1,29 +1,67 @@ -import React from "react"; - import { HashRouter as Router, Routes, Route, Navigate, + useParams, } from "react-router-dom"; import routes from "routes"; +import React, { useEffect, useState } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { useNavigate } from "react-router-dom"; +import { AppDispatch } from "slices/store"; +import { setLoading } from "slices/viewState"; +import config from "utils/config"; +import { getStorageItem, setStorageItem } from "utils/helper"; +import { fetchIPNS } from "utils/web3.storage"; +import * as Name from "w3name"; interface IMainLayout {} const MainLayout = (props: IMainLayout) => { + const dispatch = useDispatch(); + const navigate = useNavigate(); + const account = useSelector( + (state) => state.web3.selectedAddress + ); + const {ipnsCid} = useParams(); + + useEffect(() => { + (async function() { + if (!account) { + return; + } + + dispatch(setLoading(true)); + let ipnsCid1 = (getStorageItem(config.IPNS_DATA + account, "") || ""); + if (!ipnsCid1) { + const ipnsData = await fetchIPNS(account); + const name = await Name.from(new Uint8Array(ipnsData)); + ipnsCid1 = name.toString(); + } + setStorageItem(config.IPNS_DATA + account, ipnsCid1); + + // redirect page + console.log("Reditect url: ", `/main/${ipnsCid1}`); + dispatch(setLoading(false)); + + if (!ipnsCid) { + navigate(`/main/${ipnsCid1}`); + } + })(); + }, [account]); + return ( - - - {routes.map((e) => ( - - ))} - } /> - - + + {routes.map((e) => ( + + ))} + } /> + ); }; diff --git a/src/pages/RedirectPage.tsx b/src/pages/RedirectPage.tsx new file mode 100644 index 0000000..b55884d --- /dev/null +++ b/src/pages/RedirectPage.tsx @@ -0,0 +1,48 @@ +import React, { useEffect } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { useNavigate } from "react-router-dom"; +import { AppDispatch } from "slices/store"; +import { setLoading } from "slices/viewState"; +import config from "utils/config"; +import { getStorageItem, setStorageItem } from "utils/helper"; +import { fetchIPNS } from "utils/web3.storage"; +import * as Name from "w3name"; + +interface IRedirectPage {} + +const RedirectPage = (props: IRedirectPage) => { + const dispatch = useDispatch(); + const navigate = useNavigate(); + const account = useSelector( + (state) => state.web3.selectedAddress + ); + + // useEffect(() => { + // (async function() { + // if (!account) { + // return; + // } + + // dispatch(setLoading(true)); + // let ipnsData = (getStorageItem(config.ACCOUNT_IPNS + account, []) || []) as number[]; + // if (!ipnsData.length) { + // ipnsData = await fetchIPNS(account); + // setStorageItem(config.ACCOUNT_IPNS + account, ipnsData) + // } + + // dispatch(setIPNSData(ipnsData)); + + // // redirect page + // const name = await Name.from(new Uint8Array(ipnsData)); + // console.log("Reditect url: ", `/main/${name.toString()}`); + // dispatch(setLoading(false)); + // navigate(`/main/${name.toString()}`); + // })(); + // }, [account]); + + return ( + null + ); +}; + +export default RedirectPage; diff --git a/src/routes/index.ts b/src/routes/index.ts index 0224a59..a39ce38 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -1,4 +1,5 @@ import HomeLayout from 'pages/HomeLayout'; +import RedirectPage from 'pages/RedirectPage'; import Welcome from 'pages/Welcome'; const routes: any[] = [ @@ -7,7 +8,11 @@ const routes: any[] = [ component: Welcome, }, { - path: '/main/*', + path: '/main', + component: RedirectPage, + }, + { + path: '/main/:ipnsCid/*', component: HomeLayout, }, ]; diff --git a/src/slices/dbSlice.ts b/src/slices/dbSlice.ts index 3893485..f838251 100644 --- a/src/slices/dbSlice.ts +++ b/src/slices/dbSlice.ts @@ -3,6 +3,7 @@ import { convertTitle, getStorageItem } from "utils/helper"; import { Service, Web3Storage } from "web3.storage"; import config from "utils/config"; import { updateRss } from "utils/rss"; +import { addCIDtoIPNS, uploadData } from "utils/web3.storage"; const client = new Web3Storage({ token: config.REACT_APP_WEB3_STORAGE_API_TOKEN, @@ -10,36 +11,33 @@ const client = new Web3Storage({ const initialState = { Initialized: false, + ipnsData: [] as Number[], Blogs: [] as IBlog[], Users: {} as { [key: string]: IUser }, } as IDatabase; export const createBlog = createAsyncThunk( "createBlog", - async (blog: IBlog, { rejectWithValue }) => { + async (data: any, { rejectWithValue }) => { try { + const {blog, ipnsCid} = data; // upload blog body let title = "blogbody_" + convertTitle(blog.Title); - let blobBody = new File([blog.Body || ""], title, { type: "text/plain" }); - let cid = await client.put([blobBody], { - name: title, - maxRetries: 3, - wrapWithDirectory: false, - }); - blog.BodyCID = cid; + blog.BodyCID = await uploadData(client, title, blog.Body, "text/plain") || ""; blog.Body = undefined; // upload blog title = "blog_" + convertTitle(blog.Title); - let blob = new File([JSON.stringify(blog, null, 2)], title, { - type: "application/json", - }); - cid = await client.put([blob], { - name: title, - maxRetries: 3, - wrapWithDirectory: false, - }); - blog.CID = cid; + blog.Date = new Date(); + blog.CID = await uploadData(client, title, blog); + + // add updating to the ipns + const ipnsData = getStorageItem(config.IPNS_DATA + ipnsCid, []) || []; + if (ipnsData.length === 0) { + alert("ipnsData is empty"); + } + + await addCIDtoIPNS(ipnsData, blog.CID || ""); // update rss updateRss(); @@ -52,19 +50,20 @@ export const createBlog = createAsyncThunk( export const createUser = createAsyncThunk( "createUser", - async (user: IUser, { rejectWithValue }) => { + async (data: any, { rejectWithValue }) => { try { + const {user, ipnsCid} = data; const title = "user_" + convertTitle(user.Wallet); - let blob = new File([JSON.stringify(user, null, 2)], title, { - type: "application/json", - }); - const rootCid = await client.put([blob], { - name: title, - maxRetries: 3, - wrapWithDirectory: false, - }); + user.CID = await uploadData(client, title, user); + + // add updating to the ipns + const ipnsData = getStorageItem(config.IPNS_DATA + ipnsCid, []) || []; + if (ipnsData.length === 0) { + alert("ipnsData is empty"); + } + + await addCIDtoIPNS(ipnsData, user.CID || ""); - user.CID = rootCid; return user; } catch (err: any) { return rejectWithValue(err.response.data); @@ -74,13 +73,23 @@ export const createUser = createAsyncThunk( export const uploadImage = createAsyncThunk( "uploadImage", - async (file: File, { rejectWithValue }) => { + async (data: any, { rejectWithValue }) => { try { + const {file, ipnsCid} = data; const rootCid = await client.put([file], { name: "image_" + new Date().getTime().toString(), maxRetries: 3, wrapWithDirectory: false, }); + + // add updating to the ipns + const ipnsData = getStorageItem(config.IPNS_DATA + ipnsCid, []) || []; + if (ipnsData.length === 0) { + alert("ipnsData is empty"); + } + + await addCIDtoIPNS(ipnsData, rootCid || ""); + return rootCid; } catch (err: any) { return rejectWithValue(err.response.data); @@ -96,20 +105,24 @@ export const dbSlice = createSlice({ resetFromLocalStorage: (state: IDatabase, action: PayloadAction) => { // dispose individual action - const startNumber = state.Initialized ? action.payload.lastSyncNumber : 0; - const account = state.Initialized ? action.payload.account : ""; + console.log("resetFromLocalStorage payload: ", action.payload); + const startNumber = 0;//state.Initialized ? action.payload.lastSyncNumber : 0; + const nameString = action.payload.name; const blogs = startNumber > 0 ? state.Blogs : []; const users = state.Users; const lastSyncNumber = - getStorageItem(config.LAST_SYNC_NUMBER + account, 0) || 0; + getStorageItem(config.LAST_SYNC_NUMBER + nameString, 0) || 0; + console.log("Last synced number in resetFromLocalStorage: ", lastSyncNumber); + console.log("Last synced number title in resetFromLocalStorage: ", config.LAST_SYNC_NUMBER + nameString); for (let i = startNumber; i < lastSyncNumber; i++) { const data = - getStorageItem(config.LAST_SYNC_RECORD + account + i, {}) || {}; - if (!data.Date) { - continue; - } + getStorageItem(config.LAST_SYNC_RECORD + nameString + i, {}) || {}; + // if (!data.Date) { + // continue; + // } data.Date = new Date(data.Date); + console.log({data}); switch (data.Type) { case "ADD_BLOG": blogs.push(data); @@ -138,7 +151,7 @@ export const dbSlice = createSlice({ state.Blogs = blogs; state.Users = users; state.Initialized = true; - }, + } }, extraReducers: (builder) => { builder.addCase( diff --git a/src/utils/config.ts b/src/utils/config.ts index 80a41be..3b4fbdb 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -1,8 +1,10 @@ const config = { appID: 'IPP', REACT_APP_WEB3_STORAGE_API_TOKEN: process.env.REACT_APP_WEB3_STORAGE_API_TOKEN || "", + REACT_APP_WEB3_STORAGE_IPNS_API_TOKEN: process.env.REACT_APP_WEB3_STORAGE_IPNS_API_TOKEN || "", LAST_SYNC_NUMBER: "IPP_LAST_SYNC_NUMBER", LAST_SYNC_RECORD: "LAST_SYNC_RECORD_", + IPNS_DATA: "IPNS_DATA_", VERSION_LABEL: "VERSION", version: "#7", RSS_COUNT: 10 diff --git a/src/utils/helper.ts b/src/utils/helper.ts index 81c2a08..edf595e 100644 --- a/src/utils/helper.ts +++ b/src/utils/helper.ts @@ -46,14 +46,16 @@ export const decrypt = (ciphertext: any) => { export const setStorageItem = (key: string, data: any) => { localStorage.setItem( config.appID + "_" + key, - encryptJson(JSON.stringify(data)) + // encryptJson(JSON.stringify(data)) + JSON.stringify(data) ); }; export const getStorageItem = (key: string, defaultVal: any) => { try { return JSON.parse( - decryptJson(localStorage.getItem(config.appID + "_" + key) as string) + // decryptJson(localStorage.getItem(config.appID + "_" + key) as string) + localStorage.getItem(config.appID + "_" + key) as string ); } catch (e) { return defaultVal || false; @@ -107,9 +109,10 @@ export function convertTitle(str: string) { export function getW3link(cid: string) { // return `https://w3s.link/ipfs/${cid}`; - // return `https://${cid}.ipfs.w3s.link/`; + return `https://${cid}.ipfs.w3s.link/`; // return `https://braindance.mypinata.cloud/ipns/k51qzi5uqu5djrhlp3k6pdu4bsdoc548vshanx5ypadecaxxswwklx0qk4g86x`; - return `https://braindance.mypinata.cloud/ipfs/${cid}`; + // return `https://braindance.mypinata.cloud/ipfs/${cid}`; + // return `https://nftymeta.mypinata.cloud/ipfs/${cid}`; } export function sleep(ms: number) { diff --git a/src/utils/rss.ts b/src/utils/rss.ts index 87d1a2f..a4e0d7d 100644 --- a/src/utils/rss.ts +++ b/src/utils/rss.ts @@ -110,6 +110,7 @@ export async function updateRss() { // name: k51qzi5uqu5dg77idvtml88k2bc6scmi9aftri6nub8f7k73fjbiw8xau3xe46 // path: https://k51qzi5uqu5dg77idvtml88k2bc6scmi9aftri6nub8f7k73fjbiw8xau3xe46.ipns.dweb.link/ // https://k51qzi5uqu5dg77idvtml88k2bc6scmi9aftri6nub8f7k73fjbiw8xau3xe46.ipns.w3s.link/ + // https://k51qzi5uqu5div70qnyjfu5y0mwzcqs2fegxnq2fafo5oy145lwsb369mqyjir.ipns.w3s.link/ const bytes = [ 8, 1, 18, 64, 156, 104, 197, 108, 98, 238, 128, 116, 13, 126, 208, 222, 80, 194, 183, 170, 167, 73, 250, 124, 95, 102, 184, 215, 105, 153, 34, 130, 205, diff --git a/src/utils/web3.storage.ts b/src/utils/web3.storage.ts new file mode 100644 index 0000000..0f364a0 --- /dev/null +++ b/src/utils/web3.storage.ts @@ -0,0 +1,115 @@ +import { Service, Upload, Web3Storage } from "web3.storage"; +import api from "./api"; +import config from "./config"; +import { getW3link } from "./helper"; +import * as Name from 'w3name'; + +export async function fetchIPNS(account: string) { + const client = new Web3Storage({ + token: config.REACT_APP_WEB3_STORAGE_IPNS_API_TOKEN, + } as Service); + + const uploadObjects: Upload[] = []; + for await (const upload of client.list()) { + uploadObjects.push(upload); + } + + const uploadName = "wallet_" + account; + uploadObjects.sort((a, b) => (a.created > b.created ? 1 : -1)); + for (let i = 0; i < uploadObjects.length; i++) { + const upload = uploadObjects[i]; + if (upload.name.indexOf(uploadName) === 0) { + const contentRes = await api.get(getW3link(upload.cid)); + return contentRes.data as number[]; + } + } + + console.log("No name found"); + // wallet is not registered yet + const name = await Name.create(); + const value = 'bafkreiem4twkqzsq2aj4shbycd4yvoj2cx72vezicletlhi7dijjciqpui'; + const revision = await Name.v0(name, value); + await Name.publish(revision, name.key); + console.log("New name has been created: ", name.toString()); + + const bytes = Array.from(name.key.bytes) as number[]; + uploadData(client, uploadName + "_" + name.toString(), bytes); + return bytes; +} + +export async function fetchIPNSFromName(ipnsCid: string) { + const client = new Web3Storage({ + token: config.REACT_APP_WEB3_STORAGE_IPNS_API_TOKEN, + } as Service); + + const uploadObjects: Upload[] = []; + for await (const upload of client.list()) { + uploadObjects.push(upload); + } + + uploadObjects.sort((a, b) => (a.created > b.created ? 1 : -1)); + for (let i = 0; i < uploadObjects.length; i++) { + const upload = uploadObjects[i]; + if (upload.name.indexOf(ipnsCid) >= 0) { + const contentRes = await api.get(getW3link(upload.cid)); + return contentRes.data as number[]; + } + } + + return null; +} + +export async function uploadData(client: Web3Storage, title: string, data: any, type: string = "application/json") { + try { + const blob = new File([JSON.stringify(data, null, 2)], title, { + type, + }); + + const rootCid = await client.put([blob], { + name: title, + maxRetries: 3, + wrapWithDirectory: false, + }); + + return rootCid; + } catch (err: any) { + return undefined; + } +} + +export async function addCIDtoIPNS(ipnsData: number[], cid: string) { + const name = await Name.from(new Uint8Array(ipnsData)); + const revision = await Name.resolve(name); + + // let {data} = await api.get(getW3link(revision.value)); + let {data} = await api.get(`https://${revision.value}.ipfs.w3s.link/`); + if (Array.isArray(data)) { + data.push(cid); + } + else { + data = [cid]; + } + + const client = new Web3Storage({ + token: config.REACT_APP_WEB3_STORAGE_API_TOKEN, + } as Service); + const newCID = await uploadData(client, "ipns_" + cid, data); + + const nextRevision = await Name.increment(revision, newCID || ""); + await Name.publish(nextRevision, name.key); + + console.log("nextRevision value: ", nextRevision.value); +} + +export async function getCIDsFromIPNS(ipnsData: number[]) { + const name = await Name.from(new Uint8Array(ipnsData)); + const revision = await Name.resolve(name); + console.log("Latest revision value: ", revision.value); + // let {data} = await api.get(getW3link(revision.value)); + let {data} = await api.get(`https://${revision.value}.ipfs.w3s.link/`); + return Array.isArray(data) ? data : []; + + // const name = await Name.from(new Uint8Array(ipnsData)); + // let {data} = await api.get(`https://${name.toString()}.ipns.w3s.link/`); + // return Array.isArray(data) ? data : []; +}