add deno run script
This commit is contained in:
294
static/main.js
294
static/main.js
@@ -115,6 +115,7 @@ function log(message) {
|
||||
if (logLines.length > 10) {
|
||||
logLines = logLines.slice(logLines.length - logLength);
|
||||
}
|
||||
renderLog();
|
||||
}
|
||||
function generateID() {
|
||||
if (self.crypto.hasOwnProperty("randomUUID")) {
|
||||
@@ -201,6 +202,53 @@ async function compressString(input) {
|
||||
return new Uint8Array(compressedArray);
|
||||
}
|
||||
class wsConnection {
|
||||
constructor(userID, peerID, IDsToSync) {
|
||||
this.websocket = null;
|
||||
this.userID = "";
|
||||
this.peerID = "";
|
||||
this.websocketPingInterval = 0;
|
||||
this.helloRefreshInterval = 0;
|
||||
this.retry = 10;
|
||||
this.state = 'disconnected';
|
||||
// peers: Map<string, string[]> = new Map();
|
||||
this.messageHandlers = new Map();
|
||||
this.peerMessageHandlers = new Map();
|
||||
this.seenPeers = new Map();
|
||||
// static async compressArrayBuffer(data: ArrayBuffer): Promise<ArrayBuffer> {
|
||||
// const compressionStream = new CompressionStream('gzip'); // You can also use 'deflate', 'deflate-raw', etc.
|
||||
// const compressedStream = new Response(
|
||||
// new Blob([data]).stream().pipeThrough(compressionStream)
|
||||
// );
|
||||
// const compressedArrayBuffer = await compressedStream.arrayBuffer();
|
||||
// return compressedArrayBuffer;
|
||||
// }
|
||||
this.postBlockList = new Set([
|
||||
'1c71f53c-c467-48e4-bc8c-39005b37c0d5',
|
||||
'64203497-f77b-40d6-9e76-34d17372e72a',
|
||||
'243130d8-4a41-471e-8898-5075f1bd7aec',
|
||||
'e01eff89-5100-4b35-af4c-1c1bcb007dd0',
|
||||
'194696a2-d850-4bb0-98f7-47416b3d1662',
|
||||
'f6b21eb1-a0ff-435b-8efc-6a3dd70c0dca',
|
||||
'dd1d92aa-aa24-4166-a925-94ba072a9048'
|
||||
]);
|
||||
this.userBlockList = new Set([
|
||||
'5d63f0b2-a842-41bf-bf06-e0e4f6369271',
|
||||
'5f1b85c4-b14c-454c-8df1-2cacc93f8a77',
|
||||
// 'bba3ad24-9181-4e22-90c8-c265c80873ea'
|
||||
]);
|
||||
this.userID = userID;
|
||||
this.peerID = peerID;
|
||||
this.UserIDsToSync = new Set(IDsToSync);
|
||||
this.messageHandlers.set('hello', this.helloResponseHandler.bind(this));
|
||||
this.messageHandlers.set('pong', this.pongHandler);
|
||||
this.messageHandlers.set('peer_message', this.peerMessageHandler.bind(this));
|
||||
this.peerMessageHandlers.set('get_post_ids_for_user', this.getPostIdsForUserHandler.bind(this));
|
||||
this.peerMessageHandlers.set('get_post_ids_for_user_response', this.getPostIdsForUserResponseHandler.bind(this));
|
||||
this.peerMessageHandlers.set('get_posts_for_user', this.getPostsForUserHandler.bind(this));
|
||||
this.peerMessageHandlers.set('get_posts_for_user_response', this.getPostsForUserReponseHandler.bind(this));
|
||||
window.addEventListener('beforeunload', () => this.disconnect());
|
||||
this.connect();
|
||||
}
|
||||
async send(message) {
|
||||
let json = "";
|
||||
try {
|
||||
@@ -213,46 +261,6 @@ class wsConnection {
|
||||
// log(`ws->${json.slice(0, 240)}`)
|
||||
this.websocket.send(json);
|
||||
}
|
||||
helloResponseHandler(data) {
|
||||
let users = [];
|
||||
let receivedUsers = Object.entries(data.userPeers);
|
||||
log(`Net: got ${receivedUsers.length} users from bootstrap peer.`);
|
||||
try {
|
||||
let preferentialID = app.getPreferentialID();
|
||||
let currentUserPeers = data.userPeers[preferentialID];
|
||||
users.push([preferentialID, currentUserPeers]);
|
||||
delete data.userPeers[preferentialID];
|
||||
}
|
||||
catch (e) {
|
||||
console.log('helloResponseHandler', e);
|
||||
}
|
||||
let getAllUsers = app.router.route !== App.Route.USER;
|
||||
if (getAllUsers) {
|
||||
users = [...users, ...Object.entries(data.userPeers)];
|
||||
}
|
||||
// log(`Net: got ${users.length} users from bootstrap peer. \n${users.map((user)=>user[0]).join('\n')}`)
|
||||
for (let [userID, peerIDs] of users) {
|
||||
if (this.userBlockList.has(userID)) {
|
||||
console.log("Skipping user on blocklist:", userID);
|
||||
continue;
|
||||
}
|
||||
// this.peers.set(userID, [...peerIDs]);
|
||||
for (let peerID of [...peerIDs]) {
|
||||
if (peerID === this.peerID) {
|
||||
continue;
|
||||
}
|
||||
log(`Net: Req post IDs for user ${logID(userID)} from peer ${logID(peerID)}`);
|
||||
this.send({
|
||||
type: "peer_message",
|
||||
from: this.peerID,
|
||||
from_username: app.username,
|
||||
from_peername: app.peername,
|
||||
to: peerID,
|
||||
message: { type: "get_post_ids_for_user", user_id: userID }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
pongHandler(data) {
|
||||
}
|
||||
async getPostIdsForUserResponseHandler(data) {
|
||||
@@ -341,7 +349,7 @@ class wsConnection {
|
||||
console.log(`Net: got ${message.posts.length} posts for user ${logID(message.user_id)} from peer ${logID(data.from)}`);
|
||||
for (let post of message.posts) {
|
||||
// HACK: Some posts have insanely large images, so I'm gonna skip them.
|
||||
// If we supported delete then we we could delete these posts in a sensible way.
|
||||
// Once we support delete then we we could delete these posts in a sensible way.
|
||||
if (this.postBlockList.has(post.post_id)) {
|
||||
log(`Skipping blocked post: ${post.post_id}`);
|
||||
continue;
|
||||
@@ -359,7 +367,7 @@ class wsConnection {
|
||||
await mergeDataArray(message.user_id, data.message.posts);
|
||||
let receiveTime = app.timerDelta();
|
||||
log(`getPostsForUserReponseHandler receive took: ${receiveTime.toFixed(2)}ms`);
|
||||
if (message.user_id === app.getPreferentialID()) {
|
||||
if (message.user_id === app.getPreferentialUserID() || app.following.has(message.user_id)) {
|
||||
app.render();
|
||||
}
|
||||
}
|
||||
@@ -375,12 +383,55 @@ class wsConnection {
|
||||
handler(data);
|
||||
}
|
||||
async sendHello() {
|
||||
let knownUsers = [...(await indexedDB.databases())].map((db) => db.name?.replace('user_', ''));
|
||||
knownUsers = knownUsers.filter((userID) => userID && !this.userBlockList.has(userID));
|
||||
knownUsers = knownUsers.filter(async (userID) => userID && (await getAllIds(userID)).length > 0);
|
||||
// TODO only get users you're following here. ✅
|
||||
let knownUsers = [...(await indexedDB.databases())].map(db => db.name?.replace('user_', '')).filter(userID => userID !== undefined);
|
||||
knownUsers = knownUsers
|
||||
.filter(userID => this.UserIDsToSync.has(userID))
|
||||
.filter(userID => !this.userBlockList.has(userID))
|
||||
.filter(async (userID) => (await getAllIds(userID)).length > 0); // TODO getting all the IDs is unecessary, replace it with a test to get a single ID.
|
||||
console.log('Net: Sending known users', knownUsers.map(userID => logID(userID ?? "")));
|
||||
return await this.send({ type: "hello", user_id: this.userID, user_name: app.username, peer_id: this.peerID, peer_name: app.peername, known_users: knownUsers });
|
||||
}
|
||||
helloResponseHandler(data) {
|
||||
let users = [];
|
||||
let receivedUsers = Object.entries(data.userPeers);
|
||||
log(`Net: got ${receivedUsers.length} users from bootstrap peer.`);
|
||||
try {
|
||||
let preferentialUserID = app.getPreferentialUserID();
|
||||
let currentUserPeers = data.userPeers[preferentialUserID];
|
||||
users.push([preferentialUserID, currentUserPeers]);
|
||||
delete data.userPeers[preferentialUserID];
|
||||
}
|
||||
catch (e) {
|
||||
console.log('helloResponseHandler', e);
|
||||
}
|
||||
let getAllUsers = app.router.route !== App.Route.USER;
|
||||
if (getAllUsers) {
|
||||
users = [...users, ...Object.entries(data.userPeers).filter(userID => this.UserIDsToSync.has(userID[0]))];
|
||||
}
|
||||
// log(`Net: got ${users.length} users from bootstrap peer. \n${users.map((user)=>user[0]).join('\n')}`)
|
||||
for (let [userID, peerIDs] of users) {
|
||||
if (this.userBlockList.has(userID)) {
|
||||
console.log("Skipping user on blocklist:", userID);
|
||||
continue;
|
||||
}
|
||||
// this.peers.set(userID, [...peerIDs]);
|
||||
for (let peerID of [...peerIDs]) {
|
||||
if (peerID === this.peerID) {
|
||||
continue;
|
||||
}
|
||||
log(`Net: Req post IDs for user ${logID(userID)} from peer ${logID(peerID)}`);
|
||||
this.send({
|
||||
type: "peer_message",
|
||||
from: this.peerID,
|
||||
from_username: app.username,
|
||||
from_peername: app.peername,
|
||||
to: peerID,
|
||||
message: { type: "get_post_ids_for_user", user_id: userID }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
connect() {
|
||||
if (this.websocket?.readyState === WebSocket.OPEN) {
|
||||
return;
|
||||
@@ -443,54 +494,6 @@ class wsConnection {
|
||||
disconnect() {
|
||||
this.websocket?.close();
|
||||
}
|
||||
constructor(userID, peerID) {
|
||||
this.websocket = null;
|
||||
this.userID = "";
|
||||
this.peerID = "";
|
||||
this.websocketPingInterval = 0;
|
||||
this.helloRefreshInterval = 0;
|
||||
this.retry = 10;
|
||||
this.state = 'disconnected';
|
||||
// peers: Map<string, string[]> = new Map();
|
||||
this.messageHandlers = new Map();
|
||||
this.peerMessageHandlers = new Map();
|
||||
this.seenPeers = new Map();
|
||||
// static async compressArrayBuffer(data: ArrayBuffer): Promise<ArrayBuffer> {
|
||||
// const compressionStream = new CompressionStream('gzip'); // You can also use 'deflate', 'deflate-raw', etc.
|
||||
// const compressedStream = new Response(
|
||||
// new Blob([data]).stream().pipeThrough(compressionStream)
|
||||
// );
|
||||
// const compressedArrayBuffer = await compressedStream.arrayBuffer();
|
||||
// return compressedArrayBuffer;
|
||||
// }
|
||||
this.postBlockList = new Set([
|
||||
'1c71f53c-c467-48e4-bc8c-39005b37c0d5',
|
||||
'64203497-f77b-40d6-9e76-34d17372e72a',
|
||||
'243130d8-4a41-471e-8898-5075f1bd7aec',
|
||||
'e01eff89-5100-4b35-af4c-1c1bcb007dd0',
|
||||
'194696a2-d850-4bb0-98f7-47416b3d1662',
|
||||
'f6b21eb1-a0ff-435b-8efc-6a3dd70c0dca',
|
||||
'dd1d92aa-aa24-4166-a925-94ba072a9048'
|
||||
]);
|
||||
this.userBlockList = new Set([
|
||||
'5d63f0b2-a842-41bf-bf06-e0e4f6369271',
|
||||
'5f1b85c4-b14c-454c-8df1-2cacc93f8a77',
|
||||
'bba3ad24-9181-4e22-90c8-c265c80873ea'
|
||||
]);
|
||||
this.userID = userID;
|
||||
this.peerID = peerID;
|
||||
this.messageHandlers.set('hello', this.helloResponseHandler.bind(this));
|
||||
this.messageHandlers.set('pong', this.pongHandler);
|
||||
this.messageHandlers.set('peer_message', this.peerMessageHandler.bind(this));
|
||||
this.peerMessageHandlers.set('get_post_ids_for_user', this.getPostIdsForUserHandler.bind(this));
|
||||
this.peerMessageHandlers.set('get_post_ids_for_user_response', this.getPostIdsForUserResponseHandler.bind(this));
|
||||
this.peerMessageHandlers.set('get_posts_for_user', this.getPostsForUserHandler.bind(this));
|
||||
this.peerMessageHandlers.set('get_posts_for_user_response', this.getPostsForUserReponseHandler.bind(this));
|
||||
this.connect();
|
||||
if (!this.websocket) {
|
||||
// set a timer and retry?
|
||||
}
|
||||
}
|
||||
}
|
||||
class App {
|
||||
constructor() {
|
||||
@@ -524,7 +527,7 @@ class App {
|
||||
mediaID: ''
|
||||
};
|
||||
}
|
||||
getPreferentialID() {
|
||||
getPreferentialUserID() {
|
||||
return this.router.userID.length !== 0 ? this.router.userID : this.userID;
|
||||
}
|
||||
initMarkdown() {
|
||||
@@ -576,6 +579,18 @@ class App {
|
||||
}
|
||||
return fullText;
|
||||
}
|
||||
downloadJson(data, filename = 'data.json') {
|
||||
const jsonString = JSON.stringify(data);
|
||||
const blob = new Blob([jsonString], { type: 'application/json' });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
}
|
||||
async exportPostsForUser(userID) {
|
||||
let posts = await getAllData(userID);
|
||||
let output = [];
|
||||
@@ -587,8 +602,7 @@ class App {
|
||||
}
|
||||
output.push(newPost);
|
||||
}
|
||||
let json = JSON.stringify(output);
|
||||
console.log(json);
|
||||
this.downloadJson(output, `ddln_${this.username}_export`);
|
||||
}
|
||||
async importTweetArchive(userID, tweetArchive) {
|
||||
log("Importing tweet archive");
|
||||
@@ -847,10 +861,37 @@ class App {
|
||||
reader.readAsText(file);
|
||||
});
|
||||
}
|
||||
async lazyCreateQRCode() {
|
||||
if (this.qrcode != null) {
|
||||
return;
|
||||
}
|
||||
this.qrcode = await new QRCode(document.getElementById('qrcode'), {
|
||||
text: this.connectURL,
|
||||
width: 150,
|
||||
height: 150,
|
||||
colorDark: "#000000",
|
||||
colorLight: "#ffffff",
|
||||
correctLevel: QRCode.CorrectLevel.H
|
||||
});
|
||||
}
|
||||
showInfo() {
|
||||
let infoElement = document.getElementById('info');
|
||||
if (infoElement === null) {
|
||||
return;
|
||||
}
|
||||
infoElement.style.display == 'none' ? infoElement.style.display = 'block' : infoElement.style.display = 'none';
|
||||
logVisible = infoElement.style.display == 'block';
|
||||
renderLog();
|
||||
this.lazyCreateQRCode();
|
||||
document.querySelector('#qrcode > img').classList.add('qrcode_image');
|
||||
document.querySelector('#qrcode > canvas').classList.add('qrcode_image');
|
||||
this.showLog = true;
|
||||
}
|
||||
initButtons(userID, posts, registration) {
|
||||
// let font1Button = document.getElementById("button_font1") as HTMLButtonElement;
|
||||
// let font2Button = document.getElementById("button_font2") as HTMLButtonElement;
|
||||
let importTweetsButton = document.getElementById("import_tweets");
|
||||
let exportButton = document.getElementById("export_button");
|
||||
let clearPostsButton = document.getElementById("clear_posts");
|
||||
let updateApp = document.getElementById("update_app");
|
||||
let ddlnLogoButton = document.getElementById('ddln_logo_button');
|
||||
@@ -858,6 +899,7 @@ class App {
|
||||
let filePickerLabel = document.getElementById('file_input_label');
|
||||
let filePicker = document.getElementById('file_input');
|
||||
let toggleDark = document.getElementById('toggle_dark');
|
||||
exportButton.addEventListener('click', async (e) => await this.exportPostsForUser(this.userID));
|
||||
toggleDark.addEventListener('click', () => {
|
||||
document.documentElement.style.setProperty('--main-bg-color', 'white');
|
||||
document.documentElement.style.setProperty('--main-fg-color', 'black');
|
||||
@@ -912,27 +954,8 @@ class App {
|
||||
updateApp.addEventListener("click", () => {
|
||||
registration?.active?.postMessage({ type: "update_app" });
|
||||
});
|
||||
let infoElement = document.getElementById('info');
|
||||
if (infoElement === null) {
|
||||
return;
|
||||
}
|
||||
ddlnLogoButton.addEventListener('click', async () => {
|
||||
infoElement.style.display == 'none' ? infoElement.style.display = 'block' : infoElement.style.display = 'none';
|
||||
logVisible = infoElement.style.display == 'block';
|
||||
renderLog();
|
||||
if (this.qrcode != null) {
|
||||
return;
|
||||
}
|
||||
this.qrcode = await new QRCode(document.getElementById('qrcode'), {
|
||||
text: this.connectURL,
|
||||
width: 150,
|
||||
height: 150,
|
||||
colorDark: "#000000",
|
||||
colorLight: "#ffffff",
|
||||
correctLevel: QRCode.CorrectLevel.H
|
||||
});
|
||||
document.querySelector('#qrcode > img').classList.add('qrcode_image');
|
||||
document.querySelector('#qrcode > canvas').classList.add('qrcode_image');
|
||||
this.showInfo();
|
||||
});
|
||||
}
|
||||
async getPostsForFeed() {
|
||||
@@ -960,6 +983,12 @@ class App {
|
||||
'8f6802be-c3b6-46c1-969c-5f90cbe01479', // Fiona
|
||||
];
|
||||
}
|
||||
if (userID === '05a495a0-0dd8-4186-94c3-b8309ba6fc4c') {
|
||||
return [
|
||||
'b38b623c-c3fa-4351-9cab-50233c99fa4e',
|
||||
'a0e42390-08b5-4b07-bc2b-787f8e5f1297', // BMO
|
||||
];
|
||||
}
|
||||
return ['a0e42390-08b5-4b07-bc2b-787f8e5f1297']; // Follow BMO by default :)
|
||||
}
|
||||
async loadPostsFromStorage(userID, postID) {
|
||||
@@ -983,7 +1012,7 @@ class App {
|
||||
if (knownUsers.length === 0) {
|
||||
return;
|
||||
}
|
||||
let preferredId = app.getPreferentialID();
|
||||
let preferredId = app.getPreferentialUserID();
|
||||
for (let userID of knownUsers) {
|
||||
if (userID === preferredId) {
|
||||
continue;
|
||||
@@ -1081,7 +1110,7 @@ class App {
|
||||
console.log(`create viz network took ${this.timerDelta()}ms`);
|
||||
}
|
||||
async main() {
|
||||
// await this.exportPostsForUser('bba3ad24-9181-4e22-90c8-c265c80873ea');
|
||||
// await this.exportPostsForUser('b38b623c-c3fa-4351-9cab-50233c99fa4e');
|
||||
// Get initial state and route from URL and user agent etc
|
||||
// Set local state (userid etc) based on that.
|
||||
// Init libraries
|
||||
@@ -1102,10 +1131,11 @@ class App {
|
||||
this.peername = this.getPeername();
|
||||
this.userID = this.getUserID();
|
||||
this.username = this.getUsername();
|
||||
this.connectURL = `https://${document.location.hostname}/connect/${this.userID}`;
|
||||
document.getElementById('connectURL').innerHTML = `<a href="${this.connectURL}">connect</a>`;
|
||||
let urlParams = (new URL(window.location.href)).searchParams;
|
||||
if (urlParams.has('log')) {
|
||||
document.getElementById('info').style.display = "block";
|
||||
this.showLog = true;
|
||||
this.showInfo();
|
||||
}
|
||||
if (urlParams.has('headless')) {
|
||||
this.isHeadless = true;
|
||||
@@ -1146,12 +1176,13 @@ class App {
|
||||
document.getElementById('user_id').innerText = `user_id:${this.userID}`;
|
||||
document.getElementById('peer_id').innerText = `peer_id:${this.peerID}`;
|
||||
this.initButtons(this.userID, this.posts, registration);
|
||||
this.connectURL = `https://${document.location.hostname}/connect/${this.userID}`;
|
||||
document.getElementById('connectURL').innerHTML = `<a href="${this.connectURL}">connect</a>`;
|
||||
log(`username:${this.username} user:${this.userID} peername:${this.peername} peer:${this.peerID}`);
|
||||
await this.purgeEmptyUsers();
|
||||
this.websocket = new wsConnection(this.userID, this.peerID);
|
||||
window.addEventListener('beforeunload', () => { this.websocket?.disconnect(); });
|
||||
let IDsToSync = this.following;
|
||||
if (this.router.route === App.Route.USER) {
|
||||
IDsToSync = new Set([this.router.userID]);
|
||||
}
|
||||
this.websocket = new wsConnection(this.userID, this.peerID, IDsToSync);
|
||||
this.initOffline(this.websocket);
|
||||
// this.createNetworkViz();
|
||||
// const client = new WebTorrent()
|
||||
@@ -1167,6 +1198,9 @@ class App {
|
||||
// file.appendTo(document.getElementById('torrent-content'));
|
||||
// })
|
||||
}
|
||||
renderWelcome(contentDiv) {
|
||||
contentDiv.innerHTML = `<div style="font-size:32px">Doing complicated shennanigans to load posts for you so just hang on a minute, ok!?</div>`;
|
||||
}
|
||||
async render() {
|
||||
if (this.isHeadless) {
|
||||
console.log('Headless so skipping render...');
|
||||
@@ -1218,7 +1252,7 @@ class App {
|
||||
throw new Error();
|
||||
}
|
||||
if (this.posts.length === 0) {
|
||||
contentDiv.innerHTML = `<div style="font-size:32px">Doing complicated shennanigans to load posts for you so just hang on a minute, ok!?</div>`;
|
||||
this.renderWelcome(contentDiv);
|
||||
return;
|
||||
}
|
||||
// let existingPostSet = new Set(existingPosts.map(post => post.post_id));
|
||||
@@ -1244,8 +1278,8 @@ class App {
|
||||
let postData = this.posts[i];
|
||||
// this.postsSet.add(postData);
|
||||
// return promises for all image loads and await those.
|
||||
let post = this.renderPost(postData);
|
||||
this.renderedPosts.set(postData.post_id, post);
|
||||
let post = this.renderPost(postData.data);
|
||||
// this.renderedPosts.set(postData.post_id, post);
|
||||
if (post) {
|
||||
fragment.appendChild(post);
|
||||
count++;
|
||||
@@ -1262,9 +1296,9 @@ class App {
|
||||
log(`render took: ${renderTime.toFixed(2)}ms`);
|
||||
performance.mark("render-end");
|
||||
performance.measure('render-time', 'render-start', 'render-end');
|
||||
if (performance?.memory) {
|
||||
log(`memory used: ${(performance.memory.usedJSHeapSize / 1024 / 1024).toFixed(2)}Mb`);
|
||||
}
|
||||
// if ((performance as any)?.memory) {
|
||||
// log(`memory used: ${((performance as any).memory.usedJSHeapSize / 1024 / 1024).toFixed(2)}Mb`)
|
||||
// }
|
||||
}
|
||||
async deletePost(userID, postID) {
|
||||
deleteData(userID, postID);
|
||||
|
||||
Reference in New Issue
Block a user