From dcd79702dbad26cba3918473142e4bbf4051f670 Mon Sep 17 00:00:00 2001 From: Peter Stuifzand Date: Wed, 29 Aug 2018 20:29:54 +0200 Subject: [PATCH] Fetch microsub endpoint from user (and use it) --- package.json | 1 + src/components/LoginModal.vue | 49 ++++++++++------ src/helpers/rel-scraper.js | 103 ++++++++++++++++++++++++++++++++++ src/store.js | 8 +-- 4 files changed, 139 insertions(+), 22 deletions(-) create mode 100644 src/helpers/rel-scraper.js diff --git a/package.json b/package.json index a327b9b..740516b 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "lint": "vue-cli-service lint" }, "dependencies": { + "isomorphic-fetch": "^2.2.1", "micropub-helper": "^1.4.0", "node-sass": "^4.9.3", "vue": "^2.5.17", diff --git a/src/components/LoginModal.vue b/src/components/LoginModal.vue index 44415e5..c977132 100644 --- a/src/components/LoginModal.vue +++ b/src/components/LoginModal.vue @@ -26,6 +26,7 @@ diff --git a/src/helpers/rel-scraper.js b/src/helpers/rel-scraper.js new file mode 100644 index 0000000..68a5d5f --- /dev/null +++ b/src/helpers/rel-scraper.js @@ -0,0 +1,103 @@ +import fetch from 'isomorphic-fetch' + +export default function(url) { + let baseUrl = url; + let endpoints = { + micropub: null, + microsub: null, + authorization_endpoint: null, + token_endpoint: null, + media_endpoint: null, + }; + + return new Promise((resolve, reject) => { + fetch(url) + .then(res => { + if (!res.ok) { + return reject('Error getting page'); + } + baseUrl = res.url; + + // Check for endpoints in headers + const linkHeaders = res.headers.get('link'); + if (linkHeaders) { + const links = linkHeaders.split(','); + links.forEach(link => { + Object.keys(endpoints).forEach(key => { + const rel = link.match(/rel=("([^"]*)"|([^,"<]+))/); + if ( + rel && + rel[1] && + (' ' + rel[1].toLowerCase() + ' ').indexOf(' ' + key + ' ') >= 0 + ) { + const linkValues = link.match(/[^<>|\s]+/g); + if (linkValues && linkValues[0]) { + let endpointUrl = linkValues[0]; + endpointUrl = new URL(endpointUrl, url).toString(); + endpoints[key] = endpointUrl; + } + } + }); + }); + } + return res.text(); + }) + .then(html => { + // Get rel links + const rels = htmlScraper(html, baseUrl); + + // Save necessary endpoints. + if (rels) { + Object.keys(endpoints).forEach(key => { + if (rels[key] && rels[key][0]) { + endpoints[key] = rels[key][0]; + } + }); + } + return resolve(endpoints); + }) + .catch(err => { + // eslint-disable-next-line + console.log(err); + reject('Error fetching url'); + }); + }); +} + +function htmlScraper(htmlString, url) { + let rels = {}; + let baseUrl = url; + + const doc = new DOMParser().parseFromString(htmlString, 'text/html'); + const baseEl = doc.querySelector('base[href]'); + const relEls = doc.querySelectorAll('[rel][href]'); + + if (baseEl) { + const value = baseEl.getAttribute('href'); + const url = new URL(value, url); + baseUrl = url.toString(); + } + + if (relEls.length) { + relEls.forEach(relEl => { + const names = relEl + .getAttribute('rel') + .toLowerCase() + .split('\\s+'); + const value = relEl.getAttribute('href'); + if (names.length && value !== null) { + names.forEach(name => { + if (!rels[name]) { + rels[name] = []; + } + const url = new URL(value, baseUrl).toString(); + if (rels[name].indexOf(url) === -1) { + rels[name].push(url) + } + }) + } + }) + } + + return rels +} diff --git a/src/store.js b/src/store.js index d3cffd2..13d4670 100644 --- a/src/store.js +++ b/src/store.js @@ -2,8 +2,6 @@ import Vue from 'vue' import Vuex from 'vuex' import Micropub from 'micropub-helper'; -const baseurl = "https://microsub.stuifzandapp.com/microsub" - Vue.use(Vuex) export default new Vuex.Store({ @@ -54,7 +52,7 @@ export default new Vuex.Store({ actions: { fetchChannels({commit}) { - fetch(baseurl + '?action=channels', { + fetch(this.state.microsubEndpoint + '?action=channels', { headers: { 'Authorization': 'Bearer ' + this.state.access_token } @@ -68,7 +66,7 @@ export default new Vuex.Store({ commit('clearTimeline', {channel: channel}) }, fetchTimeline({commit}, channel) { - let url = baseurl + '?action=timeline&channel=' + channel.uid + let url = this.state.microsubEndpoint + '?action=timeline&channel=' + channel.uid if (channel.after) { url += '&after=' + channel.after; } @@ -99,7 +97,7 @@ export default new Vuex.Store({ commit('newAccessToken', response) }, markRead(x, {channel, entry}) { - let url = baseurl + '?action=timeline&method=mark_read&channel=' + encodeURIComponent(channel) + '&entry=' + encodeURIComponent(entry); + let url = this.state.microsubEndpoint + '?action=timeline&method=mark_read&channel=' + encodeURIComponent(channel) + '&entry=' + encodeURIComponent(entry); return fetch(url, { method: 'POST', headers: {