This commit is contained in:
bobbydigitales
2024-09-13 22:57:30 -07:00
parent 9140739879
commit f0d073bab1
30 changed files with 2091 additions and 765 deletions

35
app.webmanifest Normal file
View File

@@ -0,0 +1,35 @@
{
"name": "Dandelion",
"short_name": "ddln",
"start_url": "/",
"display": "standalone",
"display_override": ["window-controls-overlay","standalone"],
"id": "b1dbe643-36fc-4419-9448-80f32a1baa1a",
"background_color": "#000000",
"theme_color": "#000000",
"icons": [
{
"src": "icons/dandelion_512x512.png",
"type": "image/png",
"sizes":"512x512"
}
],
"screenshots" : [
{
"src": "images/screenshot1.jpg",
"sizes": "1280x720",
"type": "image/png",
"form_factor": "wide",
"label": "Dandelion desktop"
},
{
"src": "images/screenshot2.jpg",
"sizes": "720x1280",
"type": "image/png",
"form_factor": "narrow",
"label": "Dandelion mobile"
}
]
}

71
db.js
View File

@@ -3,12 +3,11 @@
// name: string; // name: string;
// email: string; // email: string;
// } // }
const dbName = "ddln"; const postStoreName = "posts";
const storeNameBase = "posts";
let keyBase = "dandelion_posts_v1_"; let keyBase = "dandelion_posts_v1_";
let key = ""; let key = "";
export function openDatabase(userID) { export function openDatabase(userID) {
const storeName = `${storeNameBase}_${userID}`; const dbName = `user_${userID}`;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const request = indexedDB.open(dbName, 1); const request = indexedDB.open(dbName, 1);
request.onerror = (event) => { request.onerror = (event) => {
@@ -18,8 +17,8 @@ export function openDatabase(userID) {
}; };
request.onupgradeneeded = (event) => { request.onupgradeneeded = (event) => {
const db = event.target.result; const db = event.target.result;
if (!db.objectStoreNames.contains(storeName)) { if (!db.objectStoreNames.contains(postStoreName)) {
let store = db.createObjectStore(storeName, { keyPath: "id", autoIncrement: true }); let store = db.createObjectStore(postStoreName, { keyPath: "id", autoIncrement: true });
store.createIndex("datetimeIndex", "post_timestamp", { unique: false }); store.createIndex("datetimeIndex", "post_timestamp", { unique: false });
store.createIndex("postIDIndex", "data.post_id", { unique: true }); store.createIndex("postIDIndex", "data.post_id", { unique: true });
} }
@@ -32,10 +31,9 @@ export function openDatabase(userID) {
} }
export async function addData(userID, data) { export async function addData(userID, data) {
try { try {
const storeName = `${storeNameBase}_${userID}`;
const db = await openDatabase(userID); const db = await openDatabase(userID);
const transaction = db.transaction(storeName, "readwrite"); const transaction = db.transaction(postStoreName, "readwrite");
const store = transaction.objectStore(storeName); const store = transaction.objectStore(postStoreName);
const addRequest = store.add({ post_timestamp: data.post_timestamp, data: data }); const addRequest = store.add({ post_timestamp: data.post_timestamp, data: data });
addRequest.onsuccess = (e) => { addRequest.onsuccess = (e) => {
// console.log('Data has been added:', (e.target as IDBRequest).result); // console.log('Data has been added:', (e.target as IDBRequest).result);
@@ -50,12 +48,53 @@ export async function addData(userID, data) {
console.error('Error in opening database:', error); console.error('Error in opening database:', error);
} }
} }
export async function deleteData(userID, postID) {
try {
const db = await openDatabase(userID);
const transaction = db.transaction(postStoreName, "readwrite");
const store = transaction.objectStore(postStoreName);
const index = store.index("postIDIndex");
const getRequest = index.getKey(postID);
getRequest.onerror = e => console.log(e.target.error);
getRequest.onsuccess = e => {
const key = e.target.result;
if (key === undefined) {
console.error("Post not found");
return null;
}
const deleteRequest = store.delete(key);
deleteRequest.onerror = e => { console.error(e.target.error); return false; };
deleteRequest.onsuccess = () => true;
};
}
catch (error) {
console.error('Error in opening database:', error);
}
}
export async function clearData(userID) {
try {
const db = await openDatabase(userID);
const transaction = db.transaction(postStoreName, "readwrite");
const store = transaction.objectStore(postStoreName);
const clearRequest = store.clear();
clearRequest.onsuccess = (e) => {
// console.log('Data has been added:', (e.target as IDBRequest).result);
};
clearRequest.onerror = (event) => {
// Use a type assertion to access the specific properties of IDBRequest error event
const errorEvent = event;
console.error('Error in clearing data:', errorEvent.target.error?.message);
};
}
catch (error) {
console.error('Error in opening database:', error);
}
}
export async function addDataArray(userID, array) { export async function addDataArray(userID, array) {
try { try {
const storeName = `${storeNameBase}_${userID}`;
const db = await openDatabase(userID); const db = await openDatabase(userID);
const transaction = db.transaction(storeName, "readwrite"); const transaction = db.transaction(postStoreName, "readwrite");
const store = transaction.objectStore(storeName); const store = transaction.objectStore(postStoreName);
let count = 0; let count = 0;
array.reverse(); array.reverse();
for (let data of array) { for (let data of array) {
@@ -79,10 +118,9 @@ export async function addDataArray(userID, array) {
} }
} }
export async function getData(userID, lowerID, upperID) { export async function getData(userID, lowerID, upperID) {
const storeName = `${storeNameBase}_${userID}`;
const db = await openDatabase(userID); const db = await openDatabase(userID);
const transaction = db.transaction(storeName, "readonly"); const transaction = db.transaction(postStoreName, "readonly");
const store = transaction.objectStore(storeName); const store = transaction.objectStore(postStoreName);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const keyRangeValue = IDBKeyRange.bound(lowerID, upperID); const keyRangeValue = IDBKeyRange.bound(lowerID, upperID);
const records = []; const records = [];
@@ -108,10 +146,9 @@ export async function getData(userID, lowerID, upperID) {
}); });
} }
export async function getAllData(userID) { export async function getAllData(userID) {
const storeName = `${storeNameBase}_${userID}`;
const db = await openDatabase(userID); const db = await openDatabase(userID);
const transaction = db.transaction(storeName, "readonly"); const transaction = db.transaction(postStoreName, "readonly");
const store = transaction.objectStore(storeName); const store = transaction.objectStore(postStoreName);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const getRequest = store.getAll(); const getRequest = store.getAll();
getRequest.onsuccess = () => { getRequest.onsuccess = () => {

File diff suppressed because one or more lines are too long

8
go.mod
View File

@@ -21,6 +21,14 @@ require (
) )
require ( require (
github.com/pion/stun v0.6.1
github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect
golang.org/x/text v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect
) )
require (
github.com/andybalholm/brotli v1.1.0
github.com/pion/dtls/v2 v2.2.7 // indirect
github.com/pion/logging v0.2.2 // indirect
github.com/pion/transport/v2 v2.2.1 // indirect
)

52
go.sum
View File

@@ -1,3 +1,5 @@
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -12,29 +14,79 @@ github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZH
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4=
github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8=
github.com/pion/transport/v2 v2.2.1 h1:7qYnCBlpgSJNYMbLCKuSY9KbQdBFoETvPNETv0y4N7c=
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/quic-go v0.42.0 h1:uSfdap0eveIl8KXnipv9K7nlwZ5IqLlYOpJ58u5utpM= github.com/quic-go/quic-go v0.42.0 h1:uSfdap0eveIl8KXnipv9K7nlwZ5IqLlYOpJ58u5utpM=
github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M= github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

BIN
icons/dandelion_512x512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

1
images/lottie_test.json Normal file

File diff suppressed because one or more lines are too long

BIN
images/screenshot1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

BIN
images/screenshot1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

BIN
images/screenshot2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

BIN
images/screenshot2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

View File

@@ -7,8 +7,13 @@
<title>Dandelion</title> <title>Dandelion</title>
<script type="module" src="main.js"></script> <script type="module" src="main.js"></script>
<script src="marked.min.js"></script> <script src="lib/marked.min.js"></script>
<script src="lib/qrcode.min.js"></script>
<!-- <script src="lib/lottie.min.js"></script> -->
<!-- <script src="https://unpkg.com/@dotlottie/player-component@latest/dist/dotlottie-player.mjs" type="module"></script> -->
<!-- <script src="/lib/webtorrent/webtorrent_1_8_0.min.js"></script> -->
<link rel="manifest" href="/app.webmanifest">
<style> <style>
@font-face { @font-face {
font-family: 'Virgil'; font-family: 'Virgil';
@@ -40,7 +45,7 @@
} }
hr { hr {
border-color: rgb(128, 128, 128); border-color: rgb(60, 60, 60);
} }
#textarea_post { #textarea_post {
@@ -52,15 +57,17 @@
/* Make the textarea take up 100% of the body's width */ /* Make the textarea take up 100% of the body's width */
box-sizing: border-box; box-sizing: border-box;
/* Include padding and border in the element's total width and height */ /* Include padding and border in the element's total width and height */
padding: 10px; padding-left: 30px;
/* Optional padding for the inside of the textarea */ padding-right: 30px;
border: 1px solid rgb(132, 136, 138); border: 1px solid rgb(132, 136, 138);
/* Optional border for the textarea */
resize: vertical; resize: vertical;
/* Allows the textarea to be resized vertically only */ /* Allows the textarea to be resized vertically only */
border-radius: 40px;
} }
.header {
font-size: 18px;
}
.flex-container { .flex-container {
display: flex; display: flex;
@@ -72,14 +79,14 @@
} }
.content { .content {
max-width: 800px; max-width: 600px;
/* Your preferred max width for the content */ /* Your preferred max width for the content */
flex: 1; flex: 1;
/* Shorthand for flex-grow, flex-shrink and flex-basis */ /* Shorthand for flex-grow, flex-shrink and flex-basis */
min-width: 300px; min-width: 300px;
/* Minimum width the content can shrink to */ /* Minimum width the content can shrink to */
padding: 20px; padding: 20px;
box-shadow: 0 0 10px rgb(60, 60, 60); box-shadow: 0 0 5px rgb(60, 60, 60);
text-align: left; text-align: left;
overflow-x: hidden; overflow-x: hidden;
/* Hide horizontal overflow inside the flex container */ /* Hide horizontal overflow inside the flex container */
@@ -94,25 +101,64 @@
} }
.postImage { .postImage {
width:100%; width: 100%;
} }
#log { #log {
font-family: monospace; font-family: monospace;
text-wrap: nowrap; text-wrap: nowrap;
font-size: 10px;
margin-bottom: 20px;
height: 150px;
width: 50%;
} }
.button { .button {
text-align: right; text-align: right;
} }
#buttons {
margin-left: 40px;
}
#button_post {
margin-right:40px;
}
a { a {
color: rgb(29, 155, 240); color: rgb(29, 155, 240);
} }
#log { .logo {
margin-bottom: 20px; width: 32px;
height:150px; height: 32px;
image-rendering: pixelated;
}
#torrent-content {
border:solid 1px;
width:800px;
}
.img-button {
cursor: pointer;
}
button {
background-color: rgb(0, 0, 0);
border-radius: 10px;
padding-left:10px;
padding-right:10px;
padding-top:5px;
padding-bottom:5px;
margin-left:5px;
color:rgb(255,255,255);
/* border:solid 1px white; */
border: 1px solid rgb(132, 136, 138);
color: rgb(202, 208, 211);
cursor: pointer;
} }
</style> </style>
@@ -124,15 +170,29 @@
<div class="flex-container"> <div class="flex-container">
<div class="content"> <div class="content">
<div class="img-button" id="ddln-logo-button"><img class="logo" src="favicon.ico"></div>
<div id="status"></div> <div id="status"></div>
<div id="log"></div>
<input type="button" value="font1" id="button_font1" /> <div id="info" style="display:none">
<input type="button" value="font2" id="button_font2" /> <div id="log" ></div>
<div id="qrcode"></div>
<textarea cols="60" rows="8" id="textarea_post"></textarea> <!-- <dotlottie-player src="https://lottie.host/272b60dd-462d-42a3-8ed6-fec4143633d6/X4FxBascRI.json" background="transparent" speed="1" style="width: 300px; height: 300px" direction="1" playMode="normal" loop controls autoplay></dotlottie-player> -->
<div class="button">
<input type="button" value="post" id="button_post" />
</div> </div>
<!-- <div id="peer_display"><canvas></canvas></div> -->
<div id="buttons">
<button id="button_font1" >font1</button>
<button id="button_font2" >font2 </button>
<button id="import_tweets" >import</button>
<button id="clear_posts" >clear </button>
<button id="update_app" >check for updates</button>
</div>
<textarea cols="60" rows="6" id="textarea_post"></textarea>
<div class="button">
<button id="button_post" >post</button>
</div>
<!-- <div id="torrent-content"></div> -->
<div id="content"></div> <div id="content"></div>
</div> </div>
</div> </div>

1
lib/lottie.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

1
lib/qrcode.min.js vendored Normal file

File diff suppressed because one or more lines are too long

16
lib/webtorrent/webtorrent_1_8_0.min.js vendored Normal file

File diff suppressed because one or more lines are too long

BIN
main

Binary file not shown.

163
main.go
View File

@@ -34,7 +34,8 @@
// This will allow distributed content delivery but put a memory and bendwidth strain on the // This will allow distributed content delivery but put a memory and bendwidth strain on the
// bootstrap sever. Look into Web Transport for the raspberry pi overhead. Could buy a few more RPIs // bootstrap sever. Look into Web Transport for the raspberry pi overhead. Could buy a few more RPIs
// and make a little cluster // and make a little cluster
// Domain name so we can get a certificate and serve HTTPS / HTTP3
// ✅ Domain name so we can get a certificate and serve HTTPS / HTTP3
// Think about compiling Typescript on initial access and caching the JS in a service worker // Think about compiling Typescript on initial access and caching the JS in a service worker
// so you don't need a build system to change things. // so you don't need a build system to change things.
@@ -43,23 +44,59 @@
package main package main
import ( import (
"encoding/json"
"fmt"
"io"
"log" "log"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings"
"github.com/andybalholm/brotli"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/quic-go/quic-go/http3"
) )
var upgrader = websocket.Upgrader{ var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { CheckOrigin: func(r *http.Request) bool {
return true // Note: In production, you'd want to check the origin. origin := r.Header.Get("Origin")
return origin == "https://ddlion.net"
}, },
} }
// handleWebSocket handles WebSocket requests from the peer. func websocketCloseHandler(code int, text string) error {
log.Print("Client closed websocket.")
return nil
}
type Message struct {
Type string `json:"type"`
}
type MessageHandler func([]byte, *websocket.Conn) error
var messageHandlers = make(map[string]MessageHandler)
func registerHandler(messageType string, handler MessageHandler) {
messageHandlers[messageType] = handler
}
func dispatchMessage(message []byte, conn *websocket.Conn) error {
var msg Message
if err := json.Unmarshal(message, &msg); err != nil {
return err
}
handler, ok := messageHandlers[msg.Type]
if !ok {
log.Printf("No handler registered for message type: %s", msg.Type)
return nil
}
return handler(message, conn)
}
func handleWebSocket(w http.ResponseWriter, r *http.Request) { func handleWebSocket(w http.ResponseWriter, r *http.Request) {
log.Println("Websocket connection!", r.RemoteAddr) log.Println("Websocket connection!", r.RemoteAddr)
@@ -70,47 +107,126 @@ func handleWebSocket(w http.ResponseWriter, r *http.Request) {
} }
defer conn.Close() defer conn.Close()
conn.SetCloseHandler(websocketCloseHandler)
for { for {
mt, message, err := conn.ReadMessage() _, message, err := conn.ReadMessage()
if err != nil { if err != nil {
log.Println("Read error:", err) log.Println("ReadMessage error:", err)
break break
} }
log.Printf("recv: %s", message) log.Printf("recv: %s", message)
err = conn.WriteMessage(mt, message) if err := dispatchMessage(message, conn); err != nil {
if err != nil { log.Printf("Error dispatching message: %v", err)
log.Println("Write error:", err)
break
} }
} }
} }
// LoggingHandler logs requests and delegates them to the underlying handler. // Example handlers
type LoggingHandler struct { func handlePing(message []byte, conn *websocket.Conn) error {
handler http.Handler var pingMsg struct {
Type string `json:"type"`
PeerID string `json:"peer_id"`
}
if err := json.Unmarshal(message, &pingMsg); err != nil {
return err
}
log.Printf("Received ping from peer: %s", pingMsg.PeerID)
return nil
} }
func (lh *LoggingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { type PeerSet map[string]struct{}
log.Printf("Serving file: %s", r.URL.Path)
lh.handler.ServeHTTP(w, r) var userPeers = make(map[string]PeerSet)
var peerConnections = make(map[string]*websocket.Conn)
func handleHello(message []byte, conn *websocket.Conn) error {
var m struct {
Type string `json:"type"`
UserID string `json:"user_id"`
PeerID string `json:"peer_id"`
}
if err := json.Unmarshal(message, &m); err != nil {
return err
}
if userPeers[m.UserID] == nil {
userPeers[m.UserID] = make(PeerSet)
}
userPeers[m.UserID][m.PeerID] = struct{}{}
peerConnections[m.PeerID] = conn
jsonData, _ := json.MarshalIndent(userPeers, "", " ")
fmt.Println(string(jsonData), peerConnections)
log.Printf("Received connect from peer: %s, user:%s", m.PeerID, m.UserID)
return nil
}
// LoggingHandler logs requests and delegates them to the underlying handler.
// type LoggingHandler struct {
// handler http.Handler
// }
// func (lh *LoggingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// log.Printf("Serving file: %s", r.URL.Path)
// lh.handler.ServeHTTP(w, r)
// }
// BrotliResponseWriter wraps http.ResponseWriter to support Brotli compression
type brotliResponseWriter struct {
http.ResponseWriter
Writer io.Writer
}
func (w *brotliResponseWriter) Write(b []byte) (int, error) {
return w.Writer.Write(b)
} }
// noDirListing wraps an http.FileServer handler to prevent directory listings // noDirListing wraps an http.FileServer handler to prevent directory listings
func noDirListing(h http.Handler, root string) http.HandlerFunc { func noDirListing(h http.Handler, root string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
// Serve index.html when root is requested // Serve index.html when root is requested
if r.URL.Path == "/" { if r.URL.Path == "/" {
http.ServeFile(w, r, filepath.Join(root, "index.html")) http.ServeFile(w, r, filepath.Join(root, "index.html"))
return return
} }
// Check if the path is a directory
path := filepath.Join(root, r.URL.Path) path := filepath.Join(root, r.URL.Path)
if info, err := os.Stat(path); err == nil && info.IsDir() { info, err := os.Stat(path)
http.NotFound(w, r) // Return 404 for directories other than root if err != nil || info.IsDir() {
log.Printf("404 File not found/dir serving: %s to ip %s, useragent %s", r.URL.Path, r.RemoteAddr, r.UserAgent())
http.NotFound(w, r)
return return
} }
log.Printf("Serving: %s to ip %s, useragent %s", r.URL.Path, r.RemoteAddr, r.UserAgent())
// Check if client supports Brotli encoding
if strings.Contains(r.Header.Get("Accept-Encoding"), "br") {
w.Header().Set("Content-Encoding", "br")
w.Header().Del("Content-Length") // Cannot know content length with compressed data
// Wrap the ResponseWriter with Brotli writer
brWriter := brotli.NewWriter(w)
defer brWriter.Close()
// Create a ResponseWriter that writes to brWriter
bw := &brotliResponseWriter{
ResponseWriter: w,
Writer: brWriter,
}
// Serve the file using http.ServeFile
http.ServeFile(bw, r, path)
return
}
h.ServeHTTP(w, r) h.ServeHTTP(w, r)
} }
} }
@@ -122,8 +238,9 @@ func main() {
addr := ":" + strconv.Itoa(port) addr := ":" + strconv.Itoa(port)
log.Printf("Starting server on %s", addr) log.Printf("Starting server on %s", addr)
// http.Handle("/", http.FileServer(http.Dir(wwwDir))) // Register handlers
// http3.ListenAndServeQUIC("localhost:4242", "/path/to/cert/chain.pem", "/path/to/privkey.pem", nil) registerHandler("hello", handleHello)
registerHandler("ping", handlePing)
// Set up file server and WebSocket endpoint // Set up file server and WebSocket endpoint
fs := http.FileServer(http.Dir(dir)) fs := http.FileServer(http.Dir(dir))
@@ -134,11 +251,11 @@ func main() {
http.HandleFunc("/ws", handleWebSocket) http.HandleFunc("/ws", handleWebSocket)
// Configure and start the HTTP server // Configure and start the HTTP server
server := &http3.Server{ server := &http.Server{
Addr: addr, Addr: addr,
Handler: nil, // nil uses the default ServeMux, which we configured above Handler: nil, // nil uses the default ServeMux, which we configured above
} }
log.Printf("Server is configured and serving on port %d...", port) log.Printf("Server is configured and serving on port %d...", port)
log.Fatal(server.ListenAndServeTLS("./fullchain.pem", "./privkey.pem")) log.Fatal(server.ListenAndServeTLS("/etc/letsencrypt/live/ddlion.net/fullchain.pem", "/etc/letsencrypt/live/ddlion.net/privkey.pem"))
} }

