Skip to content

Commit 4687c60

Browse files
committed
Support fetching assets from CDNs when auto-updating
This commit add the ability to fetch from CDN servers when an asset is fetched as a result of auto-update. If an asset has a `cdnURLs` entry in `assets.json`, the asset will be auto-updated using one of those CDN URLs. When many CDN URLs are specified, those URLs will be shuffled in order to spread the bandwidth across all specified CDN servers. If all specified CDN servers fail to respond, uBO will fall back to usual `contentURLs` entry. The `cdnURLs` are used only when an asset is auto-updated, this ensures a user will get the more recent available version of an asset when manually updating. The motivation of this new feature is to relieve GitHub from acting as a CDN (which it is not) for uBO -- an increasing concern with the growing adoption of uBO along with the growing size of key uBO assets.
1 parent 2b5e281 commit 4687c60

File tree

2 files changed

+52
-27
lines changed

2 files changed

+52
-27
lines changed

src/js/assets.js

+47-25
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ const errorCantConnectTo = vAPI.i18n('errorCantConnectTo');
3333

3434
const api = {};
3535

36+
// A hint for various pieces of code to take measures if possible to save
37+
// bandwidth of remote servers.
38+
let remoteServerFriendly = false;
39+
3640
/******************************************************************************/
3741

3842
const observers = [];
@@ -157,14 +161,15 @@ api.fetchText = async function(url) {
157161
// https://github.com/gorhill/uBlock/issues/2592
158162
// Force browser cache to be bypassed, but only for resources which have
159163
// been fetched more than one hour ago.
160-
//
161164
// https://github.com/uBlockOrigin/uBlock-issues/issues/682#issuecomment-515197130
162165
// Provide filter list authors a way to completely bypass
163166
// the browser cache.
164167
// https://github.com/gorhill/uBlock/commit/048bfd251c9b#r37972005
165168
// Use modulo prime numbers to avoid generating the same token at the
166169
// same time across different days.
167-
if ( isExternal ) {
170+
// Do not bypass browser cache if we are asked to be gentle on remote
171+
// servers.
172+
if ( isExternal && remoteServerFriendly !== true ) {
168173
const cacheBypassToken =
169174
µBlock.hiddenSettings.updateAssetBypassBrowserCache
170175
? Math.floor(Date.now() / 1000) % 86413
@@ -743,6 +748,19 @@ const getRemote = async function(assetKey) {
743748
contentURLs = assetDetails.contentURL.slice(0);
744749
}
745750

751+
// If asked to be gentle on remote servers, favour using dedicated CDN
752+
// servers. If more than one CDN server is present, randomly shuffle the
753+
// set of servers so as to spread the bandwidth burden.
754+
if ( remoteServerFriendly && Array.isArray(assetDetails.cdnURLs) ) {
755+
const cdnURLs = assetDetails.cdnURLs.slice();
756+
for ( let i = 0, n = cdnURLs.length; i < n; i++ ) {
757+
const j = Math.floor(Math.random() * n);
758+
if ( j === i ) { continue; }
759+
[ cdnURLs[j], cdnURLs[i] ] = [ cdnURLs[i], cdnURLs[j] ];
760+
}
761+
contentURLs.unshift(...cdnURLs);
762+
}
763+
746764
for ( const contentURL of contentURLs ) {
747765
if ( reIsExternalPath.test(contentURL) === false ) { continue; }
748766

@@ -756,18 +774,17 @@ const getRemote = async function(assetKey) {
756774
if ( result.statusCode === 0 ) {
757775
error = 'network error';
758776
}
759-
registerAssetSource(
760-
assetKey,
761-
{ error: { time: Date.now(), error } }
762-
);
777+
registerAssetSource(assetKey, {
778+
error: { time: Date.now(), error }
779+
});
763780
continue;
764781
}
765782

766783
// Success
767-
assetCacheWrite(
768-
assetKey,
769-
{ content: result.content, url: contentURL }
770-
);
784+
assetCacheWrite(assetKey, {
785+
content: result.content,
786+
url: contentURL
787+
});
771788
registerAssetSource(assetKey, { error: undefined });
772789
return reportBack(result.content);
773790
}
@@ -835,9 +852,10 @@ const updaterAssetDelayDefault = 120000;
835852
const updaterUpdated = [];
836853
const updaterFetched = new Set();
837854

838-
let updaterStatus,
839-
updaterTimer,
840-
updaterAssetDelay = updaterAssetDelayDefault;
855+
let updaterStatus;
856+
let updaterTimer;
857+
let updaterAssetDelay = updaterAssetDelayDefault;
858+
let updaterAuto = false;
841859

842860
const updateFirst = function() {
843861
updaterStatus = 'updating';
@@ -861,25 +879,22 @@ const updateNext = async function() {
861879
if ( updaterFetched.has(assetKey) ) { continue; }
862880
const cacheEntry = cacheDict[assetKey];
863881
if (
864-
cacheEntry &&
882+
(cacheEntry instanceof Object) &&
865883
(cacheEntry.writeTime + assetEntry.updateAfter * 86400000) > now
866884
) {
867885
continue;
868886
}
869887
if (
870-
fireNotification(
871-
'before-asset-updated',
872-
{ assetKey: assetKey, type: assetEntry.content }
873-
) === true
888+
fireNotification('before-asset-updated', {
889+
assetKey,
890+
type: assetEntry.content
891+
}) === true
874892
) {
875893
assetKeyToUpdate = assetKey;
876894
break;
877895
}
878896
// This will remove a cached asset when it's no longer in use.
879-
if (
880-
cacheEntry &&
881-
cacheEntry.readTime < assetCacheRegistryStartTime
882-
) {
897+
if ( cacheEntry && cacheEntry.readTime < assetCacheRegistryStartTime ) {
883898
assetCacheRemove(assetKey);
884899
}
885900
}
@@ -888,7 +903,13 @@ const updateNext = async function() {
888903
}
889904
updaterFetched.add(assetKeyToUpdate);
890905

906+
// In auto-update context, be gentle on remote servers.
907+
remoteServerFriendly = updaterAuto;
908+
891909
const result = await getRemote(assetKeyToUpdate);
910+
911+
remoteServerFriendly = false;
912+
892913
if ( result.content !== '' ) {
893914
updaterUpdated.push(result.assetKey);
894915
if ( result.assetKey === 'assets.json' ) {
@@ -912,10 +933,11 @@ const updateDone = function() {
912933

913934
api.updateStart = function(details) {
914935
const oldUpdateDelay = updaterAssetDelay;
915-
const newUpdateDelay = typeof details.delay === 'number' ?
916-
details.delay :
917-
updaterAssetDelayDefault;
936+
const newUpdateDelay = typeof details.delay === 'number'
937+
? details.delay
938+
: updaterAssetDelayDefault;
918939
updaterAssetDelay = Math.min(oldUpdateDelay, newUpdateDelay);
940+
updaterAuto = details.auto === true;
919941
if ( updaterStatus !== undefined ) {
920942
if ( newUpdateDelay < oldUpdateDelay ) {
921943
clearTimeout(updaterTimer);

src/js/storage.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -1144,6 +1144,8 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
11441144
const json = await vAPI.adminStorage.getItem('adminSettings');
11451145
if ( typeof json === 'string' && json !== '' ) {
11461146
data = JSON.parse(json);
1147+
} else if ( json instanceof Object ) {
1148+
data = json;
11471149
}
11481150
} catch (ex) {
11491151
console.error(ex);
@@ -1247,7 +1249,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
12471249

12481250
/******************************************************************************/
12491251

1250-
µBlock.scheduleAssetUpdater = (function() {
1252+
µBlock.scheduleAssetUpdater = (( ) => {
12511253
let timer, next = 0;
12521254

12531255
return function(updateDelay) {
@@ -1271,7 +1273,8 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
12711273
next = 0;
12721274
this.assets.updateStart({
12731275
delay: this.hiddenSettings.autoUpdateAssetFetchPeriod * 1000 ||
1274-
120000
1276+
120000,
1277+
auto: true,
12751278
});
12761279
}, updateDelay);
12771280
};

0 commit comments

Comments
 (0)