assets.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017
  1. /*******************************************************************************
  2. ηMatrix - a browser extension to black/white list requests.
  3. Copyright (C) 2013-2019 Raymond Hill
  4. Copyright (C) 2019-2022 Alessio Vanni
  5. This program is free software: you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation, either version 3 of the License, or
  8. (at your option) any later version.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with this program. If not, see {http://www.gnu.org/licenses/}.
  15. Home: https://gitlab.com/vannilla/ematrix
  16. uMatrix Home: https://github.com/gorhill/uMatrix
  17. */
  18. 'use strict';
  19. ηMatrix.assets = (function() {
  20. let api = {};
  21. let observers = [];
  22. let externalPathRegex = /^(?:[a-z-]+):\/\//;
  23. let connectionError = vAPI.i18n('errorCantConnectTo');
  24. let notifyObservers = function (topic, details) {
  25. let result;
  26. for (let i=0; i<observers.length; ++i) {
  27. result = observers[i](topic, details);
  28. }
  29. return result;
  30. }
  31. function isEmptyString(s) {
  32. return (typeof s === 'string' && s === '');
  33. }
  34. function noOp() {
  35. return;
  36. }
  37. // Cache Registry
  38. var cacheRegistry = new Object();
  39. let cacheRegistryReady = false;
  40. let cacheRegistryCallbacks = undefined;
  41. let cacheRegistryStart = Date.now();
  42. let saveCacheRegistry = (function () {
  43. let timer;
  44. let save = function () {
  45. timer = undefined;
  46. vAPI.cacheStorage.set({
  47. assetCacheRegistry: cacheRegistry,
  48. });
  49. }
  50. return function (lazy) {
  51. if (timer !== undefined) {
  52. clearTimeout(timer);
  53. }
  54. if (lazy === true) {
  55. timer = vAPI.setTimeout(save, 500);
  56. } else {
  57. save();
  58. }
  59. };
  60. })();
  61. let getCacheRegistry = function (callback) {
  62. if (cacheRegistryReady == true) {
  63. callback(cacheRegistry);
  64. return;
  65. }
  66. if (cacheRegistryCallbacks !== undefined) {
  67. // If it's not undefined it's always an array
  68. //
  69. // eMatrix: this block in particular is probably never
  70. // called: originally, it was used because uMatrix called
  71. // some code in a callback that could wait
  72. //
  73. // While waiting, more elements could've been pushed in
  74. // the array
  75. //
  76. // Since the waiting callback is not used here, any
  77. // further callback are likely to be handled by the
  78. // condition above
  79. // This block is left here just in case
  80. cacheRegistryCallbacks.push(callback);
  81. return;
  82. }
  83. cacheRegistryCallbacks = [callback];
  84. let onRead = function (bin) {
  85. if (!bin || !bin['assetCacheRegistry']) {
  86. cacheRegistry = {};
  87. } else {
  88. cacheRegistry = bin['assetCacheRegistry'];
  89. }
  90. cacheRegistryReady = true;
  91. let f;
  92. while ((f = cacheRegistryCallbacks.shift())) {
  93. f(cacheRegistry);
  94. }
  95. };
  96. vAPI.cacheStorage.get('assetCacheRegistry', onRead);
  97. };
  98. let readCache = function (key, callback) {
  99. let fullkey = 'cache/' + key;
  100. let cache = { placeholder: true };
  101. let report = function (content, error) {
  102. let details = {
  103. assetKey: key,
  104. content: content,
  105. };
  106. if (error) {
  107. details.error = error;
  108. }
  109. return callback(details);
  110. };
  111. let onRead = function (bin) {
  112. if (!bin || !bin[fullkey]) {
  113. return report('', 'E_NOTFOUND');
  114. }
  115. let entry = cache[key];
  116. if (entry === undefined) {
  117. return report('', 'E_NOTFOUND');
  118. }
  119. entry.readTime = Date.now();
  120. saveCacheRegistry(true);
  121. report(bin[fullkey]);
  122. };
  123. let onReady = function (registry) {
  124. cache = registry;
  125. vAPI.cacheStorage.get(fullkey, onRead);
  126. };
  127. getCacheRegistry(onReady);
  128. };
  129. let writeCache = function (key, details, callback) {
  130. let fullkey = 'cache/' + key;
  131. let content = '';
  132. if (typeof details === 'string') {
  133. content = details;
  134. } else if (details instanceof Object) {
  135. content = details.content || '';
  136. }
  137. if (content === '') {
  138. return removeCache(key, callback);
  139. }
  140. let report = function (content) {
  141. let details = {
  142. assetKey: key,
  143. content: content,
  144. };
  145. if (typeof callback === 'function') {
  146. callback(details);
  147. }
  148. notifyObservers('after-asset-updated', details);
  149. };
  150. let onReady = function (registry) {
  151. let entry = registry[key];
  152. if (entry === undefined) {
  153. entry = registry[key] = {};
  154. }
  155. entry.writeTime = entry.readTime = Date.now();
  156. if (details instanceof Object && typeof details.url === 'string') {
  157. entry.remoteURL = details.url;
  158. }
  159. let bin = {
  160. assetCacheRegistry: cacheRegistry,
  161. };
  162. bin[fullkey] = content;
  163. vAPI.cacheStorage.set(bin);
  164. report(content);
  165. };
  166. getCacheRegistry(onReady);
  167. };
  168. let removeCache = function (pattern, callback) {
  169. let onReady = function (cache) {
  170. let entries = [];
  171. let contents = [];
  172. for (let key in cache) {
  173. if (pattern instanceof RegExp && !pattern.test(key)) {
  174. continue;
  175. }
  176. if (typeof pattern === 'string' && key !== pattern) {
  177. continue;
  178. }
  179. entries.push(key);
  180. contents.push('cache/' + key);
  181. delete cache[key];
  182. }
  183. if (contents.length !== 0) {
  184. vAPI.cacheStorage.remove(contents);
  185. let bin = {
  186. assetCacheRegistry: cache,
  187. };
  188. vAPI.cacheStorage.set(bin);
  189. }
  190. if (typeof callback === 'function') {
  191. callback();
  192. }
  193. for (let i=0; i<entries.length; ++i) {
  194. notifyObservers('after-asset-updated', {
  195. assetKey: entries[i],
  196. });
  197. }
  198. };
  199. getCacheRegistry(onReady);
  200. };
  201. let markDirtyCache = function (pattern, exclude, callback) {
  202. let onReady = function (registry) {
  203. let entry;
  204. let mustSave = false;
  205. for (let key in registry) {
  206. if (pattern instanceof RegExp && pattern.test(key) === false) {
  207. continue;
  208. } else if (typeof pattern === 'string' && key !== pattern) {
  209. continue;
  210. } else if (Array.isArray(pattern)
  211. && pattern.indexOf(key) === -1) {
  212. continue;
  213. }
  214. if (exclude instanceof RegExp && exclude.test(key)) {
  215. continue;
  216. } else if (typeof exclude === 'string' && key === exclude) {
  217. continue;
  218. } else if (Array.isArray(exclude)
  219. && exclude.indexOf(key) !== -1) {
  220. continue;
  221. }
  222. entry = registry[key];
  223. if (!entry.writeTime) {
  224. continue;
  225. }
  226. registry[key].writeTime = 0;
  227. mustSave = true;
  228. }
  229. if (mustSave) {
  230. let bin = {
  231. assetCacheRegistry: registry,
  232. };
  233. vAPI.cacheStorage.set(bin);
  234. }
  235. if (typeof callback === 'function') {
  236. callback();
  237. }
  238. };
  239. if (typeof exclude === 'function') {
  240. callback = exclude;
  241. exclude = undefined;
  242. }
  243. getCacheRegistry(onReady);
  244. };
  245. // Source Registry
  246. var sourceRegistry = Object.create(null);
  247. let sourceRegistryReady = false;
  248. let sourceRegistryCallbacks = undefined;
  249. let saveSourceRegistry = (function () {
  250. let timer;
  251. let save = function () {
  252. timer = undefined;
  253. vAPI.cacheStorage.set({
  254. assetSourceRegistry: sourceRegistry,
  255. });
  256. }
  257. return function (lazy) {
  258. if (timer !== undefined) {
  259. clearTimeout(timer);
  260. }
  261. if (lazy === true) {
  262. timer = vAPI.setTimeout(save, 500);
  263. } else {
  264. save();
  265. }
  266. };
  267. })();
  268. let registerSource = function (key, details) {
  269. let entry = sourceRegistry[key] || {};
  270. for (let p in details) {
  271. if (details.hasOwnProperty(p) === false) {
  272. continue;
  273. }
  274. if (details[p] !== undefined) {
  275. entry[p] = details[p];
  276. } else {
  277. delete entry[p];
  278. }
  279. }
  280. let contentUrl = details.contentURL;
  281. if (contentUrl) {
  282. if (typeof contentUrl === 'string') {
  283. contentUrl = entry.contentURL = [contentUrl];
  284. } else if (Array.isArray(contentUrl) === false) {
  285. contentUrl = entry.contentURL = [];
  286. }
  287. let remoteCount = 0;
  288. for (let i=0; i<contentUrl.length; ++i) {
  289. if (externalPathRegex.test(contentUrl[i])) {
  290. ++remoteCount;
  291. }
  292. }
  293. entry.hasLocalURL = (remoteCount !== contentUrl.length);
  294. entry.hasRemoteURL = (remoteCount !== 0);
  295. } else {
  296. entry.contentURL = [];
  297. }
  298. if (typeof entry.updateAfter !== 'number') {
  299. entry.updateAfter = 13;
  300. }
  301. if (entry.submitter) {
  302. entry.submitTime = Date.now(); // Detects stale entries
  303. }
  304. sourceRegistry[key] = entry;
  305. };
  306. let unregisterSource = function (key) {
  307. removeCache(key);
  308. delete sourceRegistry[key];
  309. saveSourceRegistry();
  310. };
  311. let updateSourceRegistry = function (string, silent) {
  312. let json;
  313. try {
  314. json = JSON.parse(string);
  315. } catch (e) {
  316. return;
  317. }
  318. if (json instanceof Object === false) {
  319. return;
  320. }
  321. for (let key in sourceRegistry) {
  322. if (json[key] === undefined
  323. && sourceRegistry[key].submitter === undefined) {
  324. unregisterSource(key);
  325. }
  326. }
  327. for (let key in json) {
  328. if (sourceRegistry[key] === undefined && !silent) {
  329. notifyObservers('builtin-asset-source-added', {
  330. assetKey: key,
  331. entry: json[key],
  332. });
  333. }
  334. registerSource(key, json[key]);
  335. }
  336. saveSourceRegistry();
  337. };
  338. let getSourceRegistry = function (callback) {
  339. if (sourceRegistryReady === true) {
  340. callback(sourceRegistry);
  341. return;
  342. }
  343. if (sourceRegistryCallbacks !== undefined) {
  344. // If it's not undefined it's always an array
  345. sourceRegistryCallbacks.push(callback);
  346. return;
  347. }
  348. sourceRegistryCallbacks = [callback];
  349. let onReady = function () {
  350. sourceRegistryReady = true;
  351. let f;
  352. while ((f = sourceRegistryCallbacks.shift())) {
  353. f(sourceRegistry);
  354. }
  355. };
  356. let createRegistry = function () {
  357. api.fetchText
  358. (ηMatrix.assetsBootstrapLocation || 'assets/assets.json',
  359. function (details) {
  360. updateSourceRegistry(details.content, true);
  361. onReady();
  362. });
  363. };
  364. let onRead = function (bin) {
  365. if (!bin || !bin.assetSourceRegistry
  366. || Object.keys(bin.assetSourceRegistry).length == 0) {
  367. createRegistry();
  368. return;
  369. }
  370. sourceRegistry = bin.assetSourceRegistry;
  371. onReady();
  372. };
  373. vAPI.cacheStorage.get('assetSourceRegistry', onRead);
  374. };
  375. // Remote
  376. let getRemote = function (key, callback) {
  377. let assetDetails = {};
  378. let contentUrl = '';
  379. let urls = [];
  380. let report = function (content, error) {
  381. let details = {
  382. assetKey: key,
  383. content: content,
  384. };
  385. if (error) {
  386. details.error = assetDetails.lastError = error;
  387. } else {
  388. assetDetails.lastError = undefined;
  389. }
  390. callback(details);
  391. };
  392. let tryLoad = function (tries) {
  393. let tr = (tries === undefined) ? 10 : tries;
  394. if (tr <= 0) {
  395. console.warn('ηMatrix could not load the asset '
  396. +assetDetails.title);
  397. return;
  398. }
  399. while ((contentUrl = urls.shift())) {
  400. if (externalPathRegex.test(contentUrl)) {
  401. break;
  402. }
  403. }
  404. if (!contentUrl) {
  405. report('', 'E_NOTFOUND');
  406. return;
  407. }
  408. api.fetchText(contentUrl,
  409. onRemoteContentLoad,
  410. onRemoteContentError,
  411. tr-1);
  412. };
  413. let onRemoteContentLoad = function (details, tries) {
  414. if (isEmptyString(details.content) === true) {
  415. registerSource(key, {
  416. error: {
  417. time: Date.now(),
  418. error: 'No content'
  419. }
  420. });
  421. return tryLoad(tries);
  422. }
  423. writeCache(key, {
  424. content: details.content,
  425. url: contentUrl,
  426. });
  427. registerSource(key, {error: undefined});
  428. report(details.content);
  429. };
  430. let onRemoteContentError = function (details, tries) {
  431. let text = details.statusText;
  432. if (details.statusCode === 0) {
  433. text = 'network error';
  434. }
  435. registerSource(key, {
  436. error: {
  437. time: Date.now(),
  438. error: text,
  439. }
  440. });
  441. tryLoad(tries);
  442. };
  443. let onReady = function (registry) {
  444. assetDetails = registry[key] || {};
  445. if (typeof assetDetails.contentURL === 'string') {
  446. urls = [assetDetails.contentURL];
  447. } else if (Array.isArray(assetDetails.contentURL)) {
  448. urls = assetDetails.contentURL.slice(0);
  449. }
  450. tryLoad();
  451. };
  452. getSourceRegistry(onReady);
  453. };
  454. // Updater
  455. let updateStatus = 'stop';
  456. let updateDefaultDelay = 120000;
  457. let updateDelay = updateDefaultDelay;
  458. let updateTimer;
  459. let updated = [];
  460. let updateFetch = new Set();
  461. let updateStart = function () {
  462. updateStatus = 'running';
  463. updateFetch.clear();
  464. updated = [];
  465. notifyObservers('before-assets-updated');
  466. updateNext();
  467. };
  468. let updateNext = function () {
  469. let cacheReg = undefined;
  470. let sourceReg = undefined;
  471. let gcOne = function (key) {
  472. let entry = cacheReg[key];
  473. if (entry && entry.readTime < cacheRegistryStart) {
  474. removeCache(key);
  475. }
  476. };
  477. let findOne = function () {
  478. let now = Date.now();
  479. let sourceEntry;
  480. let cacheEntry;
  481. for (let key in sourceReg) {
  482. sourceEntry = sourceReg[key];
  483. if (sourceEntry.hasRemoteURL !== true) {
  484. continue;
  485. }
  486. if (updateFetch.has(key)) {
  487. continue;
  488. }
  489. cacheEntry = cacheReg[key];
  490. if (cacheEntry
  491. && (cacheEntry.writeTime
  492. + sourceEntry.updateAfter*86400000) > now) {
  493. continue;
  494. }
  495. if (notifyObservers('before-asset-updated', {assetKey: key})) {
  496. return key;
  497. }
  498. gcOne(key);
  499. }
  500. return undefined;
  501. };
  502. let onUpdate = function (details) {
  503. if (details.content !== '') {
  504. updated.push(details.assetKey);
  505. if (details.assetKey === 'assets.json') {
  506. updateSourceRegistry(details.content);
  507. }
  508. } else {
  509. notifyObservers('asset-update-failed', {
  510. assetKey: details.assetKey,
  511. });
  512. }
  513. if (findOne() !== undefined) {
  514. vAPI.setTimeout(updateNext, updateDelay);
  515. } else {
  516. updateEnd();
  517. }
  518. };
  519. let updateOne = function () {
  520. let key = findOne();
  521. if (key === undefined) {
  522. updateEnd();
  523. return;
  524. }
  525. updateFetch.add(key);
  526. getRemote(key, onUpdate);
  527. };
  528. let onSourceReady = function (registry) {
  529. sourceReg = registry;
  530. updateOne();
  531. };
  532. let onCacheReady = function (registry) {
  533. cacheReg = registry;
  534. getSourceRegistry(onSourceReady);
  535. };
  536. getCacheRegistry(onCacheReady);
  537. };
  538. let updateEnd = function () {
  539. let keys = updated.slice(0);
  540. updateFetch.clear();
  541. updateStatus = 'stop';
  542. updateDelay = updateDefaultDelay;
  543. notifyObservers('after-asset-updated', {
  544. assetKeys: keys,
  545. });
  546. };
  547. // Assets API
  548. api.addObserver = function (observer) {
  549. if (observers.indexOf(observer) === -1) {
  550. observers.push(observer);
  551. }
  552. };
  553. api.removeObserver = function (observer) {
  554. let pos = observers.indexOf(observer);
  555. while (pos !== -1) {
  556. observers.splice(pos, 1);
  557. pos = observers.indexOf(observer);
  558. }
  559. };
  560. api.fetchText = function (url, onLoad, onError, tries) {
  561. let iurl = externalPathRegex.test(url) ? url : vAPI.getURL(url);
  562. let tr = (tries === undefined) ? 10 : tries;
  563. if (typeof onError !== 'function') {
  564. onError = onLoad;
  565. }
  566. let onResponseReceived = function () {
  567. this.onload = this.onerror = this.ontimeout = null;
  568. let details = {
  569. url: url,
  570. content: '',
  571. // On local files this.status is 0, but the request
  572. // is successful
  573. statusCode: this.status || 200,
  574. statusText: this.statusText || '',
  575. };
  576. if (details.statusCode < 200 || details.statusCode >= 300) {
  577. return onError.call(null, details, tr);
  578. }
  579. if (isEmptyString(this.responseText) === true) {
  580. return onError.call(null, details, tr);
  581. }
  582. let t = this.responseText.trim();
  583. // Discard HTML as it's probably an error
  584. // (the request expects plain text as a response)
  585. if (t.startsWith('<') && t.endsWith('>')) {
  586. return onError.call(null, details, tr);
  587. }
  588. details.content = t;
  589. return onLoad.call(null, details, tr);
  590. };
  591. let onErrorReceived = function () {
  592. this.onload = this.onerror = this.ontimeout = null;
  593. ηMatrix.logger.writeOne('', 'error',
  594. connectionError.replace('{{url}}', iurl));
  595. onError.call(null, {
  596. url: url,
  597. content: '',
  598. }, tr);
  599. };
  600. let req = new XMLHttpRequest();
  601. req.open('GET', iurl, true);
  602. req.timeout = 30000;
  603. req.onload = onResponseReceived;
  604. req.onerror = onErrorReceived;
  605. req.ontimeout = onErrorReceived;
  606. req.responseType = 'text';
  607. try {
  608. // This can throw in some cases
  609. req.send();
  610. } catch (e) {
  611. onErrorReceived.call(req);
  612. }
  613. };
  614. api.registerAssetSource = function (key, details) {
  615. getSourceRegistry(function () {
  616. registerSource(key, details);
  617. saveSourceRegistry(true);
  618. });
  619. };
  620. api.unregisterAssetSource = function (key) {
  621. getSourceRegistry(function () {
  622. unregisterSource(key);
  623. saveSourceRegistry(true);
  624. });
  625. };
  626. api.get = function (key, options, callback) {
  627. let cb;
  628. let opt;
  629. if (typeof options === 'function') {
  630. cb = options;
  631. opt = {};
  632. } else if (typeof callback !== 'function') {
  633. cb = noOp;
  634. opt = options;
  635. } else {
  636. cb = callback;
  637. opt = options;
  638. }
  639. let assetDetails = {};
  640. let urls = [];
  641. let contentUrl = '';
  642. let report = function (content, error) {
  643. let details = {
  644. assetKey: key,
  645. content: content,
  646. };
  647. if (error) {
  648. details.error = assetDetails.error = error;
  649. } else {
  650. assetDetails.error = undefined;
  651. }
  652. cb(details);
  653. };
  654. let onContentNotLoaded = function (details, tries) {
  655. let external;
  656. let tr = (tries === undefined) ? 10 : tries;
  657. if (tr <= 0) {
  658. console.warn('ηMatrix couldn\'t download the asset '
  659. +assetDetails.title);
  660. return;
  661. }
  662. while ((contentUrl = urls.shift())) {
  663. external = externalPathRegex.test(contentUrl);
  664. if (external === true && assetDetails.loaded !== true) {
  665. break;
  666. }
  667. if (external === false || assetDetails.hasLocalURL !== true) {
  668. break;
  669. }
  670. }
  671. if (!contentUrl) {
  672. return report('', 'E_NOTFOUND');
  673. }
  674. api.fetchText(contentUrl,
  675. onContentLoaded,
  676. onContentNotLoaded,
  677. tr-1);
  678. };
  679. let onContentLoaded = function (details, tries) {
  680. if (isEmptyString(details.content) === true) {
  681. return onContentNotLoaded(undefined, tries);
  682. }
  683. if (externalPathRegex.test(details.url)
  684. && opt.dontCache !== true) {
  685. writeCache(key, {
  686. content: details.content,
  687. url: contentUrl,
  688. });
  689. }
  690. assetDetails.loaded = true;
  691. report(details.content);
  692. };
  693. let onCachedContentLoad = function (details) {
  694. if (details.content !== '') {
  695. return report(details.content);
  696. }
  697. let onRead = function (details) {
  698. console.debug(details);
  699. report(details.content || 'missing contents');
  700. }
  701. let onReady = function (registry) {
  702. assetDetails = registry[key] || {};
  703. if (typeof assetDetails.contentURL === 'string') {
  704. urls = [assetDetails.contentURL];
  705. } else if (Array.isArray(assetDetails.contentURL)) {
  706. urls = assetDetails.contentURL.slice(0);
  707. }
  708. if (true === assetDetails.loaded) {
  709. readCache(key, onRead);
  710. } else {
  711. onContentNotLoaded();
  712. }
  713. }
  714. getSourceRegistry(onReady);
  715. };
  716. readCache(key, onCachedContentLoad);
  717. };
  718. api.put = function (key, content, callback) {
  719. writeCache(key, content, callback);
  720. };
  721. api.metadata = function (callback) {
  722. let onSourceReady = function (registry) {
  723. let source = JSON.parse(JSON.stringify(registry));
  724. let cache = cacheRegistry;
  725. let sourceEntry;
  726. let cacheEntry;
  727. let now = Date.now();
  728. let obsoleteAfter;
  729. for (let key in source) {
  730. sourceEntry = source[key];
  731. cacheEntry = cache[key];
  732. if (cacheEntry) {
  733. sourceEntry.cached = true;
  734. sourceEntry.writeTime = cacheEntry.writeTime;
  735. obsoleteAfter = cacheEntry.writeTime
  736. + sourceEntry.updateAfter * 86400000;
  737. sourceEntry.obsolete = obsoleteAfter < now;
  738. sourceEntry.remoteURL = cacheEntry.remoteURL;
  739. } else {
  740. sourceEntry.writeTime = 0;
  741. obsoleteAfter = 0;
  742. sourceEntry.obsolete = true;
  743. }
  744. }
  745. callback(source);
  746. }
  747. let onCacheReady = function () {
  748. getSourceRegistry(onSourceReady);
  749. }
  750. getCacheRegistry(onCacheReady);
  751. };
  752. api.purge = function (pattern, exclude, callback) {
  753. markDirtyCache(pattern, exclude, callback);
  754. };
  755. api.remove = function (pattern, callback) {
  756. removeCache(pattern, callback);
  757. };
  758. api.rmrf = function () {
  759. removeCache(/./);
  760. };
  761. api.updateStart = function (details) {
  762. let oldDelay = updateDelay;
  763. let newDelay = details.delay || updateDefaultDelay;
  764. updateDelay = Math.min(oldDelay, newDelay);
  765. if (updateStatus !== 'stop') {
  766. if (newDelay < oldDelay) {
  767. clearTimeout(updateTimer);
  768. updateTimer = vAPI.setTimeout(updateNext, updateDelay);
  769. }
  770. return;
  771. }
  772. updateStart();
  773. };
  774. api.updateStop = function () {
  775. if (updateTimer) {
  776. clearTimeout(updateTimer);
  777. updateTimer = undefined;
  778. }
  779. if (updateStatus === 'running') {
  780. updateEnd();
  781. }
  782. };
  783. api.checkVersion = function () {
  784. let cache;
  785. let onSourceReady = function (registry) {
  786. let source = JSON.parse(JSON.stringify(registry));
  787. let version = ηMatrix.userSettings.assetVersion;
  788. if (!version) {
  789. ηMatrix.userSettings.assetVersion = 1;
  790. version = 1;
  791. }
  792. if (!source["assets.json"].version
  793. || version > source["assets.json"].version) {
  794. for (let key in source) {
  795. switch (key) {
  796. case "hphosts":
  797. case "malware-0":
  798. case "malware-1":
  799. delete source[key];
  800. api.remove(key, function () {});
  801. break;
  802. default:
  803. break;
  804. }
  805. source["assets.json"].version = version;
  806. }
  807. let createRegistry = function () {
  808. api.fetchText
  809. (ηMatrix.assetsBootstrapLocation || 'assets/assets.json',
  810. function (details) {
  811. updateSourceRegistry(details.content, true);
  812. });
  813. };
  814. createRegistry();
  815. }
  816. };
  817. let onCacheReady = function (registry) {
  818. cache = JSON.parse(JSON.stringify(registry));
  819. getSourceRegistry(onSourceReady);
  820. };
  821. getCacheRegistry(onCacheReady);
  822. };
  823. return api;
  824. })();