783
main.js
View File

@@ -1,4 +1,4 @@
import { getData, addData, addDataArray } from "./db.js"; import { getData, addData, addDataArray, clearData, deleteData } from "./db.js";
// let posts:any; // let posts:any;
// let keyBase = "dandelion_posts_v1_" // let keyBase = "dandelion_posts_v1_"
// let key:string = ""; // let key:string = "";
@@ -10,6 +10,31 @@ import { getData, addData, addDataArray } from "./db.js";
// minute: number, // minute: number,
// second: number, // second: number,
// } // }
function waitMs(durationMs) {
return new Promise(resolve => setTimeout(resolve, durationMs));
}
function uuidv4() {
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, (c) => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16));
}
let logLines = [];
let logLength = 10;
function log(message) {
logLines.push(`${new Date().toLocaleTimeString()}: ${message}`);
if (logLines.length > 10) {
logLines = logLines.slice(logLines.length - logLength);
}
let log = document.getElementById("log");
if (!log) {
throw new Error();
}
log.innerText = logLines.join("\n");
}
function generateID() {
if (self.crypto.hasOwnProperty("randomUUID")) {
return self.crypto.randomUUID();
}
return uuidv4();
}
class Post { class Post {
constructor(author, author_id, text, post_timestamp, imageData = null, importedFrom = null, importSource = null) { constructor(author, author_id, text, post_timestamp, imageData = null, importedFrom = null, importSource = null) {
this.post_timestamp = post_timestamp; this.post_timestamp = post_timestamp;
@@ -34,319 +59,477 @@ window.addEventListener('scroll', () => {
// You can perform your action here // You can perform your action here
} }
}); });
function initMarkdown() { // let connectionReply = await wsConnection.send('hello');
const renderer = new marked.Renderer(); // for (let peeer of connectionReply) {
renderer.link = (href, title, text) => { // let peerConnection = await wsConnection.send('connect', peer.id);
return `<a href="${href}" target="_blank"${title ? ` title="${title}"` : ''}>${text}</a>`; // if (peerConnection) {
}; // this.peers.push(peerConnection);
marked.setOptions({ renderer: renderer }); // let postIDs = await peerConnection.getPostIDs();
} // let postsWeDontHave = this.diffPostIDs(postIDs);
function waitMs(durationMs) { // let newPosts = await peerConnection.getPosts(postsWeDontHave);
return new Promise(resolve => setTimeout(resolve, durationMs)); // this.addPosts(newPosts);
} // }
function uuidv4() { // }
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, (c) => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)); class wsConnection {
} connect() {
let logLines = []; if (this.websocket?.readyState === WebSocket.OPEN) {
let logLength = 10; return;
function log(message) {
logLines.push(`${new Date().toLocaleTimeString()}: ${message}`);
if (logLines.length > 10) {
logLines = logLines.slice(logLines.length - logLength);
}
let log = document.getElementById("log");
if (!log) {
throw new Error();
}
log.innerText = logLines.join("\n");
}
function arrayBufferToBase64(buffer) {
return new Promise((resolve, reject) => {
const blob = new Blob([buffer], { type: 'application/octet-stream' });
const reader = new FileReader();
reader.onloadend = () => {
const dataUrl = reader.result;
if (!dataUrl) {
resolve(null);
return;
}
const base64 = dataUrl.split(',')[1];
resolve(base64);
};
reader.onerror = (error) => {
reject(error);
};
reader.readAsDataURL(blob);
});
}
async function createTestData() {
let postsTestData = await (await fetch("./postsTestData.json")).json();
return postsTestData;
}
let time = 0;
function timerStart() {
time = performance.now();
}
function timerDelta() {
return performance.now() - time;
}
async function getFixedTweetText(entry) {
let fullText = entry.tweet.full_text;
let linkMarkdown = "";
for (const url of entry.tweet.entities.urls) {
linkMarkdown = `[${url.display_url}](${url.expanded_url})`;
fullText = fullText.replace(url.url, linkMarkdown);
}
return fullText;
}
async function createTestData2(userID) {
log("Importing tweet archive");
let postsTestData = [];
let response = await fetch("./tweets.js");
let tweetsText = await response.text();
tweetsText = tweetsText.replace("window.YTD.tweets.part0", "window.tweetData");
new Function(tweetsText)();
// let tweets = JSON.parse(tweetJSON);
let count = 0;
for (let entry of window.tweetData) {
// if (entry.tweet.hasOwnProperty("in_reply_to_screen_name") || entry.tweet.retweeted || entry.tweet.full_text.startsWith("RT")) {
// continue;
// }
let mediaURL = entry.tweet?.entities?.media?.[0]?.media_url;
let isImage = false;
if (mediaURL) {
isImage = mediaURL.includes('jpg');
} }
let imageData = null; window.clearInterval(this.websocketPingInterval);
if (isImage) { if (this.websocket) {
try { this.websocket.close();
let response = await fetch(mediaURL); }
await waitMs(100); ;
if (response.status === 200) { try {
imageData = await response.arrayBuffer(); this.websocket = new WebSocket(`wss://${window.location.hostname}:${window.location.port}/ws`);
}
catch (error) {
console.log(error.message);
return;
}
this.websocket.onopen = (evt) => {
log("ws:connected");
this.websocket.send(`{"type":"hello", "user_id": "${this.userID}", "peer_id":"${this.peerID}"}`);
this.websocketPingInterval = window.setInterval(() => {
if (!navigator.onLine) {
return;
} }
console.log(imageData); this.websocket.send(`{"type":"ping", "peer_id": "${this.peerID}"}`);
} }, 10000);
catch (e) { };
console.log(e); this.websocket.onclose = (evt) => {
} log("ws:disconnected");
// this.retry *= 2;
log(`Retrying in ${this.retry} seconds`);
window.setTimeout(() => { this.connect(); }, this.retry * 1000);
};
this.websocket.onmessage = (event) => {
log('ws:response: ' + event.data);
};
this.websocket.onerror = (event) => {
log('ws:error: ' + event);
};
}
disconnect() {
this.websocket?.close();
}
constructor(userID, peerID) {
this.websocket = null;
this.userID = "";
this.peerID = "";
this.websocketPingInterval = 0;
this.retry = 10;
this.state = 'disconnected';
this.userID = userID;
this.peerID = peerID;
this.connect();
if (!this.websocket) {
// set a timer and retry?
} }
let timeStamp = new Date(entry.tweet.created_at); }
let tweetText = await getFixedTweetText(entry); }
let newPost = new Post('bobbydigitales', userID, tweetText, timeStamp, imageData, 'twitter', entry); // function connectWebsocket(userID: string) {
postsTestData.push(newPost); // let websocket = new WebSocket(`ws://${window.location.hostname}:${window.location.port}/ws`);
count++; // websocket.onopen = function (evt) {
if (count % 100 === 0) { // log("Websocket: CONNECTED");
log(`Imported ${count} posts...`); // websocket.send(`{"messageType":"connect", "id": "${userID}"}`);
// render(postsTestData); // let websocketPingInterval = window.setInterval(() => { websocket.send(`{"messageType":"ping", "id": "${userID}"}`); }, 5000)
// };
// websocket.onclose = function (evt) {
// log("Websocket: DISCONNECTED");
// };
// websocket.onmessage = function (evt) {
// log('Websocket: RESPONSE: ' + evt.data);
// };
// websocket.onerror = function (evt) {
// log('Websocket: ERROR: ' + evt);
// };
// return websocket;
// }
class App {
constructor() {
this.userID = '';
this.peerID = '';
this.time = 0;
}
initMarkdown() {
const renderer = new marked.Renderer();
renderer.link = (href, title, text) => {
return `<a href="${href}" target="_blank"${title ? ` title="${title}"` : ''}>${text}</a>`;
};
marked.setOptions({ renderer: renderer });
}
arrayBufferToBase64(buffer) {
return new Promise((resolve, reject) => {
const blob = new Blob([buffer], { type: 'application/octet-stream' });
const reader = new FileReader();
reader.onloadend = () => {
const dataUrl = reader.result;
if (!dataUrl) {
resolve(null);
return;
}
const base64 = dataUrl.split(',')[1];
resolve(base64);
};
reader.onerror = (error) => {
reject(error);
};
reader.readAsDataURL(blob);
});
}
async createTestData() {
let postsTestData = await (await fetch("./postsTestData.json")).json();
return postsTestData;
}
timerStart() {
this.time = performance.now();
}
timerDelta() {
return performance.now() - this.time;
}
getFixedTweetText(entry) {
let fullText = entry.tweet.full_text;
let linkMarkdown = "";
for (const url of entry.tweet.entities.urls) {
linkMarkdown = `[${url.display_url}](${url.expanded_url})`;
fullText = fullText.replace(url.url, linkMarkdown);
} }
// if (count == 100-1) { return fullText;
// break;
// }
} }
return postsTestData; async importTweetArchive(userID, tweetArchive) {
} log("Importing tweet archive");
async function createTestData3(userID) { let postsTestData = [];
let posts = await (await (fetch('./posts.json'))).json(); // let response = await fetch("./tweets.js");
return posts; // let tweetsText = await response.text();
} // tweetsText = tweetsText.replace("window.YTD.tweets.part0", "window.tweetData");
async function registerServiceWorker() { // new Function(tweetsText)();
if (!("serviceWorker" in navigator)) { // let tweets = JSON.parse(tweetJSON);
return; let count = 0;
for (let entry of tweetArchive) {
if (entry.tweet.hasOwnProperty("in_reply_to_screen_name") || entry.tweet.retweeted || entry.tweet.full_text.startsWith("RT")) {
continue;
}
let mediaURL = entry.tweet?.entities?.media?.[0]?.media_url_https;
let isImage = false;
if (mediaURL) {
isImage = mediaURL.includes('jpg');
}
let imageData = null;
// if (isImage) {
// try {
// let response = await fetch(mediaURL);
// await waitMs(100);
// if (response.status === 200) {
// imageData = await response.arrayBuffer();
// }
// console.log(imageData);
// } catch (e) {
// console.log(e);
// }
// }
let timeStamp = new Date(entry.tweet.created_at);
let tweetText = this.getFixedTweetText(entry);
let newPost = new Post('bobbydigitales', userID, tweetText, timeStamp, imageData, 'twitter', entry);
postsTestData.push(newPost);
count++;
if (count % 100 === 0) {
log(`Imported ${count} posts...`);
// render(postsTestData);
}
// if (count == 100-1) {
// break;
// }
}
return postsTestData;
} }
let registrations = await navigator.serviceWorker.getRegistrations(); async createTestData3(userID) {
if (registrations.length > 0) { let posts = await (await (fetch('./posts.json'))).json();
console.log("Service worker already registered.");
return registrations[0];
}
navigator.serviceWorker
.register("/sw.js")
.then((registration) => {
console.log("Service Worker registered with scope:", registration.scope);
return registration;
})
.catch((error) => {
console.error("Service Worker registration failed:", error);
});
}
function addPost(userID, posts, postText) {
if ((typeof postText !== "string") || postText.length === 0) {
log("Not posting an empty string...");
return;
}
let post = new Post(`bobbydigitales`, userID, postText, new Date());
posts.push(post);
// localStorage.setItem(key, JSON.stringify(posts));
addData(userID, post);
render(posts);
}
function generateID() {
if (self.crypto.hasOwnProperty("randomUUID")) {
return self.crypto.randomUUID();
}
return uuidv4();
}
function getUserID() {
let id = localStorage.getItem("dandelion_id");
if (!id) {
id = generateID();
localStorage.setItem("dandelion_id", id);
}
return id;
}
function connectWebsocket(userID) {
let websocket = new WebSocket(`ws://${window.location.hostname}:${window.location.port}/ws`);
websocket.onopen = function (evt) {
log("Websocket: CONNECTED");
websocket.send(`{"messageType":"connect", "id": "${userID}"}`);
let websocketPingInterval = window.setInterval(() => { websocket.send(`{"messageType":"ping", "id": "${userID}"}`); }, 5000);
};
websocket.onclose = function (evt) {
log("Websocket: DISCONNECTED");
};
websocket.onmessage = function (evt) {
log('Websocket: RESPONSE: ' + evt.data);
};
websocket.onerror = function (evt) {
log('Websocket: ERROR: ' + evt);
};
return websocket;
}
function setFont(fontName, fontSize) {
let content = document.getElementById('content');
if (!content) {
return;
}
content.style.fontFamily = fontName;
content.style.fontSize = fontSize;
let textArea = document.getElementById('textarea_post');
if (!textArea) {
return;
}
textArea.style.fontFamily = fontName;
textArea.style.fontSize = fontSize;
}
function initOffline() {
// Event listener for going offline
window.addEventListener('offline', () => { log("offline"); });
// Event listener for going online
window.addEventListener('online', () => { log("online"); });
log(`Online status: ${navigator.onLine ? "online" : "offline"}`);
}
function initButtons(userID, posts) {
let font1Button = document.getElementById("button_font1");
let font2Button = document.getElementById("button_font2");
font1Button.addEventListener('click', () => { setFont('Bookerly', '16px'); });
font2Button.addEventListener('click', () => { setFont('Virgil', '24px'); });
let postButton = document.getElementById("button_post");
let postText = document.getElementById("textarea_post");
if (!(postButton && postText)) {
throw new Error();
}
postButton.addEventListener("click", () => {
addPost(userID, posts, postText.value);
postText.value = "";
});
}
async function loadPosts(userID) {
timerStart();
let posts = await getData(userID, new Date(2022, 8), new Date());
if (posts.length > 0) {
log(`Loaded ${posts.length} posts in ${timerDelta().toFixed(2)}ms`);
return posts; return posts;
} }
posts = await createTestData2(userID); async registerServiceWorker() {
log("Adding test data..."); if (!("serviceWorker" in navigator)) {
addDataArray(userID, posts); return;
return await getData(userID, new Date(2022, 8), new Date());
}
async function main() {
let posts = [];
let time = 0;
``;
let delta = 0;
let urlParams = (new URL(window.location.href)).searchParams;
if (urlParams.get("sw") === "true") {
let registration = await registerServiceWorker();
}
let userID = getUserID();
log(`Your user ID is: ${userID}`);
if (navigator.storage && navigator.storage.persist && !navigator.storage.persisted) {
const isPersisted = await navigator.storage.persist();
log(`Persisted storage granted: ${isPersisted}`);
}
log(`Persisted: ${(await navigator?.storage?.persisted())?.toString()}`);
initMarkdown();
// let main = await fetch("/main.js");
// let code = await main.text();
// console.log(code);
// registration.active.postMessage({type:"updateMain", code:code});
posts = await loadPosts(userID);
let websocket = connectWebsocket(userID);
initOffline();
initButtons(userID, posts);
// debugger;
timerStart();
render(posts);
let renderTime = timerDelta();
log(`render took: ${renderTime.toFixed(2)}ms`);
log(`memory used: ${(performance.memory.usedJSHeapSize / 1024 / 1024).toFixed(2)}Mb`);
}
function render(posts) {
const fragment = document.createDocumentFragment();
let contentDiv = document.getElementById("content");
if (!contentDiv) {
throw new Error();
}
contentDiv.innerHTML = "";
let count = 0;
for (let i = posts.length - 1; i >= 0; i--) {
let postData = posts[i];
let post = renderPost(postData);
if (post) {
fragment.appendChild(post);
count++;
} }
if (count > 100) { let registrations = await navigator.serviceWorker.getRegistrations();
break; if (registrations.length > 0) {
console.log("Service worker already registered.");
return registrations[0];
} }
navigator.serviceWorker
.register("/sw.js")
.then((registration) => {
console.log("Service Worker registered with scope:", registration.scope);
return registration;
})
.catch((error) => {
console.error("Service Worker registration failed:", error);
});
} }
if (!contentDiv) { addPost(userID, posts, postText) {
throw new Error("Couldn't get content div!"); if ((typeof postText !== "string") || postText.length === 0) {
log("Not posting an empty string...");
return;
}
let post = new Post(`bobbydigitales`, userID, postText, new Date());
posts.push(post);
// localStorage.setItem(key, JSON.stringify(posts));
addData(userID, post);
this.render(posts);
} }
contentDiv.appendChild(fragment); getPeerID() {
} let id = localStorage.getItem("peer_id");
function renderPost(post) { if (!id) {
if (!(post.hasOwnProperty("text"))) { id = generateID();
throw new Error("Post is malformed!"); localStorage.setItem("peer_id", id);
}
return id;
} }
let containerDiv = document.createElement("div"); getUserID() {
let textDiv = document.createElement("div"); let id = localStorage.getItem("dandelion_id");
let timestampDiv = document.createElement("div"); if (!id) {
let hr = document.createElement("hr"); id = generateID();
textDiv.innerHTML = marked.parse(post.text); localStorage.setItem("dandelion_id", id);
// textDiv.innerHTML = DOMPurify.sanitize(marked.parse(post.text)); }
timestampDiv.innerText = `${post.post_timestamp.toDateString()}`; return id;
timestampDiv.title = `${post.post_timestamp.toLocaleTimeString()} · ${post.post_timestamp.toDateString()}`; }
containerDiv.appendChild(hr); setFont(fontName, fontSize) {
containerDiv.appendChild(textDiv); let content = document.getElementById('content');
if (!("image_data" in post && post.image_data)) { if (!content) {
containerDiv.appendChild(timestampDiv); return;
}
content.style.fontFamily = fontName;
content.style.fontSize = fontSize;
let textArea = document.getElementById('textarea_post');
if (!textArea) {
return;
}
textArea.style.fontFamily = fontName;
textArea.style.fontSize = fontSize;
}
initOffline(connection) {
// Event listener for going offline
window.addEventListener('offline', () => {
log("offline");
});
// Event listener for going online
window.addEventListener('online', () => {
log("online");
connection.connect();
});
log(`Online status: ${navigator.onLine ? "online" : "offline"}`);
}
selectFile(contentType) {
return new Promise(resolve => {
let input = document.createElement('input');
input.type = 'file';
// input.multiple = multiple;
input.accept = contentType;
input.onchange = () => {
if (input.files == null) {
resolve(null);
return;
}
let files = Array.from(input.files);
// if (multiple)
// resolve(files);
// else
resolve(files[0]);
};
input.click();
});
}
readFile(file) {
// Always return a Promise
return new Promise((resolve, reject) => {
let content = '';
const reader = new FileReader();
// Wait till complete
reader.onloadend = function (e) {
content = e.target.result;
resolve(content);
};
// Make sure to handle error states
reader.onerror = function (e) {
reject(e);
};
reader.readAsText(file);
});
}
initButtons(userID, posts, registration) {
let font1Button = document.getElementById("button_font1");
let font2Button = document.getElementById("button_font2");
let importTweetsButton = document.getElementById("import_tweets");
let clearPostsButton = document.getElementById("clear_posts");
let updateApp = document.getElementById("update_app");
let ddlnLogoButton = document.getElementById('ddln-logo-button');
font1Button.addEventListener('click', () => { this.setFont('Bookerly', '16px'); });
font2Button.addEventListener('click', () => { this.setFont('Virgil', '16px'); });
importTweetsButton.addEventListener('click', async () => {
let file = await this.selectFile('text/*');
console.log(file);
if (file == null) {
return;
}
let tweetData = await this.readFile(file);
tweetData = tweetData.replace('window.YTD.tweets.part0 = ', '');
const tweets = JSON.parse(tweetData);
let imported_posts = await this.importTweetArchive(userID, tweets);
clearData(userID);
// posts = posts.reverse();
addDataArray(userID, imported_posts);
posts = await this.loadPosts(userID) ?? [];
this.render(posts);
});
clearPostsButton.addEventListener('click', () => { clearData(userID); posts = []; this.render(posts); });
let postButton = document.getElementById("button_post");
let postText = document.getElementById("textarea_post");
if (!(postButton && postText)) {
throw new Error();
}
postButton.addEventListener("click", () => {
this.addPost(userID, posts, postText.value);
postText.value = "";
});
updateApp.addEventListener("click", () => {
registration?.active?.postMessage({ type: "update_app" });
});
let infoElement = document.getElementById('info');
if (infoElement === null) {
return;
}
ddlnLogoButton.addEventListener('click', () => { infoElement.style.display == 'none' ? infoElement.style.display = 'block' : infoElement.style.display = 'none'; });
}
async loadPosts(userID) {
this.timerStart();
let posts = await getData(userID, new Date(2022, 8), new Date());
if (posts.length > 0) {
log(`Loaded ${posts.length} posts in ${this.timerDelta().toFixed(2)}ms`);
return posts;
}
// posts = await createTestData2(userID);
// log("Adding test data...");
// addDataArray(userID, posts);
// return await getData(userID, new Date(2022, 8), new Date());
}
async main() {
let urlParams = (new URL(window.location.href)).searchParams;
let connection_userID = urlParams.get('connect');
let registration = undefined;
if (urlParams.get("sw") === "true") {
registration = await this.registerServiceWorker();
}
if (connection_userID) {
console.log('connect', connection_userID);
localStorage.setItem("dandelion_id", connection_userID);
}
let posts = [];
let userID = this.getUserID();
let peerID = this.getPeerID();
this.userID = userID;
this.peerID = peerID;
log(`user:${userID} peer:${peerID}`);
let websocket = new wsConnection(userID, peerID);
window.addEventListener('beforeunload', () => { websocket.disconnect(); });
this.initOffline(websocket);
this.initButtons(userID, posts, registration);
let time = 0;
let delta = 0;
if (navigator.storage && navigator.storage.persist && !navigator.storage.persisted) {
debugger;
const isPersisted = await navigator.storage.persist();
log(`Persisted storage granted: ${isPersisted}`);
}
log(`Persisted: ${(await navigator?.storage?.persisted())?.toString()}`);
this.initMarkdown();
// let main = await fetch("/main.js");
// let code = await main.text();
// console.log(code);
// registration.active.postMessage({type:"updateMain", code:code});
posts = await this.loadPosts(userID) ?? [];
// debugger;
this.timerStart();
this.render(posts); // , (postID:string)=>{this.deletePost(userID, postID)}
let renderTime = this.timerDelta();
log(`render took: ${renderTime.toFixed(2)}ms`);
if (performance?.memory) {
log(`memory used: ${(performance.memory.usedJSHeapSize / 1024 / 1024).toFixed(2)}Mb`);
}
// const client = new WebTorrent()
// // Sintel, a free, Creative Commons movie
// const torrentId = 'magnet:?xt=urn:btih:6091e199a8d9272a40dd9a25a621a5c355d6b0be&dn=WING+IT!+-+Blender+Open+Movie+1080p.mp4&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=udp%3A%2F%2Fexplodie.org%3A6969&tr=udp%3A%2F%2Ftracker.empire-js.us%3A1337';
// client.add(torrentId, function (torrent: any) {
// // Torrents can contain many files. Let's use the .mp4 file
// const file = torrent.files.find(function (file: any) {
// return file.name.endsWith('.mp4')
// })
// // Display the file by adding it to the DOM.
// // Supports video, audio, image files, and more!
// file.appendTo(document.getElementById('torrent-content'));
// })
}
render(posts) {
const fragment = document.createDocumentFragment();
let contentDiv = document.getElementById("content");
if (!contentDiv) {
throw new Error();
}
contentDiv.innerHTML = "";
let count = 0;
new QRCode(document.getElementById('qrcode'), `https://ddlion.net/?connect=${this.userID}`);
for (let i = posts.length - 1; i >= 0; i--) {
let postData = posts[i];
let post = this.renderPost(postData, posts);
if (post) {
fragment.appendChild(post);
count++;
}
if (count > 100) {
break;
}
}
if (!contentDiv) {
throw new Error("Couldn't get content div!");
}
contentDiv.appendChild(fragment);
}
deletePost(userID, postID) {
deleteData(userID, postID);
}
renderPost(post, posts) {
if (!(post.hasOwnProperty("text"))) {
throw new Error("Post is malformed!");
}
let containerDiv = document.createElement("div");
let timestamp = `${post.post_timestamp.toLocaleTimeString()} · ${post.post_timestamp.toLocaleDateString()}`;
let deleteButton = document.createElement('button');
deleteButton.innerText = 'delete';
// deleteButton.onclick = ()=>{deletefunc(post.post_id)};
let postTemplate = `<div><hr>
<div><span class='header' title='${timestamp}'>@${post.author} - ${post.post_timestamp.toLocaleDateString()}</span></div>
<div>${marked.parse(post.text)}</div>
</div>`;
containerDiv.innerHTML = postTemplate;
containerDiv.appendChild(deleteButton);
// if (!("image_data" in post && post.image_data)) {
// containerDiv.appendChild(timestampDiv);
// return containerDiv;
// // return null;
// }
// let image = document.createElement("img");
// const blob = new Blob([post.image_data as ArrayBuffer], { type: 'image/jpg' });
// const url = URL.createObjectURL(blob);
// image.onload = () => {
// URL.revokeObjectURL(url);
// };
// image.src = url;
// // image.src = image.src = "data:image/png;base64," + post.image;
// image.className = "postImage";
// containerDiv.appendChild(image);
// containerDiv.appendChild(timestampDiv);
return containerDiv; return containerDiv;
// return null;
} }
let image = document.createElement("img");
const blob = new Blob([post.image_data], { type: 'image/jpg' });
const url = URL.createObjectURL(blob);
image.onload = () => {
URL.revokeObjectURL(url);
};
image.src = url;
// image.src = image.src = "data:image/png;base64," + post.image;
image.className = "postImage";
containerDiv.appendChild(image);
containerDiv.appendChild(timestampDiv);
return containerDiv;
} }
window.addEventListener("load", main); let app = new App();
window.addEventListener("load", app.main.bind(app));
//# sourceMappingURL=main.js.map //# sourceMappingURL=main.js.map

