Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 101 additions & 89 deletions lib/siteAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,44 @@ const localForage = require('localforage')

module.exports = siteAdapter = {}

// IPv6/localhost-safe URL helpers -------------------------------------------
const isLoopbackHost = (s = '') => {
const host = s.toLowerCase();
return (
host === 'localhost' ||
host.endsWith('.localhost') ||
/^127(?:\.\d{1,3}){3}$/.test(host) || // 127/8
host === '::1' ||
host === '[::1]'
);
};

const normalizeSite = rawSite => {
const s = String(rawSite).trim();
if (s.startsWith('[')) return s; // already bracketed IPv6
const colonCount = (s.match(/:/g) || []).length;
if (colonCount >= 2) {
// raw IPv6, maybe with :port
const last = s.lastIndexOf(':');
if (last > 0 && last !== s.indexOf(':')) {
const host = s.slice(0, last);
const port = s.slice(last + 1);
return `[${host}]${port ? `:${port}` : ''}`;
}
return `[${s}]`;
}
return s; // host or host:port (IPv4/hostname)
};

const siteKey = rawSite => normalizeSite(rawSite);
const httpOriginFor = norm => `http://${norm}`;
const httpsOriginFor = norm => `https://${norm}`;
const proxyPathFor = norm => `/proxy/${encodeURIComponent(norm)}`;
const isLoopbackSite = norm => {
const host = norm.startsWith('[') ? norm.slice(1, norm.indexOf(']')) : norm.split(':')[0];
return isLoopbackHost(host);
};

// we save the site prefix once we have determined it,
const sitePrefix = {}
// and if the CORS request requires credentials...
Expand Down Expand Up @@ -65,71 +103,58 @@ const testWikiSite = function (url, good, bad) {
}

const findAdapterQ = queue(function (task, done) {
let testURL
const { site } = task
if (sitePrefix[site]) {
done(sitePrefix[site])
}
const { site } = task;
const norm = siteKey(site);

if (sitePrefix[site]) return done(sitePrefix[site]);

if (site.split('.').at(-1).split(':')[0] === 'localhost') {
testURL = `http://${site}/favicon.png`
let testURL;
if (isLoopbackSite(norm)) {
testURL = `${httpOriginFor(norm)}/favicon.png`; // prefer direct http for loopback
} else if (location.protocol === 'https:') {
testURL = `${proxyPathFor(norm)}/favicon.png`; // avoid mixed content
} else {
testURL = `//${site}/favicon.png`
testURL = `${httpOriginFor(norm)}/favicon.png`;
}

return testWikiSite(
testURL,
function () {
sitePrefix[site] = testURL.slice(0, -12)
done(testURL.slice(0, -12))
sitePrefix[site] = testURL.replace(/\/favicon\.png$/, '');
done(sitePrefix[site]);
},
function () {
switch (location.protocol) {
case 'http:':
testURL = `https://${site}/favicon.png`
testWikiSite(
testURL,
() => {
sitePrefix[site] = `https://${site}`
done(`https://${site}`)
},
() => {
sitePrefix[site] = ''
done('')
},
)
break
case 'https:':
testURL = `/proxy/${site}/favicon.png`
testWikiSite(
testURL,
() => {
sitePrefix[site] = `/proxy/${site}`
done(`/proxy/${site}`)
},
() => {
sitePrefix[site] = ''
done('')
},
)
break
default:
sitePrefix[site] = ''
done('')
if (location.protocol === 'https:') {
const alt = `${httpsOriginFor(norm)}/favicon.png`;
testWikiSite(
alt,
() => {
sitePrefix[site] = alt.replace(/\/favicon\.png$/, '');
done(sitePrefix[site]);
},
() => {
sitePrefix[site] = '';
done('');
}
);
} else {
sitePrefix[site] = '';
done('');
}
},
)
}, findQueueWorkers) // start with 8 process working on the queue
}
);
}, findQueueWorkers); // start with 8 workers

