diff --git a/src/lib/client/api.js b/src/lib/client/api.js
index c02517a..27f0e0b 100644
--- a/src/lib/client/api.js
+++ b/src/lib/client/api.js
@@ -1,4 +1,4 @@
-import { client } from '../client/client.js';
+import { Client } from '../client/client.js';
import { capabilities } from '../client/instance.js';
import Post from '../post.js';
import User from '../user/user.js';
@@ -31,23 +31,25 @@ export async function createApp(host) {
}
export function getOAuthUrl() {
- return `https://${get(client).instance.host}/oauth/authorize` +
- `?client_id=${get(client).app.id}` +
+ let client = get(Client.get());
+ return `https://${client.instance.host}/oauth/authorize` +
+ `?client_id=${client.app.id}` +
"&scope=read+write+push" +
`&redirect_uri=${location.origin}/callback` +
"&response_type=code";
}
export async function getToken(code) {
+ let client = get(Client.get());
let form = new FormData();
- form.append("client_id", get(client).app.id);
- form.append("client_secret", get(client).app.secret);
+ form.append("client_id", client.app.id);
+ form.append("client_secret", client.app.secret);
form.append("redirect_uri", `${location.origin}/callback`);
form.append("grant_type", "authorization_code");
form.append("code", code);
form.append("scope", "read write push");
- const res = await fetch(`https://${get(client).instance.host}/oauth/token`, {
+ const res = await fetch(`https://${client.instance.host}/oauth/token`, {
method: "POST",
body: form,
})
@@ -63,12 +65,13 @@ export async function getToken(code) {
}
export async function revokeToken() {
+ let client = get(Client.get());
let form = new FormData();
- form.append("client_id", get(client).app.id);
- form.append("client_secret", get(client).app.secret);
- form.append("token", get(client).app.token);
+ form.append("client_id", client.app.id);
+ form.append("client_secret", client.app.secret);
+ form.append("token", client.app.token);
- const res = await fetch(`https://${get(client).instance.host}/oauth/revoke`, {
+ const res = await fetch(`https://${client.instance.host}/oauth/revoke`, {
method: "POST",
body: form,
})
@@ -82,32 +85,34 @@ export async function revokeToken() {
}
export async function verifyCredentials() {
- let url = `https://${get(client).instance.host}/api/v1/accounts/verify_credentials`;
+ let client = get(Client.get());
+ let url = `https://${client.instance.host}/api/v1/accounts/verify_credentials`;
const data = await fetch(url, {
method: 'GET',
- headers: { "Authorization": "Bearer " + get(client).app.token }
+ headers: { "Authorization": "Bearer " + client.app.token }
}).then(res => res.json());
return data;
}
export async function getTimeline(last_post_id) {
- if (!get(client).instance || !get(client).app) return false;
- let url = `https://${get(client).instance.host}/api/v1/timelines/home`;
+ let client = get(Client.get());
+ let url = `https://${client.instance.host}/api/v1/timelines/home`;
if (last_post_id) url += "?max_id=" + last_post_id;
const data = await fetch(url, {
method: 'GET',
- headers: { "Authorization": "Bearer " + get(client).app.token }
+ headers: { "Authorization": "Bearer " + client.app.token }
}).then(res => res.json());
return data;
}
export async function getPost(post_id, ancestor_count) {
- let url = `https://${get(client).instance.host}/api/v1/statuses/${post_id}`;
+ let client = get(Client.get());
+ let url = `https://${client.instance.host}/api/v1/statuses/${post_id}`;
const data = await fetch(url, {
method: 'GET',
- headers: { "Authorization": "Bearer " + get(client).app.token }
+ headers: { "Authorization": "Bearer " + client.app.token }
}).then(res => { return res.ok ? res.json() : false });
if (data === false) return false;
@@ -115,10 +120,11 @@ export async function getPost(post_id, ancestor_count) {
}
export async function getPostContext(post_id) {
- let url = `https://${get(client).instance.host}/api/v1/statuses/${post_id}/context`;
+ let client = get(Client.get());
+ let url = `https://${client.instance.host}/api/v1/statuses/${post_id}/context`;
const data = await fetch(url, {
method: 'GET',
- headers: { "Authorization": "Bearer " + get(client).app.token }
+ headers: { "Authorization": "Bearer " + client.app.token }
}).then(res => { return res.ok ? res.json() : false });
if (data === false) return false;
@@ -126,10 +132,11 @@ export async function getPostContext(post_id) {
}
export async function boostPost(post_id) {
- let url = `https://${get(client).instance.host}/api/v1/statuses/${post_id}/reblog`;
+ let client = get(Client.get());
+ let url = `https://${client.instance.host}/api/v1/statuses/${post_id}/reblog`;
const data = await fetch(url, {
method: 'POST',
- headers: { "Authorization": "Bearer " + get(client).app.token }
+ headers: { "Authorization": "Bearer " + client.app.token }
}).then(res => { return res.ok ? res.json() : false });
if (data === false) return false;
@@ -137,10 +144,11 @@ export async function boostPost(post_id) {
}
export async function unboostPost(post_id) {
- let url = `https://${get(client).instance.host}/api/v1/statuses/${post_id}/unreblog`;
+ let client = get(Client.get());
+ let url = `https://${client.instance.host}/api/v1/statuses/${post_id}/unreblog`;
const data = await fetch(url, {
method: 'POST',
- headers: { "Authorization": "Bearer " + get(client).app.token }
+ headers: { "Authorization": "Bearer " + client.app.token }
}).then(res => { return res.ok ? res.json() : false });
if (data === false) return false;
@@ -148,10 +156,11 @@ export async function unboostPost(post_id) {
}
export async function favouritePost(post_id) {
- let url = `https://${get(client).instance.host}/api/v1/statuses/${post_id}/favourite`;
+ let client = get(Client.get());
+ let url = `https://${client.instance.host}/api/v1/statuses/${post_id}/favourite`;
const data = await fetch(url, {
method: 'POST',
- headers: { "Authorization": "Bearer " + get(client).app.token }
+ headers: { "Authorization": "Bearer " + client.app.token }
}).then(res => { return res.ok ? res.json() : false });
if (data === false) return false;
@@ -159,10 +168,11 @@ export async function favouritePost(post_id) {
}
export async function unfavouritePost(post_id) {
- let url = `https://${get(client).instance.host}/api/v1/statuses/${post_id}/unfavourite`;
+ let client = get(Client.get());
+ let url = `https://${client.instance.host}/api/v1/statuses/${post_id}/unfavourite`;
const data = await fetch(url, {
method: 'POST',
- headers: { "Authorization": "Bearer " + get(client).app.token }
+ headers: { "Authorization": "Bearer " + client.app.token }
}).then(res => { return res.ok ? res.json() : false });
if (data === false) return false;
@@ -175,10 +185,11 @@ export async function reactPost(post_id, shortcode) {
// to the default like emote.
// identical api calls on chuckya instances do not display
// this behaviour.
- let url = `https://${get(client).instance.host}/api/v1/statuses/${post_id}/react/${encodeURIComponent(shortcode)}`;
+ let client = get(Client.get());
+ let url = `https://${client.instance.host}/api/v1/statuses/${post_id}/react/${encodeURIComponent(shortcode)}`;
const data = await fetch(url, {
method: 'POST',
- headers: { "Authorization": "Bearer " + get(client).app.token }
+ headers: { "Authorization": "Bearer " + client.app.token }
}).then(res => { return res.ok ? res.json() : false });
if (data === false) return false;
@@ -186,23 +197,26 @@ export async function reactPost(post_id, shortcode) {
}
export async function unreactPost(post_id, shortcode) {
- let url = `https://${get(client).instance.host}/api/v1/statuses/${post_id}/unreact/${encodeURIComponent(shortcode)}`;
+ let client = get(Client.get());
+ let url = `https://${client.instance.host}/api/v1/statuses/${post_id}/unreact/${encodeURIComponent(shortcode)}`;
const data = await fetch(url, {
method: 'POST',
- headers: { "Authorization": "Bearer " + get(client).app.token }
+ headers: { "Authorization": "Bearer " + client.app.token }
}).then(res => { return res.ok ? res.json() : false });
if (data === false) return false;
return data;
}
-export async function parsePost(data, ancestor_count) {
+export async function parsePost(data, ancestor_count, with_context) {
+ let client = get(Client.get());
let post = new Post();
post.text = data.content;
post.reply = null;
- if ((data.in_reply_to_id || data.reply) &&
+ if (!with_context && // ancestor replies are handled in full later
+ (data.in_reply_to_id || data.reply) &&
ancestor_count !== 0
) {
const reply_data = data.reply || await getPost(data.in_reply_to_id, ancestor_count - 1);
@@ -211,9 +225,29 @@ export async function parsePost(data, ancestor_count) {
if (!reply_data) return false;
post.reply = await parsePost(reply_data, ancestor_count - 1, false);
}
-
post.boost = data.reblog ? await parsePost(data.reblog, 1, false) : null;
+ post.replies = [];
+ if (with_context) {
+ const replies_data = await getPostContext(data.id);
+ if (replies_data) {
+ // posts this is replying to
+ if (replies_data.ancestors) {
+ let head = post;
+ while (replies_data.ancestors.length > 0) {
+ head.reply = await parsePost(replies_data.ancestors.pop(), 0, false);
+ head = head.reply;
+ }
+ }
+ // posts in reply to this
+ if (replies_data.descendants) {
+ for (let i in replies_data.descendants) {
+ post.replies.push(await parsePost(replies_data.descendants[i], 0, false));
+ }
+ }
+ }
+ }
+
post.id = data.id;
post.created_at = new Date(data.created_at);
post.user = await parseUser(data.account);
@@ -241,7 +275,7 @@ export async function parsePost(data, ancestor_count) {
});
}
- if (data.reactions && get(client).instance.capabilities.includes(capabilities.REACTIONS)) {
+ if (data.reactions && client.instance.capabilities.includes(capabilities.REACTIONS)) {
post.reactions = parseReactions(data.reactions);
}
return post;
@@ -252,7 +286,8 @@ export async function parseUser(data) {
console.error("Attempted to parse user data but no data was provided");
return null;
}
- let user = await get(client).getCacheUser(data.id);
+ let client = get(Client.get());
+ let user = await client.getCacheUser(data.id);
if (user) return user;
// cache miss!
@@ -267,7 +302,7 @@ export async function parseUser(data) {
if (data.acct.includes('@'))
user.host = data.acct.split('@')[1];
else
- user.host = get(client).instance.host;
+ user.host = client.instance.host;
user.emojis = [];
data.emojis.forEach(emoji_data => {
@@ -277,11 +312,12 @@ export async function parseUser(data) {
user.emojis.push(parseEmoji(emoji_data));
});
- get(client).putCacheUser(user);
+ client.putCacheUser(user);
return user;
}
export function parseReactions(data) {
+ let client = get(Client.get());
let reactions = [];
data.forEach(reaction_data => {
let reaction = {
@@ -302,15 +338,16 @@ export function parseEmoji(data) {
data.host,
data.url,
);
- get(client).putCacheEmoji(emoji);
+ get(Client.get()).putCacheEmoji(emoji);
return emoji;
}
export async function getUser(user_id) {
- let url = `https://${get(client).instance.host}/api/v1/accounts/${user_id}`;
+ let client = get(Client.get());
+ let url = `https://${client.instance.host}/api/v1/accounts/${user_id}`;
const data = await fetch(url, {
method: 'GET',
- headers: { "Authorization": "Bearer " + get(client).app.token }
+ headers: { "Authorization": "Bearer " + client.app.token }
}).then(res => res.json());
const user = await parseUser(data);
diff --git a/src/lib/client/client.js b/src/lib/client/client.js
index 6e96f19..f4e3788 100644
--- a/src/lib/client/client.js
+++ b/src/lib/client/client.js
@@ -2,7 +2,7 @@ import { Instance, server_types } from './instance.js';
import * as api from './api.js';
import { get, writable } from 'svelte/store';
-export const client = writable(false);
+let client = writable(false);
const save_name = "campfire";
@@ -22,6 +22,15 @@ export class Client {
};
}
+ static get() {
+ let current = get(client);
+ if (current && current.app) return client;
+ let new_client = new Client();
+ new_client.load();
+ client.set(new_client);
+ return client;
+ }
+
async init(host) {
if (host.startsWith("https://")) host = host.substring(8);
const url = `https://${host}/api/v1/instance`;
@@ -67,30 +76,30 @@ export class Client {
console.error("Failed to obtain access token");
return false;
}
- return token;
+ this.app.token = token;
+ client.set(this);
}
async revokeToken() {
return await api.revokeToken();
}
- async getUser() {
- // already known
+ async verifyCredentials() {
if (this.user) return this.user;
-
- // cannot provide- not logged in
if (!this.app || !this.app.token) {
+ this.user = false;
return false;
}
-
- // logged in- attempt to retrieve using token
const data = await api.verifyCredentials();
if (!data) {
+ this.user = false;
return false;
}
- const user = await api.parseUser(data);
- console.log(`Logged in as @${user.username}@${user.host}`);
- return user;
+ await client.update(async c => {
+ c.user = await api.parseUser(data);
+ console.log(`Logged in as @${c.user.username}@${c.user.host}`);
+ });
+ return this.user;
}
async getTimeline(last_post_id) {
@@ -101,10 +110,6 @@ export class Client {
return await api.getPost(post_id, parent_replies, child_replies);
}
- async getPostContext(post_id) {
- return await api.getPostContext(post_id);
- }
-
async boostPost(post_id) {
return await api.boostPost(post_id);
}
@@ -194,7 +199,7 @@ export class Client {
console.warn("Failed to log out correctly; ditching the old tokens anyways.");
}
localStorage.removeItem(save_name);
- client.set(new Client());
+ client.set(false);
console.log("Logged out successfully.");
}
}
diff --git a/src/lib/emoji.js b/src/lib/emoji.js
index 89df2d1..4fdd161 100644
--- a/src/lib/emoji.js
+++ b/src/lib/emoji.js
@@ -1,4 +1,4 @@
-import { client } from './client/client.js';
+import { Client } from './client/client.js';
import { get } from 'svelte/store';
export const EMOJI_REGEX = /:[\w\-.]{0,32}@[\w\-.]{0,32}:/g;
@@ -33,7 +33,7 @@ export function parseText(text, host) {
let length = text.substring(index + 1).search(':');
if (length <= 0) return text;
let emoji_name = text.substring(index + 1, index + length + 1);
- let emoji = get(client).getEmoji(emoji_name + '@' + host);
+ let emoji = get(Client.get()).getEmoji(emoji_name + '@' + host);
if (emoji) {
return text.substring(0, index) + emoji.html +
@@ -46,7 +46,7 @@ export function parseText(text, host) {
export function parseOne(emoji_id) {
if (emoji_id == '❤') return '❤️'; // stupid heart unicode
if (EMOJI_REGEX.exec(':' + emoji_id + ':')) return emoji_id;
- let cached_emoji = get(client).getEmoji(emoji_id);
+ let cached_emoji = get(Client.get()).getEmoji(emoji_id);
if (!cached_emoji) return emoji_id;
return cached_emoji.html;
}
diff --git a/src/lib/timeline.js b/src/lib/timeline.js
index a9fcdbf..5858199 100644
--- a/src/lib/timeline.js
+++ b/src/lib/timeline.js
@@ -1,4 +1,4 @@
-import { client } from '$lib/client/client.js';
+import { Client } from '$lib/client/client.js';
import { get, writable } from 'svelte/store';
import { parsePost } from '$lib/client/api.js';
@@ -10,9 +10,11 @@ export async function getTimeline(clean) {
if (loading) return; // no spamming!!
loading = true;
+ let client = get(Client.get());
+
let timeline_data;
- if (clean || get(posts).length === 0) timeline_data = await get(client).getTimeline()
- else timeline_data = await get(client).getTimeline(get(posts)[get(posts).length - 1].id);
+ if (clean || get(posts).length === 0) timeline_data = await client.getTimeline()
+ else timeline_data = await client.getTimeline(get(posts)[get(posts).length - 1].id);
if (!timeline_data) {
console.error(`Failed to retrieve timeline.`);
diff --git a/src/lib/ui/Feed.svelte b/src/lib/ui/Feed.svelte
index d249165..bd3cef4 100644
--- a/src/lib/ui/Feed.svelte
+++ b/src/lib/ui/Feed.svelte
@@ -1,6 +1,10 @@
-Home
-
-