File diff suppressed because one or more lines are too long

13
mdns/main.js Normal file
View File

@@ -0,0 +1,13 @@
const bonjour = require("bonjour")();
const PORT = 6789;
// Advertise the service using bonjour
let result = bonjour.publish({
name: "ddln",
type: "https",
port: PORT,
host: "ddln.local",
});
console.log(result);

370
mdns/package-lock.json generated Normal file
View File

@@ -0,0 +1,370 @@
{
"name": "mdns",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"bonjour": "^3.5.0"
}
},
"node_modules/array-flatten": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz",
"integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ=="
},
"node_modules/bonjour": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz",
"integrity": "sha512-RaVTblr+OnEli0r/ud8InrU7D+G0y6aJhlxaLa6Pwty4+xoxboF1BsUI45tujvRpbj9dQVoglChqonGAsjEBYg==",
"dependencies": {
"array-flatten": "^2.1.0",
"deep-equal": "^1.0.1",
"dns-equal": "^1.0.0",
"dns-txt": "^2.0.2",
"multicast-dns": "^6.0.1",
"multicast-dns-service-types": "^1.1.0"
}
},
"node_modules/buffer-indexof": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz",
"integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g=="
},
"node_modules/call-bind": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz",
"integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==",
"dependencies": {
"function-bind": "^1.1.2",
"get-intrinsic": "^1.2.1",
"set-function-length": "^1.1.1"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/deep-equal": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz",
"integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==",
"dependencies": {
"is-arguments": "^1.0.4",
"is-date-object": "^1.0.1",
"is-regex": "^1.0.4",
"object-is": "^1.0.1",
"object-keys": "^1.1.1",
"regexp.prototype.flags": "^1.2.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/define-data-property": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz",
"integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==",
"dependencies": {
"get-intrinsic": "^1.2.1",
"gopd": "^1.0.1",
"has-property-descriptors": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/define-properties": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
"dependencies": {
"define-data-property": "^1.0.1",
"has-property-descriptors": "^1.0.0",
"object-keys": "^1.1.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/dns-equal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz",
"integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg=="
},
"node_modules/dns-packet": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz",
"integrity": "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==",
"dependencies": {
"ip": "^1.1.0",
"safe-buffer": "^5.0.1"
}
},
"node_modules/dns-txt": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz",
"integrity": "sha512-Ix5PrWjphuSoUXV/Zv5gaFHjnaJtb02F2+Si3Ht9dyJ87+Z/lMmy+dpNHtTGraNK958ndXq2i+GLkWsWHcKaBQ==",
"dependencies": {
"buffer-indexof": "^1.0.0"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/functions-have-names": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-intrinsic": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz",
"integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==",
"dependencies": {
"function-bind": "^1.1.2",
"has-proto": "^1.0.1",
"has-symbols": "^1.0.3",
"hasown": "^2.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/gopd": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
"dependencies": {
"get-intrinsic": "^1.1.3"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-property-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz",
"integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==",
"dependencies": {
"get-intrinsic": "^1.2.2"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
"integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
"dependencies": {
"has-symbols": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
"integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/ip": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz",
"integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg=="
},
"node_modules/is-arguments": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
"integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
"dependencies": {
"call-bind": "^1.0.2",
"has-tostringtag": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-date-object": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
"integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
"dependencies": {
"has-tostringtag": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-regex": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
"integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
"dependencies": {
"call-bind": "^1.0.2",
"has-tostringtag": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/multicast-dns": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz",
"integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==",
"dependencies": {
"dns-packet": "^1.3.1",
"thunky": "^1.0.2"
},
"bin": {
"multicast-dns": "cli.js"
}
},
"node_modules/multicast-dns-service-types": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz",
"integrity": "sha512-cnAsSVxIDsYt0v7HmC0hWZFwwXSh+E6PgCrREDuN/EsjgLwA5XRmlMHhSiDPrt6HxY1gTivEa/Zh7GtODoLevQ=="
},
"node_modules/object-is": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz",
"integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==",
"dependencies": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/regexp.prototype.flags": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz",
"integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==",
"dependencies": {
"call-bind": "^1.0.2",
"define-properties": "^1.2.0",
"set-function-name": "^2.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/set-function-length": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz",
"integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==",
"dependencies": {
"define-data-property": "^1.1.1",
"get-intrinsic": "^1.2.1",
"gopd": "^1.0.1",
"has-property-descriptors": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/set-function-name": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz",
"integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==",
"dependencies": {
"define-data-property": "^1.0.1",
"functions-have-names": "^1.2.3",
"has-property-descriptors": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/thunky": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
"integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA=="
}
}
}

5
mdns/package.json Normal file
View File

@@ -0,0 +1,5 @@
{
"dependencies": {
"bonjour": "^3.5.0"
}
}

View File

@@ -1,5 +1,5 @@
{ {
"devDependencies": { "devDependencies": {
"typescript": "^5.4.3" "typescript": "^5.5.4"
} }
} }

2
robots.txt Normal file
View File

@@ -0,0 +1,2 @@
User-agent: *
Disallow: /

View File

@@ -4,8 +4,7 @@
// email: string; // email: string;
// } // }
const dbName: string = "ddln"; const postStoreName: string = "posts";
const storeNameBase: string = "posts";
let keyBase = "dandelion_posts_v1_" let keyBase = "dandelion_posts_v1_"
let key = ""; let key = "";
@@ -19,8 +18,8 @@ type DBError = Event & {
target: { errorCode: DOMException }; target: { errorCode: DOMException };
}; };
export function openDatabase(userID:string): Promise<IDBDatabase> { export function openDatabase(userID: string): Promise<IDBDatabase> {
const storeName = `${storeNameBase}_${userID}`; const dbName = `user_${userID}`
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const request: IDBOpenDBRequest = indexedDB.open(dbName, 1); const request: IDBOpenDBRequest = indexedDB.open(dbName, 1);
@@ -33,8 +32,8 @@ export function openDatabase(userID:string): Promise<IDBDatabase> {
request.onupgradeneeded = (event: IDBVersionChangeEvent) => { request.onupgradeneeded = (event: IDBVersionChangeEvent) => {
const db: IDBDatabase = (event.target as IDBOpenDBRequest).result; const db: IDBDatabase = (event.target as IDBOpenDBRequest).result;
if (!db.objectStoreNames.contains(storeName)) { if (!db.objectStoreNames.contains(postStoreName)) {
let store = db.createObjectStore(storeName, { keyPath: "id", autoIncrement: true }); let store = db.createObjectStore(postStoreName, { keyPath: "id", autoIncrement: true });
store.createIndex("datetimeIndex", "post_timestamp", { unique: false }); store.createIndex("datetimeIndex", "post_timestamp", { unique: false });
store.createIndex("postIDIndex", "data.post_id", { unique: true }); store.createIndex("postIDIndex", "data.post_id", { unique: true });
@@ -52,12 +51,11 @@ export function openDatabase(userID:string): Promise<IDBDatabase> {
export async function addData(userID: string, data: any): Promise<void> { export async function addData(userID: string, data: any): Promise<void> {
try { try {
const storeName = `${storeNameBase}_${userID}`;
const db = await openDatabase(userID); const db = await openDatabase(userID);
const transaction = db.transaction(storeName, "readwrite"); const transaction = db.transaction(postStoreName, "readwrite");
const store = transaction.objectStore(storeName); const store = transaction.objectStore(postStoreName);
const addRequest = store.add({post_timestamp: data.post_timestamp, data:data}); const addRequest = store.add({ post_timestamp: data.post_timestamp, data: data });
addRequest.onsuccess = (e: Event) => { addRequest.onsuccess = (e: Event) => {
// console.log('Data has been added:', (e.target as IDBRequest).result); // console.log('Data has been added:', (e.target as IDBRequest).result);
@@ -73,23 +71,72 @@ export async function addData(userID: string, data: any): Promise<void> {
} }
} }
export async function deleteData(userID: string, postID: string) {
try {
const db = await openDatabase(userID);
const transaction = db.transaction(postStoreName, "readwrite");
const store = transaction.objectStore(postStoreName);
const index = store.index("postIDIndex");
const getRequest = index.getKey(postID);
getRequest.onerror = e => console.log((e.target as IDBRequest).error)
getRequest.onsuccess = e => {
const key = (e.target as IDBRequest).result;
if (key === undefined) {
console.error("Post not found");
return null;
}
const deleteRequest = store.delete(key);
deleteRequest.onerror = e => { console.error((e.target as IDBRequest).error); return false };
deleteRequest.onsuccess = () => true;
}
} catch (error) {
console.error('Error in opening database:', error);
}
}
export async function clearData(userID: string) {
try {
const db = await openDatabase(userID);
const transaction = db.transaction(postStoreName, "readwrite");
const store = transaction.objectStore(postStoreName);
const clearRequest = store.clear();
clearRequest.onsuccess = (e: Event) => {
// console.log('Data has been added:', (e.target as IDBRequest).result);
};
clearRequest.onerror = (event: Event) => {
// Use a type assertion to access the specific properties of IDBRequest error event
const errorEvent = event as IDBRequestEvent;
console.error('Error in clearing data:', errorEvent.target.error?.message);
};
} catch (error) {
console.error('Error in opening database:', error);
}
}
export async function addDataArray(userID: string, array: any[]): Promise<void> { export async function addDataArray(userID: string, array: any[]): Promise<void> {
try { try {
const storeName = `${storeNameBase}_${userID}`;
const db = await openDatabase(userID); const db = await openDatabase(userID);
const transaction = db.transaction(storeName, "readwrite"); const transaction = db.transaction(postStoreName, "readwrite");
const store = transaction.objectStore(storeName); const store = transaction.objectStore(postStoreName);
let count = 0; let count = 0;
array.reverse(); array.reverse();
for (let data of array) { for (let data of array) {
const addRequest = store.add({post_timestamp: data.post_timestamp, data:data}); const addRequest = store.add({ post_timestamp: data.post_timestamp, data: data });
addRequest.onsuccess = (e: Event) => { addRequest.onsuccess = (e: Event) => {
// console.log('Data has been added:', (e.target as IDBRequest).result); // console.log('Data has been added:', (e.target as IDBRequest).result);
}; };
addRequest.onerror = (event: Event) => { addRequest.onerror = (event: Event) => {
// Use a type assertion to access the specific properties of IDBRequest error event // Use a type assertion to access the specific properties of IDBRequest error event
const errorEvent = event as IDBRequestEvent; const errorEvent = event as IDBRequestEvent;
@@ -103,17 +150,16 @@ export async function addDataArray(userID: string, array: any[]): Promise<void>
// } // }
} }
} catch (error) { } catch (error) {
console.error('Error in opening database:', error); console.error('Error in opening database:', error);
} }
} }
export async function getData(userID:string, lowerID:Date, upperID:Date): Promise<any | undefined> { export async function getData(userID: string, lowerID: Date, upperID: Date): Promise<any | undefined> {
const storeName = `${storeNameBase}_${userID}`;
const db = await openDatabase(userID); const db = await openDatabase(userID);
const transaction = db.transaction(storeName, "readonly"); const transaction = db.transaction(postStoreName, "readonly");
const store = transaction.objectStore(storeName); const store = transaction.objectStore(postStoreName);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const keyRangeValue = IDBKeyRange.bound(lowerID, upperID); const keyRangeValue = IDBKeyRange.bound(lowerID, upperID);
@@ -145,11 +191,10 @@ export async function getData(userID:string, lowerID:Date, upperID:Date): Promis
}); });
} }
export async function getAllData(userID:string): Promise<any | undefined> { export async function getAllData(userID: string): Promise<any | undefined> {
const storeName = `${storeNameBase}_${userID}`;
const db = await openDatabase(userID); const db = await openDatabase(userID);
const transaction = db.transaction(storeName, "readonly"); const transaction = db.transaction(postStoreName, "readonly");
const store = transaction.objectStore(storeName); const store = transaction.objectStore(postStoreName);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const getRequest = store.getAll(); const getRequest = store.getAll();

File diff suppressed because it is too large Load Diff

83
src/webRTC.ts Normal file
View File

@@ -0,0 +1,83 @@
const config = {
iceServers: [{ urls: "stun: stun.l.google.com" }],
};
let localConnection = new RTCPeerConnection();
function handleSendChannelStatusChange() {
console.log(handleSendChannelStatusChange);
}
let sendChannel = localConnection.createDataChannel("sendChannel");
sendChannel.onopen = handleSendChannelStatusChange;
sendChannel.onclose = handleSendChannelStatusChange;
let remoteConnection = new RTCPeerConnection();
remoteConnection.ondatachannel = receiveChannelCallback;
localConnection.onicecandidate = (e:any) =>
!e.candidate ||
remoteConnection.addIceCandidate(e.candidate).catch(handleAddCandidateError);
remoteConnection.onicecandidate = (e) =>
!e.candidate ||
localConnection.addIceCandidate(e.candidate).catch(handleAddCandidateError);
function handleCreateDescriptionError(error:any) {
console.log(`Unable to create an offer: ${error.toString()}`);
}
function handleLocalAddCandidateSuccess() {
console.log('handleLocalAddCandidateSuccess');
}
function handleRemoteAddCandidateSuccess() {
console.log('handleRemoteAddCandidateSuccess');
}
function handleAddCandidateError() {
console.log("Oh noes! addICECandidate failed!");
}
localConnection
.createOffer()
.then((offer) => localConnection.setLocalDescription(offer))
.then(() =>
remoteConnection.setRemoteDescription(localConnection.localDescription as RTCSessionDescriptionInit),
)
.then(() => remoteConnection.createAnswer())
.then((answer) => remoteConnection.setLocalDescription(answer))
.then(() =>
localConnection.setRemoteDescription(remoteConnection.localDescription as RTCSessionDescriptionInit),
)
.catch(handleCreateDescriptionError);
function handleReceiveChannelStatusChange(event:any) {
let receiveChannel = event.channel;
if (receiveChannel) {
console.log(
`Receive channel's status has changed to ${receiveChannel.readyState}`,
);
}
}
function receiveChannelCallback(event:any) {
let receiveChannel = event.channel;
receiveChannel.onmessage = handleReceiveMessage;
receiveChannel.onopen = handleReceiveChannelStatusChange;
receiveChannel.onclose = handleReceiveChannelStatusChange;
}
function sendMessage(message:string) {
sendChannel.send(message);
}
function handleReceiveMessage(event:any) {
console.log(event.data);
}

56
webRTC.js Normal file
View File

@@ -0,0 +1,56 @@
"use strict";
const config = {
iceServers: [{ urls: "stun: stun.l.google.com" }],
};
let localConnection = new RTCPeerConnection();
function handleSendChannelStatusChange() {
console.log(handleSendChannelStatusChange);
}
let sendChannel = localConnection.createDataChannel("sendChannel");
sendChannel.onopen = handleSendChannelStatusChange;
sendChannel.onclose = handleSendChannelStatusChange;
let remoteConnection = new RTCPeerConnection();
remoteConnection.ondatachannel = receiveChannelCallback;
localConnection.onicecandidate = (e) => !e.candidate ||
remoteConnection.addIceCandidate(e.candidate).catch(handleAddCandidateError);
remoteConnection.onicecandidate = (e) => !e.candidate ||
localConnection.addIceCandidate(e.candidate).catch(handleAddCandidateError);
function handleCreateDescriptionError(error) {
console.log(`Unable to create an offer: ${error.toString()}`);
}
function handleLocalAddCandidateSuccess() {
console.log('handleLocalAddCandidateSuccess');
}
function handleRemoteAddCandidateSuccess() {
console.log('handleRemoteAddCandidateSuccess');
}
function handleAddCandidateError() {
console.log("Oh noes! addICECandidate failed!");
}
localConnection
.createOffer()
.then((offer) => localConnection.setLocalDescription(offer))
.then(() => remoteConnection.setRemoteDescription(localConnection.localDescription))
.then(() => remoteConnection.createAnswer())
.then((answer) => remoteConnection.setLocalDescription(answer))
.then(() => localConnection.setRemoteDescription(remoteConnection.localDescription))
.catch(handleCreateDescriptionError);
function handleReceiveChannelStatusChange(event) {
let receiveChannel = event.channel;
if (receiveChannel) {
console.log(`Receive channel's status has changed to ${receiveChannel.readyState}`);
}
}
function receiveChannelCallback(event) {
let receiveChannel = event.channel;
receiveChannel.onmessage = handleReceiveMessage;
receiveChannel.onopen = handleReceiveChannelStatusChange;
receiveChannel.onclose = handleReceiveChannelStatusChange;
}
function sendMessage(message) {
sendChannel.send(message);
}
function handleReceiveMessage(event) {
console.log(event.data);
}
//# sourceMappingURL=webRTC.js.map

1
webRTC.js.map Normal file
View File

@@ -0,0 +1 @@
{"version":3,"file":"webRTC.js","sourceRoot":"","sources":["src/webRTC.ts"],"names":[],"mappings":";AAAA,MAAM,MAAM,GAAG;IACX,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,yBAAyB,EAAE,CAAC;CACpD,CAAC;AAEF,IAAI,eAAe,GAAG,IAAI,iBAAiB,EAAE,CAAC;AAE9C,SAAS,6BAA6B;IAClC,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;AAC/C,CAAC;AAED,IAAI,WAAW,GAAG,eAAe,CAAC,iBAAiB,CAAC,aAAa,CAAC,CAAC;AACnE,WAAW,CAAC,MAAM,GAAG,6BAA6B,CAAC;AACnD,WAAW,CAAC,OAAO,GAAG,6BAA6B,CAAC;AAGpD,IAAI,gBAAgB,GAAG,IAAI,iBAAiB,EAAE,CAAC;AAC/C,gBAAgB,CAAC,aAAa,GAAG,sBAAsB,CAAC;AAGxD,eAAe,CAAC,cAAc,GAAG,CAAC,CAAK,EAAE,EAAE,CACvC,CAAC,CAAC,CAAC,SAAS;IACZ,gBAAgB,CAAC,eAAe,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;AAEjF,gBAAgB,CAAC,cAAc,GAAG,CAAC,CAAC,EAAE,EAAE,CACpC,CAAC,CAAC,CAAC,SAAS;IACZ,eAAe,CAAC,eAAe,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;AAIhF,SAAS,4BAA4B,CAAC,KAAS;IAC3C,OAAO,CAAC,GAAG,CAAC,8BAA8B,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;AAChE,CAAC;AAED,SAAS,8BAA8B;IACrC,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;AAChD,CAAC;AAED,SAAS,+BAA+B;IACtC,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;AAEjD,CAAC;AAED,SAAS,uBAAuB;IAC9B,OAAO,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC;AAClD,CAAC;AAEH,eAAe;KACZ,WAAW,EAAE;KACb,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,eAAe,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC;KAC3D,IAAI,CAAC,GAAG,EAAE,CACT,gBAAgB,CAAC,oBAAoB,CAAC,eAAe,CAAC,gBAA6C,CAAC,CACrG;KACA,IAAI,CAAC,GAAG,EAAE,CAAC,gBAAgB,CAAC,YAAY,EAAE,CAAC;KAC3C,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,gBAAgB,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC;KAC9D,IAAI,CAAC,GAAG,EAAE,CACT,eAAe,CAAC,oBAAoB,CAAC,gBAAgB,CAAC,gBAA6C,CAAC,CACrG;KACA,KAAK,CAAC,4BAA4B,CAAC,CAAC;AAErC,SAAS,gCAAgC,CAAC,KAAS;IACjD,IAAI,cAAc,GAAG,KAAK,CAAC,OAAO,CAAC;IAEnC,IAAI,cAAc,EAAE,CAAC;QACnB,OAAO,CAAC,GAAG,CACT,2CAA2C,cAAc,CAAC,UAAU,EAAE,CACvE,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,sBAAsB,CAAC,KAAS;IACvC,IAAI,cAAc,GAAG,KAAK,CAAC,OAAO,CAAC;IACnC,cAAc,CAAC,SAAS,GAAG,oBAAoB,CAAC;IAChD,cAAc,CAAC,MAAM,GAAG,gCAAgC,CAAC;IACzD,cAAc,CAAC,OAAO,GAAG,gCAAgC,CAAC;AAC5D,CAAC;AAED,SAAS,WAAW,CAAC,OAAc;IACjC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AAC5B,CAAC;AAED,SAAS,oBAAoB,CAAC,KAAS;IACrC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}