const findAdapter = (site, done) =>
routeStore
.getItem(site)
const findAdapter = (site, done) => {
const key = siteKey(site)
return routeStore
.getItem(key)
.then(function (value) {
// console.log "findAdapter: ", site, value
if (!value) {
findAdapterQ.push({ site }, function (prefix) {
sitePrefix[site] = prefix
routeStore
.setItem(site, prefix)
.setItem(key, prefix)
.then(() => done(prefix))
.catch(function (err) {
console.log('findAdapter setItem error: ', site, err)
Expand All @@ -146,6 +171,7 @@ const findAdapter = (site, done) =>
sitePrefix[site] = ''
done('')
})
}

siteAdapter.local = {
flag() {
Expand Down Expand Up @@ -323,6 +349,7 @@ siteAdapter.recycler = {
}

siteAdapter.site = function (site) {
const key = siteKey(site);
if (!site || site === window.location.host) {
return siteAdapter.origin
}
Expand Down Expand Up @@ -416,16 +443,10 @@ siteAdapter.site = function (site) {
console.log(`${site} is unreachable`)
} else {
console.log(`Prefix for ${site} is ${prefix}, about to fixup links`)
// add href to journal fork
const norm = siteKey(site)
const base = prefix.startsWith('/proxy/') ? httpOriginFor(norm) : prefix
$('a[target="' + site + '"]').each(function () {
let thisPrefix
if (/proxy/.test(prefix)) {
const thisSite = prefix.substring(7)
thisPrefix = `http://${thisSite}`
} else {
thisPrefix = prefix
}
$(this).attr('href', `${thisPrefix}/${$(this).data('slug')}.html`)
$(this).attr('href', `${base}/${$(this).data('slug')}.html`)
})
}
})
Expand All @@ -434,35 +455,26 @@ siteAdapter.site = function (site) {
},

getDirectURL(route) {
let thisPrefix, thisSite
if (sitePrefix[site] != null) {
if (sitePrefix[site] === '') {
console.log(`${site} is unreachable, can't link to ${route}`)
return ''
} else {
if (/proxy/.test(sitePrefix[site])) {
thisSite = sitePrefix[site].substring(7)
thisPrefix = `http://${thisSite}`
} else {
thisPrefix = sitePrefix[site]
}
return `${thisPrefix}/${route}`
const pref = sitePrefix[site]
const norm = siteKey(site)
const base = pref.startsWith('/proxy/') ? httpOriginFor(norm) : pref
return `${base}/${route}`
}
} else {
findAdapter(site, function (prefix) {
if (prefix === '') {
console.log(`${site} is unreachable`)
} else {
console.log(`Prefix for ${site} is ${prefix}, about to fixup links`)
// add href to journal fork
const norm = siteKey(site)
const base = prefix.startsWith('/proxy/') ? httpOriginFor(norm) : prefix
$('a[target="' + site + '"]').each(function () {
if (/proxy/.test(prefix)) {
thisSite = prefix.substring(7)
thisPrefix = `http://${thisSite}`
} else {
thisPrefix = prefix
}
$(this).attr('href', `${thisPrefix}/${$(this).data('slug')}.html`)
$(this).attr('href', `${base}/${$(this).data('slug')}.html`)
})
}
})
Expand All @@ -480,7 +492,7 @@ siteAdapter.site = function (site) {

var getContent = function (route, done) {
const url = `${sitePrefix[site]}/${route}`
const useCredentials = credentialsNeeded[site] || false
const useCredentials = credentialsNeeded[key] || false

return $.ajax({
type: 'GET',
Expand All @@ -492,15 +504,15 @@ siteAdapter.site = function (site) {
((route === 'system/sitemap.json' && Array.isArray(data) && data[0] === 'Login Required') ||
data.title === 'Login Required') &&
!url.includes('login-required') &&
credentialsNeeded[site] !== true
credentialsNeeded[key] !== true
) {
credentialsNeeded[site] = true
credentialsNeeded[key] = true;
getContent(route, function (err, page) {
if (!err) {
withCredsStore.setItem(site, true)
withCredsStore.setItem(key, true)
done(err, page)
} else {
credentialsNeeded[site] = false
credentialsNeeded[key] = false
done(err, page)
}
})
Expand Down Expand Up @@ -564,7 +576,7 @@ siteAdapter.site = function (site) {

var getContent = function (route, done) {
const url = `${sitePrefix[site]}/${route}`
const useCredentials = credentialsNeeded[site] || false
const useCredentials = credentialsNeeded[key] || false

return $.ajax({
type: 'GET',
Expand All @@ -575,15 +587,15 @@ siteAdapter.site = function (site) {
if (
data.title === 'Login Required' &&
!url.includes('login-required') &&
credentialsNeeded[site] !== true
credentialsNeeded[key] !== true
) {
credentialsNeeded[site] = true
credentialsNeeded[key] = true
return getContent(route, function (err, page) {
if (!err) {
withCredsStore.setItem(site, true)
withCredsStore.setItem(key, true)
done(err, page)
} else {
credentialsNeeded[site] = false
credentialsNeeded[key] = false
done(err, page)
}
})
Expand Down Expand Up @@ -643,7 +655,7 @@ siteAdapter.site = function (site) {
// replace flag with temp flags
const tempFlag = createTempFlag(site)
tempFlags[site] = tempFlag
const realFlag = sitePrefix[site] + '/favicon.png'
const realFlag = `${sitePrefix[site]}/favicon.png`
// replace flag with temporary flag where it is used as an image
$('img[src="' + realFlag + '"]').attr('src', tempFlag)
// replace temporary flag where its used as a background to fork event in journal
Expand All @@ -655,19 +667,19 @@ siteAdapter.site = function (site) {

// update storage
routeStore
.removeItem(site)
.removeItem(key)
.then(() => {
findAdapterQ.push({ site }, prefix => {
routeStore
.setItem(site, prefix)
.setItem(key, prefix)
.then(() => {
if (prefix === '') {
console.log(`Refreshed prefix for ${site} is undetermined...`)
} else {
console.log(`Refreshed prefix for ${site} is ${prefix}`)
// replace temp flags
const tempFlag = tempFlags[site]
const realFlag = sitePrefix[site] + '/favicon.png'
const realFlag = `${sitePrefix[site]}/favicon.png`
// replace temporary flag where it is used as an image
$('img[src="' + tempFlag + '"]').attr('src', realFlag)
// replace temporary flag where its used as a background to fork event in journal
Expand Down
Loading