new post - dnsimg
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 2m34s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 2m34s
This commit is contained in:
parent
f5c2085fe1
commit
f1ff991010
BIN
public/img1.png
Normal file
BIN
public/img1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 43 KiB |
BIN
public/img2.png
Normal file
BIN
public/img2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 390 KiB |
BIN
public/img3.png
Normal file
BIN
public/img3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 758 KiB |
BIN
public/img4.jpg
Normal file
BIN
public/img4.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
BIN
public/img5.png
Normal file
BIN
public/img5.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 634 KiB |
BIN
public/img6.png
Normal file
BIN
public/img6.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 37 KiB |
@ -40,7 +40,7 @@ export default function ExamplePost() {
|
|||||||
document.head.appendChild(metaOgDesc);
|
document.head.appendChild(metaOgDesc);
|
||||||
}
|
}
|
||||||
metaOgDesc.setAttribute('content', description);
|
metaOgDesc.setAttribute('content', description);
|
||||||
}, []);
|
}, [pageTitle]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageTransition>
|
<PageTransition>
|
||||||
|
34
src/app/blog/posts/2/accuracy.json
Normal file
34
src/app/blog/posts/2/accuracy.json
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"data": {"url": "./scatter.csv"},
|
||||||
|
"transform": [
|
||||||
|
{
|
||||||
|
"calculate": "1 - abs(datum.y - 3.141592653589793) / 3.141592653589793",
|
||||||
|
"as": "closeness_to_pi"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"mark": {"type": "point", "size": 30},
|
||||||
|
"width": 300,
|
||||||
|
"height": 300,
|
||||||
|
"encoding": {
|
||||||
|
"x": {
|
||||||
|
"field": "log10_x",
|
||||||
|
"title": "Iterations",
|
||||||
|
"axis": {
|
||||||
|
"labelOverlap": "greedy",
|
||||||
|
"labelExpr": "'10^' + datum.value"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"field": "closeness_to_pi",
|
||||||
|
"title": "Accuracy (% close to π)",
|
||||||
|
"scale": {
|
||||||
|
"zero": false,
|
||||||
|
"reverse": true
|
||||||
|
},
|
||||||
|
"axis": {
|
||||||
|
"labelOverlap": "greedy",
|
||||||
|
"format": ".1%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
src/app/blog/posts/2/metadata.ts
Normal file
23
src/app/blog/posts/2/metadata.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { Metadata } from "next";
|
||||||
|
|
||||||
|
// Post content
|
||||||
|
export const title = "dnsimg - storing images in txt records";
|
||||||
|
export const description = "I was intrigued by the idea of storing images in DNS records, and I wanted to test out how effectively images could be stored in DNS records. I've always been interested in TXT records because they seem to be a useful way of storing arbitrary data, and in this blog post I'll discuss how I went from an idea to developing the project into almost a protocol sort of method for storing an image on a domain name.";
|
||||||
|
|
||||||
|
// Next.js metadata
|
||||||
|
export const generateMetadata = (): Metadata => {
|
||||||
|
return {
|
||||||
|
title: title,
|
||||||
|
description: description,
|
||||||
|
openGraph: {
|
||||||
|
title: title,
|
||||||
|
description: description,
|
||||||
|
type: 'article',
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
card: 'summary',
|
||||||
|
title: title,
|
||||||
|
description: description,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
244
src/app/blog/posts/2/page.tsx
Normal file
244
src/app/blog/posts/2/page.tsx
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
"use client"
|
||||||
|
import { title, description } from './metadata';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { PageTransition } from '@/components/PageTransition';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import BackButton from '@/components/BackButton';
|
||||||
|
import Image from 'next/image';
|
||||||
|
|
||||||
|
import CodeBlock from '../../../../components/code'
|
||||||
|
|
||||||
|
// Note: Static metadata is also generated by the generateMetadata export in metadata.ts
|
||||||
|
// This useEffect hook ensures the title is properly set on the client side
|
||||||
|
|
||||||
|
export default function ExamplePost() {
|
||||||
|
const pageTitle = `${title} | Asher Falcon`;
|
||||||
|
|
||||||
|
// Update the title and metadata on the client side
|
||||||
|
useEffect(() => {
|
||||||
|
document.title = pageTitle;
|
||||||
|
|
||||||
|
// Update meta tags
|
||||||
|
const metaDescription = document.querySelector('meta[name="description"]');
|
||||||
|
if (metaDescription) {
|
||||||
|
metaDescription.setAttribute('content', description);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update Open Graph tags
|
||||||
|
let metaOgTitle = document.querySelector('meta[property="og:title"]');
|
||||||
|
if (!metaOgTitle) {
|
||||||
|
metaOgTitle = document.createElement('meta');
|
||||||
|
metaOgTitle.setAttribute('property', 'og:title');
|
||||||
|
document.head.appendChild(metaOgTitle);
|
||||||
|
}
|
||||||
|
metaOgTitle.setAttribute('content', title);
|
||||||
|
|
||||||
|
let metaOgDesc = document.querySelector('meta[property="og:description"]');
|
||||||
|
if (!metaOgDesc) {
|
||||||
|
metaOgDesc = document.createElement('meta');
|
||||||
|
metaOgDesc.setAttribute('property', 'og:description');
|
||||||
|
document.head.appendChild(metaOgDesc);
|
||||||
|
}
|
||||||
|
metaOgDesc.setAttribute('content', description);
|
||||||
|
}, [pageTitle]);
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageTransition>
|
||||||
|
<div className='pb-[20px]'>
|
||||||
|
<div style={{paddingTop: "50px"}} className="relative flex justify-center items-center h-[50px]">
|
||||||
|
<div className="absolute left-0 flex items-center h-full ml-6">
|
||||||
|
<BackButton />
|
||||||
|
</div>
|
||||||
|
<span className="text-sm sm:text-base md:text-xl lg:text-2xl truncate max-w-[80%]">{title}</span>
|
||||||
|
</div>
|
||||||
|
<div style={{paddingTop: "30px"}} className='flex justify-center items-center md:w-[70%] xl:w-[50%] w-[85%] mx-auto'>
|
||||||
|
<div>
|
||||||
|
<p>{description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style={{paddingTop: "30px",paddingBottom: "30px"}} className='flex flex-col justify-center items-center md:w-[70%] xl:w-[50%] w-[85%] mx-auto'>
|
||||||
|
<p>So, an image inside DNS? How can it be done? Well the most obvious way and the method I tried here was storing the data inside TXT records. Firstly, we need to find a way to store the image inside the dns records. At first the method I tried was simply getting the hex characters of the data. We get this using the command below: </p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CodeBlock language="bash" code={`xxd -p output.jpg > output.txt`} />
|
||||||
|
|
||||||
|
<div style={{paddingTop: "30px",paddingBottom: "30px"}} className='flex flex-col justify-center items-center md:w-[70%] xl:w-[50%] w-[85%] mx-auto'>
|
||||||
|
<p>This will not be as efficient as storing the data in base64 format, as this will use 2x the space where base64 would only use 1.33x the file size, however for testing I believe it is fine to use for now.</p>
|
||||||
|
<p>The next hurdle is as you can see below, when I tried to just add all the hex data in one txt record, cloudflare shows us an errror:</p>
|
||||||
|
</div>
|
||||||
|
<div style={{paddingTop: "30px",paddingBottom: "30px"}} className='flex flex-col justify-center items-center md:w-[70%] xl:w-[50%] w-[85%] mx-auto'>
|
||||||
|
<Image src="/img1.png" alt="output" width={600} height={400} />
|
||||||
|
</div>
|
||||||
|
<div style={{paddingTop: "30px",paddingBottom: "30px"}} className='flex flex-col justify-center items-center md:w-[70%] xl:w-[50%] w-[85%] mx-auto'>
|
||||||
|
<p>So, we need to split our hex data into 2048 character chunks. A simple python script can be written to do this and found below:</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CodeBlock language="python" code={`image = open("output.txt", "r").read()
|
||||||
|
image = image.replace("\\n", "")
|
||||||
|
chunks = []
|
||||||
|
|
||||||
|
total = int(len(image)/2048)+1
|
||||||
|
|
||||||
|
for i in range(total):
|
||||||
|
chunk = image[i*2048:(i+1)*2048]
|
||||||
|
print(f"Chunk #{i+1}, size: {len(chunk)}")
|
||||||
|
chunks.append(chunk)
|
||||||
|
|
||||||
|
domain = "asherfalcon.com"
|
||||||
|
|
||||||
|
with open(f"{domain}.txt", "a") as dns:
|
||||||
|
for chunkIndex in range(len(chunks)):
|
||||||
|
dns.write(f"dnsimg-{chunkIndex+1}.{domain}. 60 IN TXT \"{chunks[chunkIndex]}\"\n")
|
||||||
|
dns.write(f"dnsimg-count.{domain}. 60 IN TXT \"{len(chunks)}\"\n")`} />
|
||||||
|
|
||||||
|
<div style={{paddingTop: "30px",paddingBottom: "30px"}} className='flex flex-col justify-center items-center md:w-[70%] xl:w-[50%] w-[85%] mx-auto'>
|
||||||
|
<p>This will create a txt record for each chunk of the image, and a 'dnsimg-count' record for the total number of chunks. The count is neccesary so that when we want to load the iamge, we know how many chunks exist and how many we need to request. </p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div style={{paddingTop: "30px",paddingBottom: "30px"}} className='flex flex-col justify-center items-center md:w-[70%] xl:w-[50%] w-[85%] mx-auto'>
|
||||||
|
<p>We can then upload the dns file to cloudflare and import it, which will create all the records for us. After a few minutes using the dig command we can see that the chunks have been stored. Cloudflare splits them up into additional chunks per record but that is not an issue as we can just concatenate them.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{paddingTop: "30px",paddingBottom: "30px"}} className='flex flex-col justify-center items-center md:w-[70%] xl:w-[50%] w-[85%] mx-auto'>
|
||||||
|
<Image src="/img2.png" alt="output" width={600} height={400} />
|
||||||
|
<Image className='mt-4' src="/img3.png" alt="output" width={600} height={400} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{paddingTop: "30px",paddingBottom: "30px"}} className='flex flex-col justify-center items-center md:w-[70%] xl:w-[50%] w-[85%] mx-auto'>
|
||||||
|
<p>Now we know that our data is out there, lets try rebuild the image from the dns records. We can write another simple python script to fetch them using dig asynchronously and then concatenate them into a single file, in jpg format. See the script below:</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CodeBlock language="python" code={`import subprocess
|
||||||
|
import threading
|
||||||
|
import sys
|
||||||
|
|
||||||
|
class bcolors:
|
||||||
|
HEADER = '\\\\033[95m'
|
||||||
|
OKBLUE = '\\\\033[94m'
|
||||||
|
OKCYAN = '\\\\033[96m'
|
||||||
|
OKGREEN = '\\\\033[92m'
|
||||||
|
WARNING = '\\\\033[93m'
|
||||||
|
FAIL = '\\\\033[91m'
|
||||||
|
ENDC = '\\\\033[0m'
|
||||||
|
BOLD = '\\\\033[1m'
|
||||||
|
UNDERLINE = '\\\\033[4m'
|
||||||
|
|
||||||
|
# Replace with your domain
|
||||||
|
domain = "containerback.com"
|
||||||
|
|
||||||
|
# Run the dig command
|
||||||
|
result = subprocess.run(
|
||||||
|
["dig", "@8.8.8.8", "+short", f"dnsimg-count.{domain}", "TXT"],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
text=True
|
||||||
|
)
|
||||||
|
|
||||||
|
chunks = []
|
||||||
|
|
||||||
|
def printStatus():
|
||||||
|
# "\\\\033[F"+
|
||||||
|
msg = bcolors.OKBLUE+"["
|
||||||
|
for i in chunks:
|
||||||
|
if(i==""):
|
||||||
|
msg+=bcolors.FAIL+"#"
|
||||||
|
else:
|
||||||
|
msg+=bcolors.OKGREEN+"#"
|
||||||
|
msg+=bcolors.OKBLUE+"]"
|
||||||
|
print(msg)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def getChunk(chunkIndex):
|
||||||
|
chunk = subprocess.run(
|
||||||
|
["dig", "+short", f"dnsimg-{chunkIndex+1}.{domain}", "TXT"],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
text=True
|
||||||
|
)
|
||||||
|
if(chunk.stdout!=""):
|
||||||
|
chunkData = chunk.stdout.replace(" ","").replace("\\\\\\"","").replace("\\\\\\n","")
|
||||||
|
chunks[chunkIndex] = chunkData
|
||||||
|
else:
|
||||||
|
print(f"Err {chunkIndex} {chunk.stderr} '{chunk.stdout}'")
|
||||||
|
# printStatus()
|
||||||
|
# print(f"Added chunk #{chunkIndex+1} ({len(chunkData)} chars)")
|
||||||
|
|
||||||
|
if(result.stdout == ""):
|
||||||
|
print("No dnsimg found")
|
||||||
|
else:
|
||||||
|
size = int(result.stdout[1:-2])
|
||||||
|
print(f"Found dnsimg with {size} chunks")
|
||||||
|
|
||||||
|
chunks = [""]*size
|
||||||
|
|
||||||
|
threads = []
|
||||||
|
|
||||||
|
for chunkIndex in range(size):
|
||||||
|
threads.append(threading.Thread(target=getChunk, args=(chunkIndex,)))
|
||||||
|
|
||||||
|
for t in threads:
|
||||||
|
t.start()
|
||||||
|
|
||||||
|
for t in threads:
|
||||||
|
t.join()
|
||||||
|
printStatus()
|
||||||
|
|
||||||
|
printStatus()
|
||||||
|
with open("dnsimg.jpg", "wb") as output:
|
||||||
|
output.write(bytes.fromhex("".join(chunks)))`} />
|
||||||
|
|
||||||
|
<div style={{paddingTop: "30px",paddingBottom: "30px"}} className='flex flex-col justify-center items-center md:w-[70%] xl:w-[50%] w-[85%] mx-auto'>
|
||||||
|
<p>When I first tried this I don't think the records had properly propagated, so I had to wait a few minutes before I could see the image. Look below to see the (slightly) corrupted image created when a few records were missing:</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{paddingTop: "30px",paddingBottom: "30px"}} className='flex flex-col justify-center items-center md:w-[70%] xl:w-[50%] w-[85%] mx-auto'>
|
||||||
|
<Image src="/img4.jpg" alt="output" width={600} height={400} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{paddingTop: "30px",paddingBottom: "30px"}} className='flex flex-col justify-center items-center md:w-[70%] xl:w-[50%] w-[85%] mx-auto'>
|
||||||
|
<p>After waiting another 10 or so minutes, we can run it again and get the full image through! The image is stored in 21 chunks of 2048 characters, and is not a terribly high resolution but it serves as a good first proof of concept:</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{paddingTop: "30px",paddingBottom: "30px"}} className='flex flex-col justify-center items-center md:w-[70%] xl:w-[50%] w-[85%] mx-auto'>
|
||||||
|
<Image src="/img5.png" alt="output" width={600} height={400} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{paddingTop: "30px",paddingBottom: "30px"}} className='flex flex-col justify-center items-center md:w-[70%] xl:w-[50%] w-[85%] mx-auto'>
|
||||||
|
<p>Next I wanted to try some larger images, which mostly worked but I found an upper bound when I tried a (over 1MB) image. Not sure if this is a cloudflare limit or a wider rule but heres the error I got:</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{paddingTop: "30px",paddingBottom: "30px"}} className='flex flex-col justify-center items-center md:w-[70%] xl:w-[50%] w-[85%] mx-auto'>
|
||||||
|
<Image src="/img6.png" alt="output" width={600} height={200} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{paddingTop: "30px",paddingBottom: "30px"}} className='flex flex-col justify-center items-center md:w-[70%] xl:w-[50%] w-[85%] mx-auto'>
|
||||||
|
<p>So, finally I created a lovely web tool you can try out <Link className='text-blue-500' href="https://dnsimg.asherfalcon.com" target="_blank">here</Link> which allows you to type a domain and load its image. I created images on the domains 'asherfalcon.com' and 'containerback.com' but you should try add images to your own domains! You can use a domain or any subdomain and use the scripts in the repository <Link className='text-blue-500' href="https://git.asherfalcon.com/asher/image_over_dns" target="_blank">here</Link> to create your own image. If you want to see a video of the web tool in action see below:</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{paddingTop: "30px",paddingBottom: "30px"}} className='flex flex-col justify-center items-center md:w-[70%] xl:w-[50%] w-[85%] mx-auto'>
|
||||||
|
<iframe
|
||||||
|
width="560"
|
||||||
|
height="315"
|
||||||
|
src="https://www.youtube.com/embed/5BO38soTjjA?start=64"
|
||||||
|
title="YouTube video player"
|
||||||
|
frameBorder="0"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||||
|
allowFullScreen
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{paddingTop: "30px",paddingBottom: "30px"}} className='flex flex-col justify-center items-center md:w-[70%] xl:w-[50%] w-[85%] mx-auto'>
|
||||||
|
<p>I hope you enjoyed this blog post! If you have any questions or comments, please feel free to reach out to me <Link href="/contact" className="text-blue-500">here</Link></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</PageTransition>
|
||||||
|
);
|
||||||
|
}
|
87
src/app/blog/posts/2/redeploy.ts
Normal file
87
src/app/blog/posts/2/redeploy.ts
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
export const charm = `#!/bin/bash
|
||||||
|
message=$(gum input --placeholder "Commit message")
|
||||||
|
git add .
|
||||||
|
git commit -m "\${message}"
|
||||||
|
`
|
||||||
|
|
||||||
|
export const basicScript = `#!/bin/bash
|
||||||
|
git add .
|
||||||
|
git commit -m "updated docker-compose.yml"
|
||||||
|
git push
|
||||||
|
|
||||||
|
ssh -o "StrictHostKeyChecking=no" -i ssh.key $CD_USER@$CD_HOST << EOF
|
||||||
|
sudo su
|
||||||
|
cd $CD_PATH
|
||||||
|
docker compose down
|
||||||
|
git pull
|
||||||
|
docker compose up -d
|
||||||
|
exit
|
||||||
|
EOF
|
||||||
|
`
|
||||||
|
|
||||||
|
export const script = `#!/bin/bash
|
||||||
|
|
||||||
|
# A script to redeploy this docker infrastructure using ssh
|
||||||
|
|
||||||
|
if [ -f .env ]; then
|
||||||
|
source .env
|
||||||
|
else
|
||||||
|
echo ".env file not found. Please create it and set the necessary environment variables."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
check_gum() {
|
||||||
|
if command -v gum &> /dev/null; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo "Gum not found, installing..."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
install_gum() {
|
||||||
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
/bin/bash -c "$(curl -fsSL
|
||||||
|
https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||||
|
brew install gum
|
||||||
|
elif [[ -f /etc/os-release ]]; then
|
||||||
|
source /etc/os-release
|
||||||
|
if [[ $ID == "ubuntu" ]] || [[ $ID == "debian" ]]; then
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y gum
|
||||||
|
elif [[ $ID == "fedora" ]] || [[ $ID == "centos" ]] || [[ $ID == "rhel" ]]; then
|
||||||
|
sudo dnf install -y gum
|
||||||
|
else
|
||||||
|
echo "Unsupported Linux distribution. Please install Gum
|
||||||
|
manually."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Unsupported operating system. Please install Gum manually."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main script execution
|
||||||
|
if ! check_gum; then
|
||||||
|
if ! install_gum; then
|
||||||
|
echo "Failed to install Gum. Please install it manually and run
|
||||||
|
the script again."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
message=$(gum input --placeholder "Commit message")
|
||||||
|
|
||||||
|
git add .
|
||||||
|
git commit -m "\${message}"
|
||||||
|
git push
|
||||||
|
|
||||||
|
ssh -o "StrictHostKeyChecking=no" -i ssh.key $CD_USER@$CD_HOST << EOF
|
||||||
|
sudo su
|
||||||
|
cd $CD_PATH
|
||||||
|
docker compose down
|
||||||
|
git pull
|
||||||
|
docker compose up -d
|
||||||
|
exit
|
||||||
|
EOF`
|
@ -1,49 +1,13 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { IoMdArrowBack } from "react-icons/io";
|
import { IoMdArrowBack } from "react-icons/io";
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
export default function BackButton() {
|
export default function BackButton() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [pageVisits, setPageVisits] = useState<string[]>([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Get page history from session storage
|
|
||||||
const storedHistory = sessionStorage.getItem('pageHistory');
|
|
||||||
const history = storedHistory ? JSON.parse(storedHistory) : [];
|
|
||||||
|
|
||||||
// Add current page to history if it's not the most recent
|
|
||||||
const currentPath = window.location.pathname;
|
|
||||||
if (history.length === 0 || history[history.length - 1] !== currentPath) {
|
|
||||||
const newHistory = [...history, currentPath];
|
|
||||||
sessionStorage.setItem('pageHistory', JSON.stringify(newHistory));
|
|
||||||
setPageVisits(newHistory);
|
|
||||||
} else {
|
|
||||||
setPageVisits(history);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
// If we have at least 2 pages in history (current + previous)
|
router.back();
|
||||||
if (pageVisits.length > 1) {
|
|
||||||
// Remove current page from history
|
|
||||||
const newHistory = [...pageVisits];
|
|
||||||
newHistory.pop();
|
|
||||||
sessionStorage.setItem('pageHistory', JSON.stringify(newHistory));
|
|
||||||
|
|
||||||
// Go to previous page in our history
|
|
||||||
router.push(newHistory[newHistory.length - 1]);
|
|
||||||
} else {
|
|
||||||
// If no internal history, go to home page first
|
|
||||||
const currentPath = window.location.pathname;
|
|
||||||
if (currentPath !== '/') {
|
|
||||||
router.push('/');
|
|
||||||
} else {
|
|
||||||
// If already on home page, then go back normally
|
|
||||||
window.history.back();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
Loading…
x
Reference in New Issue
Block a user